blob: 98421d1b5349feba8a5e7dff139b6bdf64cb1243 [file] [log] [blame]
// Copyright (c) 2013 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 "ui/message_center/message_center_impl.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/observer_list.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "ui/message_center/message_center_types.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_blocker.h"
#include "ui/message_center/notification_list.h"
#include "ui/message_center/notification_types.h"
#include "ui/message_center/popup_timers_controller.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/public/cpp/message_center_switches.h"
namespace message_center {
namespace internal {
////////////////////////////////////////////////////////////////////////////////
// ScopedNotificationsIterationLock
class ScopedNotificationsIterationLock {
public:
// Lock may be used recursively.
explicit ScopedNotificationsIterationLock(MessageCenterImpl* message_center)
: message_center_(message_center),
original_value_(message_center->iterating_) {
message_center_->iterating_ = true;
}
~ScopedNotificationsIterationLock() {
DCHECK(message_center_->iterating_);
// |original_value_| must be false. But handle the other case just in case.
message_center_->iterating_ = original_value_;
if (!original_value_) {
message_center_->notification_change_queue_->ApplyChanges(
message_center_);
}
}
private:
MessageCenterImpl* message_center_;
const bool original_value_;
DISALLOW_COPY_AND_ASSIGN(ScopedNotificationsIterationLock);
};
} // namespace internal
////////////////////////////////////////////////////////////////////////////////
// MessageCenterImpl
MessageCenterImpl::MessageCenterImpl()
: MessageCenter(),
popup_timers_controller_(new PopupTimersController(this)) {
notification_list_.reset(new NotificationList(this));
notification_change_queue_.reset(new ChangeQueue());
}
MessageCenterImpl::~MessageCenterImpl() {
}
void MessageCenterImpl::AddObserver(MessageCenterObserver* observer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
observer_list_.AddObserver(observer);
}
void MessageCenterImpl::RemoveObserver(MessageCenterObserver* observer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
observer_list_.RemoveObserver(observer);
}
void MessageCenterImpl::AddNotificationBlocker(NotificationBlocker* blocker) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (base::ContainsValue(blockers_, blocker))
return;
blocker->AddObserver(this);
blockers_.push_back(blocker);
}
void MessageCenterImpl::RemoveNotificationBlocker(
NotificationBlocker* blocker) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
std::vector<NotificationBlocker*>::iterator iter =
std::find(blockers_.begin(), blockers_.end(), blocker);
if (iter == blockers_.end())
return;
blocker->RemoveObserver(this);
blockers_.erase(iter);
}
void MessageCenterImpl::OnBlockingStateChanged(NotificationBlocker* blocker) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
std::list<const Notification*> blocked;
NotificationList::PopupNotifications popups =
notification_list_->GetPopupNotifications(blockers_, &blocked);
// Close already displayed pop-ups that are blocked now.
for (const auto* notification : blocked) {
// Do not call MessageCenterImpl::MarkSinglePopupAsShown() directly here
// just for performance reason. MessageCenterImpl::MarkSinglePopupAsShown()
// calls NotificationList::MarkSinglePopupAsShown(), but the whole cache
// will be recreated below.
if (notification->IsRead())
notification_list_->MarkSinglePopupAsShown(notification->id(), true);
}
visible_notifications_ =
notification_list_->GetVisibleNotifications(blockers_);
{
internal::ScopedNotificationsIterationLock lock(this);
for (const auto* notification : blocked) {
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification->id());
}
}
for (auto& observer : observer_list_)
observer.OnBlockingStateChanged(blocker);
}
void MessageCenterImpl::SetVisibility(Visibility visibility) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
visible_ = (visibility == VISIBILITY_MESSAGE_CENTER);
if (visible_) {
std::set<std::string> updated_ids;
notification_list_->SetNotificationsShown(blockers_, &updated_ids);
{
internal::ScopedNotificationsIterationLock lock(this);
for (const auto& id : updated_ids) {
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id);
}
}
}
for (auto& observer : observer_list_)
observer.OnCenterVisibilityChanged(visibility);
}
bool MessageCenterImpl::IsMessageCenterVisible() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return visible_;
}
size_t MessageCenterImpl::NotificationCount() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return visible_notifications_.size();
}
bool MessageCenterImpl::HasPopupNotifications() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return !IsMessageCenterVisible() &&
notification_list_->HasPopupNotifications(blockers_);
}
bool MessageCenterImpl::IsQuietMode() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return notification_list_->quiet_mode();
}
message_center::Notification* MessageCenterImpl::FindVisibleNotificationById(
const std::string& id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return notification_list_->GetNotificationById(id);
}
const NotificationList::Notifications&
MessageCenterImpl::GetVisibleNotifications() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return visible_notifications_;
}
NotificationList::PopupNotifications
MessageCenterImpl::GetPopupNotifications() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return notification_list_->GetPopupNotifications(blockers_, NULL);
}
//------------------------------------------------------------------------------
// Client code interface.
void MessageCenterImpl::AddNotification(
std::unique_ptr<Notification> notification) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(notification);
const std::string id = notification->id();
for (size_t i = 0; i < blockers_.size(); ++i)
blockers_[i]->CheckState();
if (iterating_) {
notification_change_queue_->AddNotification(std::move(notification));
return;
}
AddNotificationImmediately(std::move(notification));
}
void MessageCenterImpl::AddNotificationImmediately(
std::unique_ptr<Notification> notification) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
const std::string id = notification->id();
// Sometimes the notification can be added with the same id and the
// |notification_list| will replace the notification instead of adding new.
// This is essentially an update rather than addition.
bool already_exists = (notification_list_->GetNotificationById(id) != NULL);
notification_list_->AddNotification(std::move(notification));
visible_notifications_ =
notification_list_->GetVisibleNotifications(blockers_);
if (already_exists) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id);
} else {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationAdded(id);
}
}
void MessageCenterImpl::UpdateNotification(
const std::string& old_id,
std::unique_ptr<Notification> new_notification) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
for (size_t i = 0; i < blockers_.size(); ++i)
blockers_[i]->CheckState();
if (iterating_) {
notification_change_queue_->UpdateNotification(old_id,
std::move(new_notification));
return;
}
UpdateNotificationImmediately(old_id, std::move(new_notification));
}
void MessageCenterImpl::UpdateNotificationImmediately(
const std::string& old_id,
std::unique_ptr<Notification> new_notification) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
std::string new_id = new_notification->id();
notification_list_->UpdateNotificationMessage(old_id,
std::move(new_notification));
visible_notifications_ =
notification_list_->GetVisibleNotifications(blockers_);
if (old_id == new_id) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(new_id);
} else {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationRemoved(old_id, false);
for (auto& observer : observer_list_)
observer.OnNotificationAdded(new_id);
}
}
void MessageCenterImpl::RemoveNotification(const std::string& id,
bool by_user) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (iterating_) {
notification_change_queue_->RemoveNotification(id, by_user);
return;
}
RemoveNotificationImmediately(id, by_user);
}
void MessageCenterImpl::RemoveNotificationImmediately(
const std::string& id, bool by_user) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
Notification* notification = FindVisibleNotificationById(id);
if (notification == NULL)
return;
if (by_user && notification->pinned()) {
// When pinned, a popup will not be removed completely but moved into the
// message center bubble.
MarkSinglePopupAsShown(id, true);
return;
}
// In many cases |id| is a reference to an existing notification instance
// but the instance can be destructed in this method. Hence copies the id
// explicitly here.
std::string copied_id(id);
scoped_refptr<NotificationDelegate> delegate =
notification_list_->GetNotificationDelegate(copied_id);
if (delegate.get())
delegate->Close(by_user);
notification_list_->RemoveNotification(copied_id);
visible_notifications_ =
notification_list_->GetVisibleNotifications(blockers_);
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationRemoved(copied_id, by_user);
}
}
Notification* MessageCenterImpl::GetLatestNotificationIncludingQueued(
const std::string& id) const {
Notification* queued_notification =
notification_change_queue_->GetLatestNotification(id);
if (queued_notification) {
DCHECK(iterating_);
return queued_notification;
}
Notification* notification = notification_list_->GetNotificationById(id);
if (notification)
return notification;
return nullptr;
}
void MessageCenterImpl::RemoveNotificationsForNotifierId(
const NotifierId& notifier_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
NotificationList::Notifications notifications =
notification_list_->GetNotificationsByNotifierId(notifier_id);
for (auto* notification : notifications)
RemoveNotification(notification->id(), false);
if (!notifications.empty()) {
visible_notifications_ =
notification_list_->GetVisibleNotifications(blockers_);
}
}
void MessageCenterImpl::RemoveAllNotifications(bool by_user, RemoveType type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
bool remove_pinned = (type == RemoveType::ALL);
const NotificationBlockers& blockers =
remove_pinned ? NotificationBlockers() /* empty blockers */
: blockers_; /* use default blockers */
const NotificationList::Notifications notifications =
notification_list_->GetVisibleNotifications(blockers);
std::set<std::string> ids;
for (auto* notification : notifications) {
if (!remove_pinned && notification->pinned())
continue;
ids.insert(notification->id());
scoped_refptr<NotificationDelegate> delegate = notification->delegate();
if (delegate.get())
delegate->Close(by_user);
notification_list_->RemoveNotification(notification->id());
}
if (!ids.empty()) {
visible_notifications_ =
notification_list_->GetVisibleNotifications(blockers_);
}
{
internal::ScopedNotificationsIterationLock lock(this);
for (const auto& id : ids) {
for (auto& observer : observer_list_)
observer.OnNotificationRemoved(id, by_user);
}
}
}
void MessageCenterImpl::SetNotificationIcon(const std::string& notification_id,
const gfx::Image& image) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (iterating_) {
Notification* notification =
GetLatestNotificationIncludingQueued(notification_id);
if (notification) {
std::unique_ptr<Notification> copied_notification =
std::make_unique<Notification>(*notification);
copied_notification->set_icon(image);
notification_change_queue_->UpdateNotification(
notification_id, std::move(copied_notification));
}
return;
}
if (notification_list_->SetNotificationIcon(notification_id, image)) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification_id);
}
}
void MessageCenterImpl::SetNotificationImage(const std::string& notification_id,
const gfx::Image& image) {
if (iterating_) {
Notification* notification =
GetLatestNotificationIncludingQueued(notification_id);
if (notification) {
std::unique_ptr<Notification> copied_notification =
std::make_unique<Notification>(*notification);
copied_notification->set_image(image);
notification_change_queue_->UpdateNotification(
notification_id, std::move(copied_notification));
}
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (notification_list_->SetNotificationImage(notification_id, image)) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification_id);
}
}
void MessageCenterImpl::SetNotificationButtonIcon(
const std::string& notification_id, int button_index,
const gfx::Image& image) {
if (iterating_) {
Notification* notification =
GetLatestNotificationIncludingQueued(notification_id);
if (notification) {
std::unique_ptr<Notification> copied_notification =
std::make_unique<Notification>(*notification);
copied_notification->SetButtonIcon(button_index, image);
notification_change_queue_->UpdateNotification(
notification_id, std::move(copied_notification));
}
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (notification_list_->SetNotificationButtonIcon(notification_id,
button_index, image)) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification_id);
}
}
void MessageCenterImpl::ClickOnNotification(const std::string& id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
if (FindVisibleNotificationById(id) == NULL)
return;
#if defined(OS_CHROMEOS)
if (HasPopupNotifications())
MarkSinglePopupAsShown(id, true);
#endif
scoped_refptr<NotificationDelegate> delegate =
notification_list_->GetNotificationDelegate(id);
if (delegate.get())
delegate->Click();
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationClicked(id);
}
}
void MessageCenterImpl::ClickOnNotificationButton(const std::string& id,
int button_index) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
if (!FindVisibleNotificationById(id))
return;
#if defined(OS_CHROMEOS)
if (HasPopupNotifications())
MarkSinglePopupAsShown(id, true);
#endif
scoped_refptr<NotificationDelegate> delegate =
notification_list_->GetNotificationDelegate(id);
if (delegate.get())
delegate->ButtonClick(button_index);
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationButtonClicked(id, button_index);
}
}
void MessageCenterImpl::ClickOnNotificationButtonWithReply(
const std::string& id,
int button_index,
const base::string16& reply) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!FindVisibleNotificationById(id))
return;
#if defined(OS_CHROMEOS)
if (HasPopupNotifications())
MarkSinglePopupAsShown(id, true);
#endif
scoped_refptr<NotificationDelegate> delegate =
notification_list_->GetNotificationDelegate(id);
if (delegate.get())
delegate->ButtonClickWithReply(button_index, reply);
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationButtonClickedWithReply(id, button_index, reply);
}
}
void MessageCenterImpl::ClickOnSettingsButton(const std::string& id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
Notification* notification = notification_list_->GetNotificationById(id);
bool handled_by_delegate =
notification &&
(notification->rich_notification_data().settings_button_handler ==
SettingsButtonHandler::DELEGATE);
if (handled_by_delegate)
notification->delegate()->SettingsClick();
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationSettingsClicked(handled_by_delegate);
}
}
void MessageCenterImpl::DisableNotification(const std::string& id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
Notification* notification = notification_list_->GetNotificationById(id);
if (notification && notification->delegate()) {
notification->delegate()->DisableNotification();
RemoveNotificationsForNotifierId(notification->notifier_id());
}
}
void MessageCenterImpl::MarkSinglePopupAsShown(const std::string& id,
bool mark_notification_as_read) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (FindVisibleNotificationById(id) == NULL)
return;
// This method doesn't check the |iterating_| flag, since this is called
// during iteration.
// TODO(yoshiki): Investigate and not to call this method during iteration if
// necessary.
#if !defined(OS_CHROMEOS)
RemoveNotification(id, false);
#else
notification_list_->MarkSinglePopupAsShown(id, mark_notification_as_read);
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id);
}
#endif // defined(OS_CHROMEOS)
}
void MessageCenterImpl::DisplayedNotification(
const std::string& id,
const DisplaySource source) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// This method may be called from the handlers, so we shouldn't manipulate
// notifications in this method.
if (FindVisibleNotificationById(id) == NULL)
return;
if (HasPopupNotifications())
notification_list_->MarkSinglePopupAsDisplayed(id);
scoped_refptr<NotificationDelegate> delegate =
notification_list_->GetNotificationDelegate(id);
{
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationDisplayed(id, source);
}
}
void MessageCenterImpl::SetQuietMode(bool in_quiet_mode) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (in_quiet_mode != notification_list_->quiet_mode()) {
notification_list_->SetQuietMode(in_quiet_mode);
for (auto& observer : observer_list_)
observer.OnQuietModeChanged(in_quiet_mode);
}
quiet_mode_timer_.reset();
}
void MessageCenterImpl::EnterQuietModeWithExpire(
const base::TimeDelta& expires_in) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (quiet_mode_timer_) {
// Note that the capital Reset() is the method to restart the timer, not
// scoped_ptr::reset().
quiet_mode_timer_->Reset();
} else {
notification_list_->SetQuietMode(true);
for (auto& observer : observer_list_)
observer.OnQuietModeChanged(true);
quiet_mode_timer_.reset(new base::OneShotTimer);
quiet_mode_timer_->Start(
FROM_HERE,
expires_in,
base::Bind(
&MessageCenterImpl::SetQuietMode, base::Unretained(this), false));
}
}
void MessageCenterImpl::RestartPopupTimers() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (popup_timers_controller_)
popup_timers_controller_->StartAll();
}
void MessageCenterImpl::PausePopupTimers() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (popup_timers_controller_)
popup_timers_controller_->PauseAll();
}
const base::string16& MessageCenterImpl::GetSystemNotificationAppName() const {
return system_notification_app_name_;
}
void MessageCenterImpl::SetSystemNotificationAppName(
const base::string16& name) {
system_notification_app_name_ = name;
}
void MessageCenterImpl::DisableTimersForTest() {
popup_timers_controller_.reset();
}
} // namespace message_center