diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc index 38da52af0bc94..47ee437c29e18 100644 --- a/chrome/browser/extensions/api/file_system/file_system_api.cc +++ b/chrome/browser/extensions/api/file_system/file_system_api.cc @@ -67,6 +67,7 @@ #include "chrome/browser/chromeos/file_manager/app_id.h" #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" #include "chrome/browser/extensions/api/file_system/request_file_system_dialog_view.h" +#include "chrome/browser/extensions/api/file_system/request_file_system_notification.h" #include "chrome/browser/ui/simple_message_box.h" #include "components/user_manager/user_manager.h" #include "extensions/common/constants.h" @@ -293,22 +294,25 @@ ConsentProvider::ConsentProvider(DelegateInterface* delegate) ConsentProvider::~ConsentProvider() { } -void ConsentProvider::RequestConsent(const extensions::Extension& extension, - base::WeakPtr volume, - bool writable, - const ConsentCallback& callback) { +void ConsentProvider::RequestConsent( + const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable, + const ConsentCallback& callback) { DCHECK(IsGrantable(extension)); - // If auto-launched kiosk app, then no need to ask user. - if (delegate_->IsAutoLaunched(extension)) { + // If a whitelisted component, then no need to ask or inform the user. + if (extension.location() == Manifest::COMPONENT && + delegate_->IsWhitelistedComponent(extension)) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(callback, CONSENT_GRANTED)); return; } - // If a whitelisted component, then no need to ask user, either. - if (extension.location() == Manifest::COMPONENT && - delegate_->IsWhitelistedComponent(extension)) { + // If auto-launched kiosk app, then no need to ask user either, but show the + // notification. + if (delegate_->IsAutoLaunched(extension)) { + delegate_->ShowNotification(extension, volume, writable); base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(callback, CONSENT_GRANTED)); return; @@ -371,7 +375,7 @@ void ConsentProviderDelegate::SetAutoDialogButtonForTest( void ConsentProviderDelegate::ShowDialog( const extensions::Extension& extension, - base::WeakPtr volume, + const base::WeakPtr& volume, bool writable, const file_system_api::ConsentProvider::ShowDialogCallback& callback) { content::WebContents* const foreground_contents = @@ -400,6 +404,14 @@ void ConsentProviderDelegate::ShowDialog( writable, base::Bind(callback)); } +void ConsentProviderDelegate::ShowNotification( + const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable) { + RequestFileSystemNotification::ShowAutoGrantedNotification( + profile_, extension, volume, writable); +} + bool ConsentProviderDelegate::IsAutoLaunched( const extensions::Extension& extension) { chromeos::KioskAppManager::App app_info; @@ -1270,7 +1282,7 @@ ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() { } void FileSystemRequestFileSystemFunction::OnConsentReceived( - base::WeakPtr volume, + const base::WeakPtr& volume, bool writable, ConsentProvider::Consent result) { using file_manager::VolumeManager; diff --git a/chrome/browser/extensions/api/file_system/file_system_api.h b/chrome/browser/extensions/api/file_system/file_system_api.h index 7ae7c386f2885..c6a6e9fb6fa94 100644 --- a/chrome/browser/extensions/api/file_system/file_system_api.h +++ b/chrome/browser/extensions/api/file_system/file_system_api.h @@ -61,10 +61,16 @@ class ConsentProvider { public: // Shows a dialog for granting permissions. virtual void ShowDialog(const extensions::Extension& extension, - base::WeakPtr volume, + const base::WeakPtr& volume, bool writable, const ShowDialogCallback& callback) = 0; + // Shows a notification about permissions automatically granted access. + virtual void ShowNotification( + const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable) = 0; + // Checks if the extension was launched in auto-launch kiosk mode. virtual bool IsAutoLaunched(const extensions::Extension& extension) = 0; @@ -80,7 +86,7 @@ class ConsentProvider { // volume by the |extension|. Must be called only if the extension is // grantable, which can be checked with IsGrantable(). void RequestConsent(const extensions::Extension& extension, - base::WeakPtr volume, + const base::WeakPtr& volume, bool writable, const ConsentCallback& callback); @@ -114,10 +120,13 @@ class ConsentProviderDelegate : public ConsentProvider::DelegateInterface { // ConsentProvider::DelegateInterface overrides: void ShowDialog(const extensions::Extension& extension, - base::WeakPtr volume, + const base::WeakPtr& volume, bool writable, const file_system_api::ConsentProvider::ShowDialogCallback& callback) override; + void ShowNotification(const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable) override; bool IsAutoLaunched(const extensions::Extension& extension) override; bool IsWhitelistedComponent(const extensions::Extension& extension) override; @@ -378,7 +387,7 @@ class FileSystemRequestFileSystemFunction : public UIThreadExtensionFunction { private: // Called when a user grants or rejects permissions for the file system // access. - void OnConsentReceived(base::WeakPtr volume, + void OnConsentReceived(const base::WeakPtr& volume, bool writable, file_system_api::ConsentProvider::Consent result); diff --git a/chrome/browser/extensions/api/file_system/file_system_api_unittest.cc b/chrome/browser/extensions/api/file_system/file_system_api_unittest.cc index e78f656979077..9a01de9b0bdbc 100644 --- a/chrome/browser/extensions/api/file_system/file_system_api_unittest.cc +++ b/chrome/browser/extensions/api/file_system/file_system_api_unittest.cc @@ -85,6 +85,7 @@ class TestingConsentProviderDelegate public: TestingConsentProviderDelegate() : show_dialog_counter_(0), + show_notification_counter_(0), dialog_button_(ui::DIALOG_BUTTON_NONE), is_auto_launched_(false) {} @@ -104,18 +105,25 @@ class TestingConsentProviderDelegate } int show_dialog_counter() const { return show_dialog_counter_; } + int show_notification_counter() const { return show_notification_counter_; } private: // ConsentProvider::DelegateInterface overrides: void ShowDialog( const extensions::Extension& extension, - base::WeakPtr volume, + const base::WeakPtr& volume, bool writable, const ConsentProvider::ShowDialogCallback& callback) override { ++show_dialog_counter_; callback.Run(dialog_button_); } + void ShowNotification(const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable) override { + ++show_notification_counter_; + } + bool IsAutoLaunched(const extensions::Extension& extension) override { return is_auto_launched_; } @@ -125,6 +133,7 @@ class TestingConsentProviderDelegate } int show_dialog_counter_; + int show_notification_counter_; ui::DialogButton dialog_button_; bool is_auto_launched_; std::string whitelisted_component_id_; @@ -330,6 +339,7 @@ TEST_F(FileSystemApiConsentProviderTest, ForNonKioskApps) { base::RunLoop().RunUntilIdle(); EXPECT_EQ(0, delegate.show_dialog_counter()); + EXPECT_EQ(0, delegate.show_notification_counter()); EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); } @@ -346,7 +356,7 @@ TEST_F(FileSystemApiConsentProviderTest, ForNonKioskApps) { TEST_F(FileSystemApiConsentProviderTest, ForKioskApps) { // Non-component apps in auto-launch kiosk mode will be granted access - // instantly without asking for user consent. + // instantly without asking for user consent, but with a notification. { scoped_refptr auto_launch_kiosk_app( test_util::BuildApp(ExtensionBuilder().Pass()) @@ -369,6 +379,7 @@ TEST_F(FileSystemApiConsentProviderTest, ForKioskApps) { base::RunLoop().RunUntilIdle(); EXPECT_EQ(0, delegate.show_dialog_counter()); + EXPECT_EQ(1, delegate.show_notification_counter()); EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); } @@ -394,6 +405,7 @@ TEST_F(FileSystemApiConsentProviderTest, ForKioskApps) { base::RunLoop().RunUntilIdle(); EXPECT_EQ(1, delegate.show_dialog_counter()); + EXPECT_EQ(0, delegate.show_notification_counter()); EXPECT_EQ(ConsentProvider::CONSENT_GRANTED, result); } @@ -412,6 +424,7 @@ TEST_F(FileSystemApiConsentProviderTest, ForKioskApps) { base::RunLoop().RunUntilIdle(); EXPECT_EQ(1, delegate.show_dialog_counter()); + EXPECT_EQ(0, delegate.show_notification_counter()); EXPECT_EQ(ConsentProvider::CONSENT_REJECTED, result); } } diff --git a/chrome/browser/extensions/api/file_system/request_file_system_notification.cc b/chrome/browser/extensions/api/file_system/request_file_system_notification.cc new file mode 100644 index 0000000000000..bbdbd6b61f457 --- /dev/null +++ b/chrome/browser/extensions/api/file_system/request_file_system_notification.cc @@ -0,0 +1,122 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/file_system/request_file_system_notification.h" + +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/file_manager/volume_manager.h" +#include "chrome/browser/extensions/app_icon_loader_impl.h" +#include "chrome/grit/generated_resources.h" +#include "extensions/common/extension.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/message_center/message_center.h" +#include "ui/message_center/notification.h" +#include "ui/message_center/notification_delegate.h" +#include "ui/message_center/notification_types.h" +#include "ui/message_center/notifier_settings.h" + +using file_manager::Volume; +using message_center::Notification; + +namespace { + +// Extension icon size for the notification. +const int kIconSize = 48; + +scoped_ptr CreateAutoGrantedNotification( + const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable, + message_center::NotificationDelegate* delegate) { + DCHECK(delegate); + + // If the volume is gone, then do not show the notification. + if (!volume.get()) + return scoped_ptr(nullptr); + + const std::string notification_id = + extension.id() + "-" + volume->volume_id(); + message_center::RichNotificationData data; + data.clickable = false; + + // TODO(mtomasz): Share this code with RequestFileSystemDialogView. + const base::string16 display_name = + base::UTF8ToUTF16(!volume->volume_label().empty() ? volume->volume_label() + : volume->volume_id()); + const base::string16 message = l10n_util::GetStringFUTF16( + writable + ? IDS_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_WRITABLE_MESSAGE + : IDS_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_MESSAGE, + display_name); + + scoped_ptr notification(new Notification( + message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, + base::UTF8ToUTF16(extension.name()), message, + gfx::Image(), // Updated asynchronously later. + base::string16(), // display_source + message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, + notification_id), + data, delegate)); + + return notification.Pass(); +} + +} // namespace + +// static +void RequestFileSystemNotification::ShowAutoGrantedNotification( + Profile* profile, + const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable) { + DCHECK(profile); + scoped_refptr + request_file_system_notification = make_scoped_refptr( + new RequestFileSystemNotification(profile, extension)); + scoped_ptr notification( + CreateAutoGrantedNotification( + extension, volume, writable, + request_file_system_notification.get() /* delegate */)); + if (notification.get()) + request_file_system_notification->Show(notification.Pass()); +} + +void RequestFileSystemNotification::SetAppImage(const std::string& id, + const gfx::ImageSkia& image) { + extension_icon_.reset(new gfx::Image(image)); + + // If there is a pending notification, then show it now. + if (pending_notification_.get()) { + pending_notification_->set_icon(*extension_icon_.get()); + g_browser_process->message_center()->AddNotification( + pending_notification_.Pass()); + } +} + +RequestFileSystemNotification::RequestFileSystemNotification( + Profile* profile, + const extensions::Extension& extension) + : icon_loader_( + new extensions::AppIconLoaderImpl(profile, kIconSize, this)) { + icon_loader_->FetchImage(extension.id()); +} + +RequestFileSystemNotification::~RequestFileSystemNotification() { +} + +void RequestFileSystemNotification::Show( + scoped_ptr notification) { + pending_notification_.reset(notification.release()); + // If the extension icon is not known yet, then defer showing the notification + // until it is (from SetAppImage). + if (!extension_icon_.get()) + return; + + pending_notification_->set_icon(*extension_icon_.get()); + g_browser_process->message_center()->AddNotification( + pending_notification_.Pass()); +} diff --git a/chrome/browser/extensions/api/file_system/request_file_system_notification.h b/chrome/browser/extensions/api/file_system/request_file_system_notification.h new file mode 100644 index 0000000000000..b53880573c92f --- /dev/null +++ b/chrome/browser/extensions/api/file_system/request_file_system_notification.h @@ -0,0 +1,65 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_H_ +#define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_H_ + +#include + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/extensions/app_icon_loader.h" +#include "ui/message_center/notification_delegate.h" + +class Profile; + +namespace extensions { +class Extension; +} // namespace extensions + +namespace file_manager { +class Volume; +} // namespace file_manager + +namespace gfx { +class Image; +class ImageSkia; +} // namespace gfx + +namespace message_center { +class Notification; +} // namespace message_center + +// Shows notifications for the chrome.fileSystem.requestFileSystem() API. +class RequestFileSystemNotification + : public message_center::NotificationDelegate, + public extensions::AppIconLoader::Delegate { + public: + // Shows a notification about automatically granted access to a file system. + static void ShowAutoGrantedNotification( + Profile* profile, + const extensions::Extension& extension, + const base::WeakPtr& volume, + bool writable); + + private: + RequestFileSystemNotification(Profile* profile, + const extensions::Extension& extension); + ~RequestFileSystemNotification() override; + + // Shows the notification. Can be called only once. + void Show(scoped_ptr notification); + + // extensions::AppIconLoader::Delegate overrides: + void SetAppImage(const std::string& id, const gfx::ImageSkia& image) override; + + scoped_ptr icon_loader_; + scoped_ptr extension_icon_; + scoped_ptr pending_notification_; + + DISALLOW_COPY_AND_ASSIGN(RequestFileSystemNotification); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_REQUEST_FILE_SYSTEM_NOTIFICATION_H_ diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index be47139207668..e9c386d56b739 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -11,6 +11,8 @@ 'browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h', 'browser/extensions/api/file_system/request_file_system_dialog_view.cc', 'browser/extensions/api/file_system/request_file_system_dialog_view.h', + 'browser/extensions/api/file_system/request_file_system_notification.cc', + 'browser/extensions/api/file_system/request_file_system_notification.h', 'browser/extensions/api/input_ime/input_ime_api.cc', 'browser/extensions/api/input_ime/input_ime_api.h', 'browser/extensions/api/log_private/filter_handler.cc',