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