1 // Copyright (c) 2011 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.h" 6 7 #include "base/logging.h" 8 #include "base/memory/scoped_ptr.h" 9 #include "base/stl_util-inl.h" 10 #include "chrome/browser/browser_process.h" 11 #include "chrome/browser/fullscreen.h" 12 #include "chrome/browser/idle.h" 13 #include "chrome/browser/notifications/balloon_collection.h" 14 #include "chrome/browser/notifications/notification.h" 15 #include "chrome/browser/prefs/pref_service.h" 16 #include "chrome/common/pref_names.h" 17 #include "content/browser/site_instance.h" 18 #include "content/common/notification_service.h" 19 #include "content/common/notification_type.h" 20 21 namespace { 22 const int kUserStatePollingIntervalSeconds = 1; 23 } 24 25 // A class which represents a notification waiting to be shown. 26 class QueuedNotification { 27 public: 28 QueuedNotification(const Notification& notification, Profile* profile) 29 : notification_(notification), 30 profile_(profile) { 31 } 32 33 const Notification& notification() const { return notification_; } 34 Profile* profile() const { return profile_; } 35 36 void Replace(const Notification& new_notification) { 37 notification_ = new_notification; 38 } 39 40 private: 41 // The notification to be shown. 42 Notification notification_; 43 44 // Non owned pointer to the user's profile. 45 Profile* profile_; 46 47 DISALLOW_COPY_AND_ASSIGN(QueuedNotification); 48 }; 49 50 NotificationUIManager::NotificationUIManager(PrefService* local_state) 51 : balloon_collection_(NULL), 52 is_user_active_(true) { 53 registrar_.Add(this, NotificationType::APP_TERMINATING, 54 NotificationService::AllSources()); 55 position_pref_.Init(prefs::kDesktopNotificationPosition, local_state, this); 56 #if defined(OS_MACOSX) 57 InitFullScreenMonitor(); 58 #endif 59 } 60 61 NotificationUIManager::~NotificationUIManager() { 62 STLDeleteElements(&show_queue_); 63 #if defined(OS_MACOSX) 64 StopFullScreenMonitor(); 65 #endif 66 } 67 68 // static 69 NotificationUIManager* NotificationUIManager::Create(PrefService* local_state) { 70 BalloonCollection* balloons = BalloonCollection::Create(); 71 NotificationUIManager* instance = new NotificationUIManager(local_state); 72 instance->Initialize(balloons); 73 balloons->set_space_change_listener(instance); 74 return instance; 75 } 76 77 // static 78 void NotificationUIManager::RegisterPrefs(PrefService* prefs) { 79 prefs->RegisterIntegerPref(prefs::kDesktopNotificationPosition, 80 BalloonCollection::DEFAULT_POSITION); 81 } 82 83 void NotificationUIManager::Initialize( 84 BalloonCollection* balloon_collection) { 85 DCHECK(!balloon_collection_.get()); 86 DCHECK(balloon_collection); 87 balloon_collection_.reset(balloon_collection); 88 balloon_collection_->SetPositionPreference( 89 static_cast<BalloonCollection::PositionPreference>( 90 position_pref_.GetValue())); 91 } 92 93 void NotificationUIManager::Add(const Notification& notification, 94 Profile* profile) { 95 if (TryReplacement(notification)) { 96 return; 97 } 98 99 VLOG(1) << "Added notification. URL: " 100 << notification.content_url().spec(); 101 show_queue_.push_back( 102 new QueuedNotification(notification, profile)); 103 CheckAndShowNotifications(); 104 } 105 106 bool NotificationUIManager::CancelById(const std::string& id) { 107 // See if this ID hasn't been shown yet. 108 NotificationDeque::iterator iter; 109 for (iter = show_queue_.begin(); iter != show_queue_.end(); ++iter) { 110 if ((*iter)->notification().notification_id() == id) { 111 show_queue_.erase(iter); 112 return true; 113 } 114 } 115 // If it has been shown, remove it from the balloon collections. 116 return balloon_collection_->RemoveById(id); 117 } 118 119 bool NotificationUIManager::CancelAllBySourceOrigin(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 = false; 123 NotificationDeque::iterator iter; 124 for (iter = show_queue_.begin(); iter != show_queue_.end();) { 125 if ((*iter)->notification().origin_url() == source) { 126 iter = show_queue_.erase(iter); 127 removed = true; 128 } else { 129 ++iter; 130 } 131 } 132 133 return balloon_collection_->RemoveBySourceOrigin(source) || removed; 134 } 135 136 void NotificationUIManager::CancelAll() { 137 STLDeleteElements(&show_queue_); 138 balloon_collection_->RemoveAll(); 139 } 140 141 void NotificationUIManager::CheckAndShowNotifications() { 142 CheckUserState(); 143 if (is_user_active_) 144 ShowNotifications(); 145 } 146 147 void NotificationUIManager::CheckUserState() { 148 bool is_user_active_previously = is_user_active_; 149 is_user_active_ = CalculateIdleState(0) != IDLE_STATE_LOCKED && 150 !IsFullScreenMode(); 151 if (is_user_active_ == is_user_active_previously) 152 return; 153 154 if (is_user_active_) { 155 user_state_check_timer_.Stop(); 156 // We need to show any postponed nofications when the user becomes active 157 // again. 158 ShowNotifications(); 159 } else if (!user_state_check_timer_.IsRunning()) { 160 // Start a timer to detect the moment at which the user becomes active. 161 user_state_check_timer_.Start( 162 base::TimeDelta::FromSeconds(kUserStatePollingIntervalSeconds), this, 163 &NotificationUIManager::CheckUserState); 164 } 165 } 166 167 void NotificationUIManager::ShowNotifications() { 168 while (!show_queue_.empty() && balloon_collection_->HasSpace()) { 169 scoped_ptr<QueuedNotification> queued_notification(show_queue_.front()); 170 show_queue_.pop_front(); 171 balloon_collection_->Add(queued_notification->notification(), 172 queued_notification->profile()); 173 } 174 } 175 176 void NotificationUIManager::OnBalloonSpaceChanged() { 177 CheckAndShowNotifications(); 178 } 179 180 bool NotificationUIManager::TryReplacement(const Notification& notification) { 181 const GURL& origin = notification.origin_url(); 182 const string16& replace_id = notification.replace_id(); 183 184 if (replace_id.empty()) 185 return false; 186 187 // First check the queue of pending notifications for replacement. 188 // Then check the list of notifications already being shown. 189 NotificationDeque::iterator iter; 190 for (iter = show_queue_.begin(); iter != show_queue_.end(); ++iter) { 191 if (origin == (*iter)->notification().origin_url() && 192 replace_id == (*iter)->notification().replace_id()) { 193 (*iter)->Replace(notification); 194 return true; 195 } 196 } 197 198 BalloonCollection::Balloons::iterator balloon_iter; 199 BalloonCollection::Balloons balloons = 200 balloon_collection_->GetActiveBalloons(); 201 for (balloon_iter = balloons.begin(); 202 balloon_iter != balloons.end(); 203 ++balloon_iter) { 204 if (origin == (*balloon_iter)->notification().origin_url() && 205 replace_id == (*balloon_iter)->notification().replace_id()) { 206 (*balloon_iter)->Update(notification); 207 return true; 208 } 209 } 210 211 return false; 212 } 213 214 BalloonCollection::PositionPreference 215 NotificationUIManager::GetPositionPreference() { 216 LOG(INFO) << "Current position preference: " << position_pref_.GetValue(); 217 218 return static_cast<BalloonCollection::PositionPreference>( 219 position_pref_.GetValue()); 220 } 221 222 void NotificationUIManager::SetPositionPreference( 223 BalloonCollection::PositionPreference preference) { 224 LOG(INFO) << "Setting position preference: " << preference; 225 position_pref_.SetValue(static_cast<int>(preference)); 226 balloon_collection_->SetPositionPreference(preference); 227 } 228 229 void NotificationUIManager::Observe(NotificationType type, 230 const NotificationSource& source, 231 const NotificationDetails& details) { 232 if (type == NotificationType::APP_TERMINATING) { 233 CancelAll(); 234 } else if (type == NotificationType::PREF_CHANGED) { 235 std::string* name = Details<std::string>(details).ptr(); 236 if (*name == prefs::kDesktopNotificationPosition) 237 balloon_collection_->SetPositionPreference( 238 static_cast<BalloonCollection::PositionPreference>( 239 position_pref_.GetValue())); 240 } else { 241 NOTREACHED(); 242 } 243 } 244