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