Home | History | Annotate | Download | only in notifications
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/notifications/message_center_notification_manager.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/prefs/pref_registry_simple.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/notifications/desktop_notification_service.h"
     13 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
     14 #include "chrome/browser/notifications/fullscreen_notification_blocker.h"
     15 #include "chrome/browser/notifications/message_center_settings_controller.h"
     16 #include "chrome/browser/notifications/notification.h"
     17 #include "chrome/browser/notifications/screen_lock_notification_blocker.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/ui/browser_finder.h"
     20 #include "chrome/browser/ui/chrome_pages.h"
     21 #include "chrome/browser/ui/host_desktop.h"
     22 #include "chrome/common/pref_names.h"
     23 #include "content/public/browser/notification_service.h"
     24 #include "content/public/browser/web_contents.h"
     25 #include "content/public/common/url_constants.h"
     26 #include "extensions/browser/extension_system.h"
     27 #include "extensions/browser/info_map.h"
     28 #include "extensions/common/extension_set.h"
     29 #include "ui/gfx/image/image_skia.h"
     30 #include "ui/message_center/message_center_style.h"
     31 #include "ui/message_center/message_center_tray.h"
     32 #include "ui/message_center/message_center_types.h"
     33 #include "ui/message_center/notifier_settings.h"
     34 
     35 #if defined(OS_CHROMEOS)
     36 #include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h"
     37 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
     38 #endif
     39 
     40 #if defined(USE_ASH)
     41 #include "ash/shell.h"
     42 #include "ash/system/web_notification/web_notification_tray.h"
     43 #endif
     44 
     45 #if defined(OS_WIN)
     46 // The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all
     47 // popups go away and the user has notifications in the message center.
     48 const int kFirstRunIdleDelaySeconds = 1;
     49 #endif
     50 
     51 MessageCenterNotificationManager::MessageCenterNotificationManager(
     52     message_center::MessageCenter* message_center,
     53     PrefService* local_state,
     54     scoped_ptr<message_center::NotifierSettingsProvider> settings_provider)
     55     : message_center_(message_center),
     56 #if defined(OS_WIN)
     57       first_run_idle_timeout_(
     58           base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)),
     59       weak_factory_(this),
     60 #endif
     61       settings_provider_(settings_provider.Pass()),
     62       system_observer_(this),
     63       stats_collector_(message_center),
     64       google_now_stats_collector_(message_center) {
     65 #if defined(OS_WIN)
     66   first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state);
     67 #endif
     68 
     69   message_center_->AddObserver(this);
     70   message_center_->SetNotifierSettingsProvider(settings_provider_.get());
     71 
     72 #if defined(OS_CHROMEOS)
     73   blockers_.push_back(
     74       new LoginStateNotificationBlockerChromeOS(message_center));
     75 #else
     76   blockers_.push_back(new ScreenLockNotificationBlocker(message_center));
     77 #endif
     78   blockers_.push_back(new FullscreenNotificationBlocker(message_center));
     79 
     80 #if defined(OS_WIN) || defined(OS_MACOSX) \
     81   || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
     82   // On Windows, Linux and Mac, the notification manager owns the tray icon and
     83   // views.Other platforms have global ownership and Create will return NULL.
     84   tray_.reset(message_center::CreateMessageCenterTray());
     85 #endif
     86   registrar_.Add(this,
     87                  chrome::NOTIFICATION_FULLSCREEN_CHANGED,
     88                  content::NotificationService::AllSources());
     89 }
     90 
     91 MessageCenterNotificationManager::~MessageCenterNotificationManager() {
     92   message_center_->SetNotifierSettingsProvider(NULL);
     93   message_center_->RemoveObserver(this);
     94 }
     95 
     96 void MessageCenterNotificationManager::RegisterPrefs(
     97     PrefRegistrySimple* registry) {
     98   registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon,
     99                                 false);
    100   registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true);
    101   registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false);
    102 }
    103 
    104 ////////////////////////////////////////////////////////////////////////////////
    105 // NotificationUIManager
    106 
    107 void MessageCenterNotificationManager::Add(const Notification& notification,
    108                                            Profile* profile) {
    109   if (Update(notification, profile))
    110     return;
    111 
    112   DesktopNotificationServiceFactory::GetForProfile(profile)->
    113       ShowWelcomeNotificationIfNecessary(notification);
    114 
    115   AddProfileNotification(
    116       new ProfileNotification(profile, notification, message_center_));
    117 }
    118 
    119 bool MessageCenterNotificationManager::Update(const Notification& notification,
    120                                               Profile* profile) {
    121   const base::string16& replace_id = notification.replace_id();
    122   if (replace_id.empty())
    123     return false;
    124 
    125   const GURL origin_url = notification.origin_url();
    126   DCHECK(origin_url.is_valid());
    127 
    128   // Since replace_id is provided by arbitrary JS, we need to use origin_url
    129   // (which is an app url in case of app/extension) to scope the replace ids
    130   // in the given profile.
    131   for (NotificationMap::iterator iter = profile_notifications_.begin();
    132        iter != profile_notifications_.end(); ++iter) {
    133     ProfileNotification* old_notification = (*iter).second;
    134     if (old_notification->notification().replace_id() == replace_id &&
    135         old_notification->notification().origin_url() == origin_url &&
    136         old_notification->profile() == profile) {
    137       // Changing the type from non-progress to progress does not count towards
    138       // the immediate update allowed in the message center.
    139       std::string old_id =
    140           old_notification->notification().delegate_id();
    141       DCHECK(message_center_->FindVisibleNotificationById(old_id));
    142 
    143       // Add/remove notification in the local list but just update the same
    144       // one in MessageCenter.
    145       delete old_notification;
    146       profile_notifications_.erase(old_id);
    147       ProfileNotification* new_notification =
    148           new ProfileNotification(profile, notification, message_center_);
    149       profile_notifications_[notification.delegate_id()] = new_notification;
    150 
    151       // Now pass a copy to message center.
    152       scoped_ptr<message_center::Notification> message_center_notification(
    153           make_scoped_ptr(new message_center::Notification(notification)));
    154       message_center_->UpdateNotification(old_id,
    155                                           message_center_notification.Pass());
    156 
    157       new_notification->StartDownloads();
    158       return true;
    159     }
    160   }
    161   return false;
    162 }
    163 
    164 const Notification* MessageCenterNotificationManager::FindById(
    165     const std::string& id) const {
    166   NotificationMap::const_iterator iter = profile_notifications_.find(id);
    167   if (iter == profile_notifications_.end())
    168     return NULL;
    169   return &(iter->second->notification());
    170 }
    171 
    172 bool MessageCenterNotificationManager::CancelById(const std::string& id) {
    173   // See if this ID hasn't been shown yet.
    174   // If it has been shown, remove it.
    175   NotificationMap::iterator iter = profile_notifications_.find(id);
    176   if (iter == profile_notifications_.end())
    177     return false;
    178 
    179   RemoveProfileNotification(iter->second);
    180   message_center_->RemoveNotification(id, /* by_user */ false);
    181   return true;
    182 }
    183 
    184 std::set<std::string>
    185 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
    186     Profile* profile,
    187     const GURL& source) {
    188 
    189   std::set<std::string> notification_ids;
    190   for (NotificationMap::iterator iter = profile_notifications_.begin();
    191        iter != profile_notifications_.end(); iter++) {
    192     if ((*iter).second->notification().origin_url() == source &&
    193         profile == (*iter).second->profile()) {
    194       notification_ids.insert(iter->first);
    195     }
    196   }
    197   return notification_ids;
    198 }
    199 
    200 bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
    201     const GURL& source) {
    202   // Same pattern as CancelById, but more complicated than the above
    203   // because there may be multiple notifications from the same source.
    204   bool removed = false;
    205 
    206   for (NotificationMap::iterator loopiter = profile_notifications_.begin();
    207        loopiter != profile_notifications_.end(); ) {
    208     NotificationMap::iterator curiter = loopiter++;
    209     if ((*curiter).second->notification().origin_url() == source) {
    210       const std::string id = curiter->first;
    211       RemoveProfileNotification(curiter->second);
    212       message_center_->RemoveNotification(id, /* by_user */ false);
    213       removed = true;
    214     }
    215   }
    216   return removed;
    217 }
    218 
    219 bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
    220   // Same pattern as CancelAllBySourceOrigin.
    221   bool removed = false;
    222 
    223   for (NotificationMap::iterator loopiter = profile_notifications_.begin();
    224        loopiter != profile_notifications_.end(); ) {
    225     NotificationMap::iterator curiter = loopiter++;
    226     if (profile == (*curiter).second->profile()) {
    227       const std::string id = curiter->first;
    228       RemoveProfileNotification(curiter->second);
    229       message_center_->RemoveNotification(id, /* by_user */ false);
    230       removed = true;
    231     }
    232   }
    233   return removed;
    234 }
    235 
    236 void MessageCenterNotificationManager::CancelAll() {
    237   message_center_->RemoveAllNotifications(/* by_user */ false);
    238 }
    239 
    240 ////////////////////////////////////////////////////////////////////////////////
    241 // MessageCenter::Observer
    242 void MessageCenterNotificationManager::OnNotificationRemoved(
    243     const std::string& notification_id,
    244     bool by_user) {
    245   NotificationMap::const_iterator iter =
    246       profile_notifications_.find(notification_id);
    247   if (iter != profile_notifications_.end())
    248     RemoveProfileNotification(iter->second);
    249 
    250 #if defined(OS_WIN)
    251   CheckFirstRunTimer();
    252 #endif
    253 }
    254 
    255 void MessageCenterNotificationManager::OnCenterVisibilityChanged(
    256     message_center::Visibility visibility) {
    257 #if defined(OS_WIN)
    258   if (visibility == message_center::VISIBILITY_TRANSIENT)
    259     CheckFirstRunTimer();
    260 #endif
    261 }
    262 
    263 void MessageCenterNotificationManager::OnNotificationUpdated(
    264     const std::string& notification_id) {
    265 #if defined(OS_WIN)
    266   CheckFirstRunTimer();
    267 #endif
    268 }
    269 
    270 void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
    271   if (tray_.get())
    272     tray_->GetMessageCenterTray()->HideMessageCenterBubble();
    273 
    274 #if defined(USE_ASH)
    275   if (ash::Shell::HasInstance()) {
    276     ash::WebNotificationTray* tray =
    277         ash::Shell::GetInstance()->GetWebNotificationTray();
    278     if (tray)
    279       tray->GetMessageCenterTray()->HideMessageCenterBubble();
    280   }
    281 #endif
    282 }
    283 
    284 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
    285     message_center::MessageCenterTrayDelegate* delegate) {
    286   tray_.reset(delegate);
    287 }
    288 
    289 void MessageCenterNotificationManager::Observe(
    290     int type,
    291     const content::NotificationSource& source,
    292     const content::NotificationDetails& details) {
    293   if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) {
    294     const bool is_fullscreen = *content::Details<bool>(details).ptr();
    295 
    296     if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray())
    297       tray_->GetMessageCenterTray()->HidePopupBubble();
    298   }
    299 }
    300 
    301 ////////////////////////////////////////////////////////////////////////////////
    302 // ImageDownloads
    303 
    304 MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
    305     message_center::MessageCenter* message_center,
    306     ImageDownloadsObserver* observer)
    307     : message_center_(message_center),
    308       pending_downloads_(0),
    309       observer_(observer) {
    310 }
    311 
    312 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
    313 
    314 void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
    315     const Notification& notification) {
    316   // In case all downloads are synchronous, assume a pending download.
    317   AddPendingDownload();
    318 
    319   // Notification primary icon.
    320   StartDownloadWithImage(
    321       notification,
    322       &notification.icon(),
    323       notification.icon_url(),
    324       base::Bind(&message_center::MessageCenter::SetNotificationIcon,
    325                  base::Unretained(message_center_),
    326                  notification.delegate_id()));
    327 
    328   // Notification image.
    329   StartDownloadWithImage(
    330       notification,
    331       NULL,
    332       notification.image_url(),
    333       base::Bind(&message_center::MessageCenter::SetNotificationImage,
    334                  base::Unretained(message_center_),
    335                  notification.delegate_id()));
    336 
    337   // Notification button icons.
    338   StartDownloadWithImage(
    339       notification,
    340       NULL,
    341       notification.button_one_icon_url(),
    342       base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
    343                  base::Unretained(message_center_),
    344                  notification.delegate_id(),
    345                  0));
    346   StartDownloadWithImage(
    347       notification,
    348       NULL,
    349       notification.button_two_icon_url(),
    350       base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
    351                  base::Unretained(message_center_),
    352                  notification.delegate_id(),
    353                  1));
    354 
    355   // This should tell the observer we're done if everything was synchronous.
    356   PendingDownloadCompleted();
    357 }
    358 
    359 void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
    360     const Notification& notification,
    361     const gfx::Image* image,
    362     const GURL& url,
    363     const SetImageCallback& callback) {
    364   // Set the image directly if we have it.
    365   if (image && !image->IsEmpty()) {
    366     callback.Run(*image);
    367     return;
    368   }
    369 
    370   // Leave the image null if there's no URL.
    371   if (url.is_empty())
    372     return;
    373 
    374   content::WebContents* contents = notification.GetWebContents();
    375   if (!contents) {
    376     LOG(WARNING) << "Notification needs an image but has no WebContents";
    377     return;
    378   }
    379 
    380   AddPendingDownload();
    381 
    382   contents->DownloadImage(
    383       url,
    384       false,  // Not a favicon
    385       0,  // No maximum size
    386       base::Bind(
    387           &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
    388           AsWeakPtr(),
    389           callback));
    390 }
    391 
    392 void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
    393     const SetImageCallback& callback,
    394     int download_id,
    395     int http_status_code,
    396     const GURL& image_url,
    397     const std::vector<SkBitmap>& bitmaps,
    398     const std::vector<gfx::Size>& original_bitmap_sizes) {
    399   PendingDownloadCompleted();
    400 
    401   if (bitmaps.empty())
    402     return;
    403   gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
    404   callback.Run(image);
    405 }
    406 
    407 // Private methods.
    408 
    409 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
    410   ++pending_downloads_;
    411 }
    412 
    413 void
    414 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
    415   DCHECK(pending_downloads_ > 0);
    416   if (--pending_downloads_ == 0 && observer_)
    417     observer_->OnDownloadsCompleted();
    418 }
    419 
    420 ////////////////////////////////////////////////////////////////////////////////
    421 // ProfileNotification
    422 
    423 MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
    424     Profile* profile,
    425     const Notification& notification,
    426     message_center::MessageCenter* message_center)
    427     : profile_(profile),
    428       notification_(notification),
    429       downloads_(new ImageDownloads(message_center, this)) {
    430   DCHECK(profile);
    431 #if defined(OS_CHROMEOS)
    432   notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile));
    433 #endif
    434 }
    435 
    436 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
    437 }
    438 
    439 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
    440   downloads_->StartDownloads(notification_);
    441 }
    442 
    443 void
    444 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
    445   notification_.DoneRendering();
    446 }
    447 
    448 std::string
    449     MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
    450   extensions::InfoMap* extension_info_map =
    451       extensions::ExtensionSystem::Get(profile())->info_map();
    452   extensions::ExtensionSet extensions;
    453   extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
    454       notification().origin_url(), notification().process_id(),
    455       extensions::APIPermission::kNotification, &extensions);
    456 
    457   DesktopNotificationService* desktop_service =
    458       DesktopNotificationServiceFactory::GetForProfile(profile());
    459   for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
    460        iter != extensions.end(); ++iter) {
    461     if (desktop_service->IsNotifierEnabled(message_center::NotifierId(
    462             message_center::NotifierId::APPLICATION, (*iter)->id()))) {
    463       return (*iter)->id();
    464     }
    465   }
    466   return std::string();
    467 }
    468 
    469 ////////////////////////////////////////////////////////////////////////////////
    470 // private
    471 
    472 void MessageCenterNotificationManager::AddProfileNotification(
    473     ProfileNotification* profile_notification) {
    474   const Notification& notification = profile_notification->notification();
    475   std::string id = notification.delegate_id();
    476   // Notification ids should be unique.
    477   DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
    478   profile_notifications_[id] = profile_notification;
    479 
    480   // Create the copy for message center, and ensure the extension ID is correct.
    481   scoped_ptr<message_center::Notification> message_center_notification(
    482       new message_center::Notification(notification));
    483   message_center_->AddNotification(message_center_notification.Pass());
    484 
    485   profile_notification->StartDownloads();
    486 }
    487 
    488 void MessageCenterNotificationManager::RemoveProfileNotification(
    489     ProfileNotification* profile_notification) {
    490   std::string id = profile_notification->notification().delegate_id();
    491   profile_notifications_.erase(id);
    492   delete profile_notification;
    493 }
    494 
    495 MessageCenterNotificationManager::ProfileNotification*
    496     MessageCenterNotificationManager::FindProfileNotification(
    497         const std::string& id) const {
    498   NotificationMap::const_iterator iter = profile_notifications_.find(id);
    499   if (iter == profile_notifications_.end())
    500     return NULL;
    501 
    502   return (*iter).second;
    503 }
    504