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/notification_ui_manager_impl.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "base/memory/linked_ptr.h"
     10 #include "base/stl_util.h"
     11 #include "chrome/browser/browser_process.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/fullscreen.h"
     14 #include "chrome/browser/idle.h"
     15 #include "chrome/browser/notifications/balloon_collection.h"
     16 #include "chrome/browser/notifications/notification.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/common/extensions/extension.h"
     19 #include "chrome/common/pref_names.h"
     20 #include "content/public/browser/notification_service.h"
     21 
     22 namespace {
     23 const int kUserStatePollingIntervalSeconds = 1;
     24 }
     25 
     26 // A class which represents a notification waiting to be shown.
     27 class QueuedNotification {
     28  public:
     29   QueuedNotification(const Notification& notification, Profile* profile)
     30       : notification_(notification),
     31         profile_(profile) {
     32   }
     33 
     34   const Notification& notification() const { return notification_; }
     35   Profile* profile() const { return profile_; }
     36 
     37   void Replace(const Notification& new_notification) {
     38     notification_ = new_notification;
     39   }
     40 
     41  private:
     42   // The notification to be shown.
     43   Notification notification_;
     44 
     45   // Non owned pointer to the user's profile.
     46   Profile* profile_;
     47 
     48   DISALLOW_COPY_AND_ASSIGN(QueuedNotification);
     49 };
     50 
     51 NotificationUIManagerImpl::NotificationUIManagerImpl()
     52     : is_user_active_(true) {
     53   registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
     54                  content::NotificationService::AllSources());
     55   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
     56                  content::NotificationService::AllSources());
     57   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
     58                  content::NotificationService::AllSources());
     59 }
     60 
     61 NotificationUIManagerImpl::~NotificationUIManagerImpl() {
     62 }
     63 
     64 void NotificationUIManagerImpl::Add(const Notification& notification,
     65                                     Profile* profile) {
     66   if (Update(notification, profile)) {
     67     return;
     68   }
     69 
     70   VLOG(1) << "Added notification. URL: "
     71           << notification.content_url().spec();
     72   show_queue_.push_back(linked_ptr<QueuedNotification>(
     73       new QueuedNotification(notification, profile)));
     74   CheckAndShowNotifications();
     75 }
     76 
     77 bool NotificationUIManagerImpl::Update(const Notification& notification,
     78                                        Profile* profile) {
     79   const GURL& origin = notification.origin_url();
     80   const string16& replace_id = notification.replace_id();
     81 
     82   if (replace_id.empty())
     83     return false;
     84 
     85   // First check the queue of pending notifications for replacement.
     86   // Then check the list of notifications already being shown.
     87   for (NotificationDeque::const_iterator iter = show_queue_.begin();
     88        iter != show_queue_.end(); ++iter) {
     89     if (profile == (*iter)->profile() &&
     90         origin == (*iter)->notification().origin_url() &&
     91         replace_id == (*iter)->notification().replace_id()) {
     92       (*iter)->Replace(notification);
     93       return true;
     94     }
     95   }
     96 
     97   // Give the subclass the opportunity to update existing notification.
     98   return UpdateNotification(notification, profile);
     99 }
    100 
    101 const Notification* NotificationUIManagerImpl::FindById(
    102     const std::string& id) const {
    103   for (NotificationDeque::const_iterator iter = show_queue_.begin();
    104        iter != show_queue_.end(); ++iter) {
    105     if ((*iter)->notification().notification_id() == id) {
    106       return &((*iter)->notification());
    107     }
    108   }
    109   return NULL;
    110 }
    111 
    112 bool NotificationUIManagerImpl::CancelById(const std::string& id) {
    113   // See if this ID hasn't been shown yet.
    114   for (NotificationDeque::iterator iter = show_queue_.begin();
    115        iter != show_queue_.end(); ++iter) {
    116     if ((*iter)->notification().notification_id() == id) {
    117       show_queue_.erase(iter);
    118       return true;
    119     }
    120   }
    121   return false;
    122 }
    123 
    124 std::set<std::string>
    125 NotificationUIManagerImpl::GetAllIdsByProfileAndSourceOrigin(
    126     Profile* profile,
    127     const GURL& source) {
    128   std::set<std::string> notification_ids;
    129   for (NotificationDeque::iterator iter = show_queue_.begin();
    130        iter != show_queue_.end(); iter++) {
    131     if ((*iter)->notification().origin_url() == source &&
    132         profile->IsSameProfile((*iter)->profile())) {
    133       notification_ids.insert((*iter)->notification().notification_id());
    134     }
    135   }
    136   return notification_ids;
    137 }
    138 
    139 bool NotificationUIManagerImpl::CancelAllBySourceOrigin(const GURL& source) {
    140   // Same pattern as CancelById, but more complicated than the above
    141   // because there may be multiple notifications from the same source.
    142   bool removed = false;
    143   for (NotificationDeque::iterator loopiter = show_queue_.begin();
    144        loopiter != show_queue_.end(); ) {
    145     if ((*loopiter)->notification().origin_url() != source) {
    146       ++loopiter;
    147       continue;
    148     }
    149 
    150     loopiter = show_queue_.erase(loopiter);
    151     removed = true;
    152   }
    153   return removed;
    154 }
    155 
    156 bool NotificationUIManagerImpl::CancelAllByProfile(Profile* profile) {
    157   // Same pattern as CancelAllBySourceOrigin.
    158   bool removed = false;
    159   for (NotificationDeque::iterator loopiter = show_queue_.begin();
    160        loopiter != show_queue_.end(); ) {
    161     if ((*loopiter)->profile() != profile) {
    162       ++loopiter;
    163       continue;
    164     }
    165 
    166     loopiter = show_queue_.erase(loopiter);
    167     removed = true;
    168   }
    169   return removed;
    170 }
    171 
    172 void NotificationUIManagerImpl::CancelAll() {
    173 }
    174 
    175 void NotificationUIManagerImpl::CheckAndShowNotifications() {
    176   CheckUserState();
    177   if (is_user_active_)
    178     ShowNotifications();
    179 }
    180 
    181 void NotificationUIManagerImpl::CheckUserState() {
    182   bool is_user_active_previously = is_user_active_;
    183   is_user_active_ = !CheckIdleStateIsLocked() && !IsFullScreenMode();
    184   if (is_user_active_ == is_user_active_previously)
    185     return;
    186 
    187   if (is_user_active_) {
    188     user_state_check_timer_.Stop();
    189     // We need to show any postponed nofications when the user becomes active
    190     // again.
    191     ShowNotifications();
    192   } else if (!user_state_check_timer_.IsRunning()) {
    193     // Start a timer to detect the moment at which the user becomes active.
    194     user_state_check_timer_.Start(FROM_HERE,
    195         base::TimeDelta::FromSeconds(kUserStatePollingIntervalSeconds), this,
    196         &NotificationUIManagerImpl::CheckUserState);
    197   }
    198 }
    199 
    200 // Attempts to show each notification, leaving any failures in the queue.
    201 // TODO(dewittj): Eliminate recursion when BallonCollection is used to render
    202 // the Notification UI surfaces.
    203 void NotificationUIManagerImpl::ShowNotifications() {
    204   while (!show_queue_.empty()) {
    205     linked_ptr<QueuedNotification> queued_notification(show_queue_.front());
    206     show_queue_.pop_front();
    207     if (!ShowNotification(queued_notification->notification(),
    208                           queued_notification->profile())) {
    209       // Subclass could not show notification, put it back in the queue.
    210       show_queue_.push_front(queued_notification);
    211       return;
    212     }
    213   }
    214 }
    215 
    216 void NotificationUIManagerImpl::GetQueuedNotificationsForTesting(
    217     std::vector<const Notification*>* notifications) {
    218   for (NotificationDeque::const_iterator iter = show_queue_.begin();
    219        iter != show_queue_.end(); ++iter) {
    220     notifications->push_back(&(*iter)->notification());
    221   }
    222 }
    223 
    224 void NotificationUIManagerImpl::Observe(
    225     int type,
    226     const content::NotificationSource& source,
    227     const content::NotificationDetails& details) {
    228   if (type == chrome::NOTIFICATION_APP_TERMINATING) {
    229     CancelAll();
    230   } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
    231     if (!content::Source<Profile>(source)->IsOffTheRecord()) {
    232       extensions::UnloadedExtensionInfo* extension_info =
    233           content::Details<extensions::UnloadedExtensionInfo>(details).ptr();
    234       const extensions::Extension* extension = extension_info->extension;
    235       CancelAllBySourceOrigin(extension->url());
    236     }
    237   } else if (type == chrome::NOTIFICATION_PROFILE_DESTROYED) {
    238     // We only want to remove the incognito notifications.
    239     if (content::Source<Profile>(source)->IsOffTheRecord())
    240       CancelAllByProfile(content::Source<Profile>(source).ptr());
    241   } else {
    242     NOTREACHED();
    243   }
    244 }
    245