// 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_service.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/notifications/desktop_notification_service_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/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/extension_set.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/info_map.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" #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 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) { #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) blockers_.push_back( new LoginStateNotificationBlockerChromeOS(message_center)); #else blockers_.push_back(new ScreenLockNotificationBlocker(message_center)); #endif blockers_.push_back(new FullscreenNotificationBlocker(message_center)); #if defined(OS_WIN) || defined(OS_MACOSX) \ || (defined(USE_AURA) && !defined(USE_ASH)) // 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_->RemoveObserver(this); } //////////////////////////////////////////////////////////////////////////////// // NotificationUIManager void MessageCenterNotificationManager::Add(const Notification& notification, Profile* profile) { if (Update(notification, profile)) return; DesktopNotificationServiceFactory::GetForProfile(profile)-> ShowWelcomeNotificationIfNecessary(notification); AddProfileNotification( new ProfileNotification(profile, notification, message_center_)); } bool MessageCenterNotificationManager::Update(const Notification& notification, Profile* profile) { const 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().notification_id(); DCHECK(message_center_->HasNotification(old_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.notification_id()] = new_notification; // Now pass a copy to message center. scoped_ptr message_center_notification( make_scoped_ptr(new message_center::Notification(notification))); message_center_->UpdateNotification(old_id, message_center_notification.Pass()); 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 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin( Profile* profile, const GURL& source) { std::set 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::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(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.notification_id())); // Notification image. StartDownloadWithImage( notification, NULL, notification.image_url(), base::Bind(&message_center::MessageCenter::SetNotificationImage, base::Unretained(message_center_), notification.notification_id())); // Notification button icons. StartDownloadWithImage( notification, NULL, notification.button_one_icon_url(), base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, base::Unretained(message_center_), notification.notification_id(), 0)); StartDownloadWithImage( notification, NULL, notification.button_two_icon_url(), base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, base::Unretained(message_center_), notification.notification_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::RenderViewHost* host = notification.GetRenderViewHost(); if (!host) { LOG(WARNING) << "Notification needs an image but has no RenderViewHost"; return; } content::WebContents* contents = content::WebContents::FromRenderViewHost(host); 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& bitmaps, const std::vector& 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); } MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() { } void MessageCenterNotificationManager::ProfileNotification::StartDownloads() { downloads_->StartDownloads(notification_); } void MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() { notification_.DoneRendering(); } std::string MessageCenterNotificationManager::ProfileNotification::GetExtensionId() { extensions::InfoMap* extension_info_map = extensions::ExtensionSystem::Get(profile())->info_map(); ExtensionSet extensions; extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin( notification().origin_url(), notification().process_id(), extensions::APIPermission::kNotification, &extensions); DesktopNotificationService* desktop_service = DesktopNotificationServiceFactory::GetForProfile(profile()); for (ExtensionSet::const_iterator iter = extensions.begin(); iter != extensions.end(); ++iter) { if (desktop_service->IsNotifierEnabled(message_center::NotifierId( message_center::NotifierId::APPLICATION, (*iter)->id()))) { return (*iter)->id(); } } return std::string(); } //////////////////////////////////////////////////////////////////////////////// // private void MessageCenterNotificationManager::AddProfileNotification( ProfileNotification* profile_notification) { const Notification& notification = profile_notification->notification(); std::string id = notification.notification_id(); // Notification ids should be unique. DCHECK(profile_notifications_.find(id) == profile_notifications_.end()); profile_notifications_[id] = profile_notification; // Create the copy for message center, and ensure the extension ID is correct. scoped_ptr message_center_notification( new message_center::Notification(notification)); message_center_->AddNotification(message_center_notification.Pass()); profile_notification->StartDownloads(); } void MessageCenterNotificationManager::RemoveProfileNotification( ProfileNotification* profile_notification) { std::string id = profile_notification->notification().notification_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; }