| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/notifications/notification_ui_manager.h" |
| |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/stl_util-inl.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/fullscreen.h" |
| #include "chrome/browser/idle.h" |
| #include "chrome/browser/notifications/balloon_collection.h" |
| #include "chrome/browser/notifications/notification.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/browser/site_instance.h" |
| #include "content/common/notification_service.h" |
| #include "content/common/notification_type.h" |
| |
| namespace { |
| const int kUserStatePollingIntervalSeconds = 1; |
| } |
| |
| // A class which represents a notification waiting to be shown. |
| class QueuedNotification { |
| public: |
| QueuedNotification(const Notification& notification, Profile* profile) |
| : notification_(notification), |
| profile_(profile) { |
| } |
| |
| const Notification& notification() const { return notification_; } |
| Profile* profile() const { return profile_; } |
| |
| void Replace(const Notification& new_notification) { |
| notification_ = new_notification; |
| } |
| |
| private: |
| // The notification to be shown. |
| Notification notification_; |
| |
| // Non owned pointer to the user's profile. |
| Profile* profile_; |
| |
| DISALLOW_COPY_AND_ASSIGN(QueuedNotification); |
| }; |
| |
| NotificationUIManager::NotificationUIManager(PrefService* local_state) |
| : balloon_collection_(NULL), |
| is_user_active_(true) { |
| registrar_.Add(this, NotificationType::APP_TERMINATING, |
| NotificationService::AllSources()); |
| position_pref_.Init(prefs::kDesktopNotificationPosition, local_state, this); |
| #if defined(OS_MACOSX) |
| InitFullScreenMonitor(); |
| #endif |
| } |
| |
| NotificationUIManager::~NotificationUIManager() { |
| STLDeleteElements(&show_queue_); |
| #if defined(OS_MACOSX) |
| StopFullScreenMonitor(); |
| #endif |
| } |
| |
| // static |
| NotificationUIManager* NotificationUIManager::Create(PrefService* local_state) { |
| BalloonCollection* balloons = BalloonCollection::Create(); |
| NotificationUIManager* instance = new NotificationUIManager(local_state); |
| instance->Initialize(balloons); |
| balloons->set_space_change_listener(instance); |
| return instance; |
| } |
| |
| // static |
| void NotificationUIManager::RegisterPrefs(PrefService* prefs) { |
| prefs->RegisterIntegerPref(prefs::kDesktopNotificationPosition, |
| BalloonCollection::DEFAULT_POSITION); |
| } |
| |
| void NotificationUIManager::Initialize( |
| BalloonCollection* balloon_collection) { |
| DCHECK(!balloon_collection_.get()); |
| DCHECK(balloon_collection); |
| balloon_collection_.reset(balloon_collection); |
| balloon_collection_->SetPositionPreference( |
| static_cast<BalloonCollection::PositionPreference>( |
| position_pref_.GetValue())); |
| } |
| |
| void NotificationUIManager::Add(const Notification& notification, |
| Profile* profile) { |
| if (TryReplacement(notification)) { |
| return; |
| } |
| |
| VLOG(1) << "Added notification. URL: " |
| << notification.content_url().spec(); |
| show_queue_.push_back( |
| new QueuedNotification(notification, profile)); |
| CheckAndShowNotifications(); |
| } |
| |
| bool NotificationUIManager::CancelById(const std::string& id) { |
| // See if this ID hasn't been shown yet. |
| NotificationDeque::iterator iter; |
| for (iter = show_queue_.begin(); iter != show_queue_.end(); ++iter) { |
| if ((*iter)->notification().notification_id() == id) { |
| show_queue_.erase(iter); |
| return true; |
| } |
| } |
| // If it has been shown, remove it from the balloon collections. |
| return balloon_collection_->RemoveById(id); |
| } |
| |
| bool NotificationUIManager::CancelAllBySourceOrigin(const GURL& source) { |
| // Same pattern as CancelById, but more complicated than the above |
| // because there may be multiple notifications from the same source. |
| bool removed = false; |
| NotificationDeque::iterator iter; |
| for (iter = show_queue_.begin(); iter != show_queue_.end();) { |
| if ((*iter)->notification().origin_url() == source) { |
| iter = show_queue_.erase(iter); |
| removed = true; |
| } else { |
| ++iter; |
| } |
| } |
| |
| return balloon_collection_->RemoveBySourceOrigin(source) || removed; |
| } |
| |
| void NotificationUIManager::CancelAll() { |
| STLDeleteElements(&show_queue_); |
| balloon_collection_->RemoveAll(); |
| } |
| |
| void NotificationUIManager::CheckAndShowNotifications() { |
| CheckUserState(); |
| if (is_user_active_) |
| ShowNotifications(); |
| } |
| |
| void NotificationUIManager::CheckUserState() { |
| bool is_user_active_previously = is_user_active_; |
| is_user_active_ = CalculateIdleState(0) != IDLE_STATE_LOCKED && |
| !IsFullScreenMode(); |
| if (is_user_active_ == is_user_active_previously) |
| return; |
| |
| if (is_user_active_) { |
| user_state_check_timer_.Stop(); |
| // We need to show any postponed nofications when the user becomes active |
| // again. |
| ShowNotifications(); |
| } else if (!user_state_check_timer_.IsRunning()) { |
| // Start a timer to detect the moment at which the user becomes active. |
| user_state_check_timer_.Start( |
| base::TimeDelta::FromSeconds(kUserStatePollingIntervalSeconds), this, |
| &NotificationUIManager::CheckUserState); |
| } |
| } |
| |
| void NotificationUIManager::ShowNotifications() { |
| while (!show_queue_.empty() && balloon_collection_->HasSpace()) { |
| scoped_ptr<QueuedNotification> queued_notification(show_queue_.front()); |
| show_queue_.pop_front(); |
| balloon_collection_->Add(queued_notification->notification(), |
| queued_notification->profile()); |
| } |
| } |
| |
| void NotificationUIManager::OnBalloonSpaceChanged() { |
| CheckAndShowNotifications(); |
| } |
| |
| bool NotificationUIManager::TryReplacement(const Notification& notification) { |
| const GURL& origin = notification.origin_url(); |
| const string16& replace_id = notification.replace_id(); |
| |
| if (replace_id.empty()) |
| return false; |
| |
| // First check the queue of pending notifications for replacement. |
| // Then check the list of notifications already being shown. |
| NotificationDeque::iterator iter; |
| for (iter = show_queue_.begin(); iter != show_queue_.end(); ++iter) { |
| if (origin == (*iter)->notification().origin_url() && |
| replace_id == (*iter)->notification().replace_id()) { |
| (*iter)->Replace(notification); |
| return true; |
| } |
| } |
| |
| BalloonCollection::Balloons::iterator balloon_iter; |
| BalloonCollection::Balloons balloons = |
| balloon_collection_->GetActiveBalloons(); |
| for (balloon_iter = balloons.begin(); |
| balloon_iter != balloons.end(); |
| ++balloon_iter) { |
| if (origin == (*balloon_iter)->notification().origin_url() && |
| replace_id == (*balloon_iter)->notification().replace_id()) { |
| (*balloon_iter)->Update(notification); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| BalloonCollection::PositionPreference |
| NotificationUIManager::GetPositionPreference() { |
| LOG(INFO) << "Current position preference: " << position_pref_.GetValue(); |
| |
| return static_cast<BalloonCollection::PositionPreference>( |
| position_pref_.GetValue()); |
| } |
| |
| void NotificationUIManager::SetPositionPreference( |
| BalloonCollection::PositionPreference preference) { |
| LOG(INFO) << "Setting position preference: " << preference; |
| position_pref_.SetValue(static_cast<int>(preference)); |
| balloon_collection_->SetPositionPreference(preference); |
| } |
| |
| void NotificationUIManager::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| if (type == NotificationType::APP_TERMINATING) { |
| CancelAll(); |
| } else if (type == NotificationType::PREF_CHANGED) { |
| std::string* name = Details<std::string>(details).ptr(); |
| if (*name == prefs::kDesktopNotificationPosition) |
| balloon_collection_->SetPositionPreference( |
| static_cast<BalloonCollection::PositionPreference>( |
| position_pref_.GetValue())); |
| } else { |
| NOTREACHED(); |
| } |
| } |