// Copyright (c) 2012 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/notifications/message_center_notification_manager.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" #include "base/stl_util.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/notification_provider/notification_provider_api.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/notifications/desktop_notification_service_factory.h" #include "chrome/browser/notifications/extension_welcome_notification.h" #include "chrome/browser/notifications/extension_welcome_notification_factory.h" #include "chrome/browser/notifications/fullscreen_notification_blocker.h" #include "chrome/browser/notifications/message_center_settings_controller.h" #include "chrome/browser/notifications/notification.h" #include "chrome/browser/notifications/notification_conversion_helper.h" #include "chrome/browser/notifications/screen_lock_notification_blocker.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/host_desktop.h" #include "chrome/common/extensions/api/notification_provider.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/web_contents.h" #include "content/public/common/url_constants.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/info_map.h" #include "extensions/common/extension_set.h" #include "extensions/common/permissions/permissions_data.h" #include "ui/gfx/image/image_skia.h" #include "ui/message_center/message_center_style.h" #include "ui/message_center/message_center_tray.h" #include "ui/message_center/message_center_types.h" #include "ui/message_center/notifier_settings.h" #if defined(OS_CHROMEOS) #include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h" #include "chrome/browser/ui/ash/multi_user/multi_user_util.h" #endif #if defined(USE_ASH) #include "ash/shell.h" #include "ash/system/web_notification/web_notification_tray.h" #endif #if defined(OS_WIN) // The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all // popups go away and the user has notifications in the message center. const int kFirstRunIdleDelaySeconds = 1; #endif MessageCenterNotificationManager::MessageCenterNotificationManager( message_center::MessageCenter* message_center, PrefService* local_state, scoped_ptr<message_center::NotifierSettingsProvider> settings_provider) : message_center_(message_center), #if defined(OS_WIN) first_run_idle_timeout_( base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)), weak_factory_(this), #endif settings_provider_(settings_provider.Pass()), system_observer_(this), stats_collector_(message_center), google_now_stats_collector_(message_center) { #if defined(OS_WIN) first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state); #endif message_center_->AddObserver(this); message_center_->SetNotifierSettingsProvider(settings_provider_.get()); #if defined(OS_CHROMEOS) #if !defined(USE_ATHENA) // TODO(oshima|hashimoto): Support notification on athena. crbug.com/408755. blockers_.push_back( new LoginStateNotificationBlockerChromeOS(message_center)); #endif #else blockers_.push_back(new ScreenLockNotificationBlocker(message_center)); #endif blockers_.push_back(new FullscreenNotificationBlocker(message_center)); #if defined(OS_WIN) || defined(OS_MACOSX) \ || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) // On Windows, Linux and Mac, the notification manager owns the tray icon and // views.Other platforms have global ownership and Create will return NULL. tray_.reset(message_center::CreateMessageCenterTray()); #endif registrar_.Add(this, chrome::NOTIFICATION_FULLSCREEN_CHANGED, content::NotificationService::AllSources()); } MessageCenterNotificationManager::~MessageCenterNotificationManager() { message_center_->SetNotifierSettingsProvider(NULL); message_center_->RemoveObserver(this); STLDeleteContainerPairSecondPointers(profile_notifications_.begin(), profile_notifications_.end()); profile_notifications_.clear(); } void MessageCenterNotificationManager::RegisterPrefs( PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon, false); registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true); registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false); } //////////////////////////////////////////////////////////////////////////////// // NotificationUIManager void MessageCenterNotificationManager::Add(const Notification& notification, Profile* profile) { if (Update(notification, profile)) return; ExtensionWelcomeNotificationFactory::GetForBrowserContext(profile)-> ShowWelcomeNotificationIfNecessary(notification); // WARNING: You MUST update the message center via the notification within a // ProfileNotification object or the profile ID will not be correctly set for // ChromeOS. ProfileNotification* profile_notification( new ProfileNotification(profile, notification, message_center_)); AddProfileNotification(profile_notification); // TODO(liyanhou): Change the logic to only send notifications to one party. // Currently, if there is an app with notificationProvider permission, // notifications will go to both message center and the app. // Change it to send notifications to message center only when the user chose // default message center (extension_id.empty()). // If there exist apps/extensions that have notificationProvider permission, // route notifications to one of the apps/extensions. std::string extension_id = GetExtensionTakingOverNotifications(profile); if (!extension_id.empty()) profile_notification->AddToAlternateProvider(extension_id); message_center_->AddNotification(make_scoped_ptr( new message_center::Notification(profile_notification->notification()))); profile_notification->StartDownloads(); } bool MessageCenterNotificationManager::Update(const Notification& notification, Profile* profile) { const base::string16& replace_id = notification.replace_id(); if (replace_id.empty()) return false; const GURL origin_url = notification.origin_url(); DCHECK(origin_url.is_valid()); // Since replace_id is provided by arbitrary JS, we need to use origin_url // (which is an app url in case of app/extension) to scope the replace ids // in the given profile. for (NotificationMap::iterator iter = profile_notifications_.begin(); iter != profile_notifications_.end(); ++iter) { ProfileNotification* old_notification = (*iter).second; if (old_notification->notification().replace_id() == replace_id && old_notification->notification().origin_url() == origin_url && old_notification->profile() == profile) { // Changing the type from non-progress to progress does not count towards // the immediate update allowed in the message center. std::string old_id = old_notification->notification().delegate_id(); // Add/remove notification in the local list but just update the same // one in MessageCenter. delete old_notification; profile_notifications_.erase(old_id); ProfileNotification* new_notification = new ProfileNotification(profile, notification, message_center_); profile_notifications_[notification.delegate_id()] = new_notification; // TODO(liyanhou): Add routing updated notifications to alternative // providers. // WARNING: You MUST use AddProfileNotification or update the message // center via the notification within a ProfileNotification object or the // profile ID will not be correctly set for ChromeOS. message_center_->UpdateNotification( old_id, make_scoped_ptr(new message_center::Notification( new_notification->notification()))); new_notification->StartDownloads(); return true; } } return false; } const Notification* MessageCenterNotificationManager::FindById( const std::string& id) const { NotificationMap::const_iterator iter = profile_notifications_.find(id); if (iter == profile_notifications_.end()) return NULL; return &(iter->second->notification()); } bool MessageCenterNotificationManager::CancelById(const std::string& id) { // See if this ID hasn't been shown yet. // If it has been shown, remove it. NotificationMap::iterator iter = profile_notifications_.find(id); if (iter == profile_notifications_.end()) return false; RemoveProfileNotification(iter->second); message_center_->RemoveNotification(id, /* by_user */ false); return true; } std::set<std::string> MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin( Profile* profile, const GURL& source) { std::set<std::string> notification_ids; for (NotificationMap::iterator iter = profile_notifications_.begin(); iter != profile_notifications_.end(); iter++) { if ((*iter).second->notification().origin_url() == source && profile == (*iter).second->profile()) { notification_ids.insert(iter->first); } } return notification_ids; } bool MessageCenterNotificationManager::CancelAllBySourceOrigin( const GURL& source) { // Same pattern as CancelById, but more complicated than the above // because there may be multiple notifications from the same source. bool removed = false; for (NotificationMap::iterator loopiter = profile_notifications_.begin(); loopiter != profile_notifications_.end(); ) { NotificationMap::iterator curiter = loopiter++; if ((*curiter).second->notification().origin_url() == source) { const std::string id = curiter->first; RemoveProfileNotification(curiter->second); message_center_->RemoveNotification(id, /* by_user */ false); removed = true; } } return removed; } bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) { // Same pattern as CancelAllBySourceOrigin. bool removed = false; for (NotificationMap::iterator loopiter = profile_notifications_.begin(); loopiter != profile_notifications_.end(); ) { NotificationMap::iterator curiter = loopiter++; if (profile == (*curiter).second->profile()) { const std::string id = curiter->first; RemoveProfileNotification(curiter->second); message_center_->RemoveNotification(id, /* by_user */ false); removed = true; } } return removed; } void MessageCenterNotificationManager::CancelAll() { message_center_->RemoveAllNotifications(/* by_user */ false); } //////////////////////////////////////////////////////////////////////////////// // MessageCenter::Observer void MessageCenterNotificationManager::OnNotificationRemoved( const std::string& notification_id, bool by_user) { NotificationMap::const_iterator iter = profile_notifications_.find(notification_id); if (iter != profile_notifications_.end()) RemoveProfileNotification(iter->second); #if defined(OS_WIN) CheckFirstRunTimer(); #endif } void MessageCenterNotificationManager::OnCenterVisibilityChanged( message_center::Visibility visibility) { #if defined(OS_WIN) if (visibility == message_center::VISIBILITY_TRANSIENT) CheckFirstRunTimer(); #endif } void MessageCenterNotificationManager::OnNotificationUpdated( const std::string& notification_id) { #if defined(OS_WIN) CheckFirstRunTimer(); #endif } void MessageCenterNotificationManager::EnsureMessageCenterClosed() { if (tray_.get()) tray_->GetMessageCenterTray()->HideMessageCenterBubble(); #if defined(USE_ASH) if (ash::Shell::HasInstance()) { ash::WebNotificationTray* tray = ash::Shell::GetInstance()->GetWebNotificationTray(); if (tray) tray->GetMessageCenterTray()->HideMessageCenterBubble(); } #endif } void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest( message_center::MessageCenterTrayDelegate* delegate) { tray_.reset(delegate); } void MessageCenterNotificationManager::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) { const bool is_fullscreen = *content::Details<bool>(details).ptr(); if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray()) tray_->GetMessageCenterTray()->HidePopupBubble(); } } //////////////////////////////////////////////////////////////////////////////// // ImageDownloads MessageCenterNotificationManager::ImageDownloads::ImageDownloads( message_center::MessageCenter* message_center, ImageDownloadsObserver* observer) : message_center_(message_center), pending_downloads_(0), observer_(observer) { } MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { } void MessageCenterNotificationManager::ImageDownloads::StartDownloads( const Notification& notification) { // In case all downloads are synchronous, assume a pending download. AddPendingDownload(); // Notification primary icon. StartDownloadWithImage( notification, ¬ification.icon(), notification.icon_url(), base::Bind(&message_center::MessageCenter::SetNotificationIcon, base::Unretained(message_center_), notification.delegate_id())); // Notification image. StartDownloadWithImage( notification, NULL, notification.image_url(), base::Bind(&message_center::MessageCenter::SetNotificationImage, base::Unretained(message_center_), notification.delegate_id())); // Notification button icons. StartDownloadWithImage( notification, NULL, notification.button_one_icon_url(), base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, base::Unretained(message_center_), notification.delegate_id(), 0)); StartDownloadWithImage( notification, NULL, notification.button_two_icon_url(), base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, base::Unretained(message_center_), notification.delegate_id(), 1)); // This should tell the observer we're done if everything was synchronous. PendingDownloadCompleted(); } void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage( const Notification& notification, const gfx::Image* image, const GURL& url, const SetImageCallback& callback) { // Set the image directly if we have it. if (image && !image->IsEmpty()) { callback.Run(*image); return; } // Leave the image null if there's no URL. if (url.is_empty()) return; content::WebContents* contents = notification.delegate()->GetWebContents(); if (!contents) { LOG(WARNING) << "Notification needs an image but has no WebContents"; return; } AddPendingDownload(); contents->DownloadImage( url, false, // Not a favicon 0, // No maximum size base::Bind( &MessageCenterNotificationManager::ImageDownloads::DownloadComplete, AsWeakPtr(), callback)); } void MessageCenterNotificationManager::ImageDownloads::DownloadComplete( const SetImageCallback& callback, int download_id, int http_status_code, const GURL& image_url, const std::vector<SkBitmap>& bitmaps, const std::vector<gfx::Size>& original_bitmap_sizes) { PendingDownloadCompleted(); if (bitmaps.empty()) return; gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]); callback.Run(image); } // Private methods. void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() { ++pending_downloads_; } void MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() { DCHECK(pending_downloads_ > 0); if (--pending_downloads_ == 0 && observer_) observer_->OnDownloadsCompleted(); } //////////////////////////////////////////////////////////////////////////////// // ProfileNotification MessageCenterNotificationManager::ProfileNotification::ProfileNotification( Profile* profile, const Notification& notification, message_center::MessageCenter* message_center) : profile_(profile), notification_(notification), downloads_(new ImageDownloads(message_center, this)) { DCHECK(profile); #if defined(OS_CHROMEOS) notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile)); #endif } MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() { } void MessageCenterNotificationManager::ProfileNotification::StartDownloads() { downloads_->StartDownloads(notification_); } void MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() { notification_.delegate()->ReleaseRenderViewHost(); } void MessageCenterNotificationManager::ProfileNotification::AddToAlternateProvider( const std::string extension_id) { // Convert data from Notification type to NotificationOptions type. extensions::api::notifications::NotificationOptions options; NotificationConversionHelper::NotificationToNotificationOptions(notification_, &options); // Send the notification to the alternate provider extension/app. extensions::NotificationProviderEventRouter event_router(profile_); event_router.CreateNotification(extension_id, notification_.notifier_id().id, notification_.delegate_id(), options); } //////////////////////////////////////////////////////////////////////////////// // private void MessageCenterNotificationManager::AddProfileNotification( ProfileNotification* profile_notification) { std::string id = profile_notification->notification().delegate_id(); // Notification ids should be unique. DCHECK(profile_notifications_.find(id) == profile_notifications_.end()); profile_notifications_[id] = profile_notification; } void MessageCenterNotificationManager::RemoveProfileNotification( ProfileNotification* profile_notification) { std::string id = profile_notification->notification().delegate_id(); profile_notifications_.erase(id); delete profile_notification; } MessageCenterNotificationManager::ProfileNotification* MessageCenterNotificationManager::FindProfileNotification( const std::string& id) const { NotificationMap::const_iterator iter = profile_notifications_.find(id); if (iter == profile_notifications_.end()) return NULL; return (*iter).second; } std::string MessageCenterNotificationManager::GetExtensionTakingOverNotifications( Profile* profile) { // TODO(liyanhou): When additional settings in Chrome Settings is implemented, // change choosing the last app with permission to a user selected app. extensions::ExtensionRegistry* registry = extensions::ExtensionRegistry::Get(profile); DCHECK(registry); std::string extension_id; for (extensions::ExtensionSet::const_iterator it = registry->enabled_extensions().begin(); it != registry->enabled_extensions().end(); ++it) { if ((*it->get()).permissions_data()->HasAPIPermission( extensions::APIPermission::ID::kNotificationProvider)) { extension_id = (*it->get()).id(); return extension_id; } } return extension_id; }