// 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();
}
}