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