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