| // 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/bind.h" |
| #include "base/command_line.h" |
| #include "base/macros.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/lock_screen/lock_screen_controller.h" |
| #include "ui/message_center/message_center_types.h" |
| #include "ui/message_center/notification_blocker.h" |
| #include "ui/message_center/notification_list.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/notification.h" |
| #include "ui/message_center/public/cpp/notification_types.h" |
| |
| namespace message_center { |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // MessageCenterImpl |
| |
| MessageCenterImpl::MessageCenterImpl( |
| std::unique_ptr<LockScreenController> lock_screen_controller) |
| : MessageCenter(), |
| lock_screen_controller_(std::move(lock_screen_controller)), |
| popup_timers_controller_(std::make_unique<PopupTimersController>(this)), |
| stats_collector_(this) { |
| notification_list_.reset(new NotificationList(this)); |
| } |
| |
| 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_); |
| auto 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_); |
| std::list<std::string> blocked; |
| NotificationList::PopupNotifications popups = |
| notification_list_->GetPopupNotifications(blockers_, &blocked); |
| |
| visible_notifications_ = |
| notification_list_->GetVisibleNotifications(blockers_); |
| |
| for (const std::string& notification_id : 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_); |
| visible_ = (visibility == VISIBILITY_MESSAGE_CENTER); |
| |
| if (visible_) { |
| std::set<std::string> updated_ids; |
| notification_list_->SetNotificationsShown(blockers_, &updated_ids); |
| |
| for (const auto& id : updated_ids) { |
| for (auto& observer : observer_list_) |
| observer.OnNotificationUpdated(id); |
| } |
| |
| for (Notification* notification : GetPopupNotifications()) |
| MarkSinglePopupAsShown(notification->id(), false); |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnCenterVisibilityChanged(visibility); |
| } |
| |
| bool MessageCenterImpl::IsMessageCenterVisible() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return visible_; |
| } |
| |
| void MessageCenterImpl::SetHasMessageCenterView(bool has_message_center_view) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| has_message_center_view_ = has_message_center_view; |
| } |
| |
| bool MessageCenterImpl::HasMessageCenterView() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return has_message_center_view_; |
| } |
| |
| 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(); |
| } |
| |
| Notification* MessageCenterImpl::FindVisibleNotificationById( |
| const std::string& id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| const auto& notifications = GetVisibleNotifications(); |
| for (auto* notification : notifications) { |
| if (notification->id() == id) |
| return notification; |
| } |
| |
| return nullptr; |
| } |
| |
| NotificationList::Notifications MessageCenterImpl::FindNotificationsByAppId( |
| const std::string& app_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return notification_list_->GetNotificationsByAppId(app_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_, nullptr); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // 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(); |
| |
| // 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_); |
| |
| for (auto& observer : observer_list_) { |
| if (already_exists) |
| observer.OnNotificationUpdated(id); |
| else |
| 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(); |
| |
| std::string new_id = new_notification->id(); |
| notification_list_->UpdateNotificationMessage(old_id, |
| std::move(new_notification)); |
| visible_notifications_ = |
| notification_list_->GetVisibleNotifications(blockers_); |
| for (auto& observer : observer_list_) { |
| if (old_id == new_id) { |
| observer.OnNotificationUpdated(new_id); |
| } else { |
| observer.OnNotificationRemoved(old_id, false); |
| observer.OnNotificationAdded(new_id); |
| } |
| } |
| } |
| |
| void MessageCenterImpl::RemoveNotification(const std::string& id, |
| bool by_user) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| Notification* notification = FindVisibleNotificationById(id); |
| if (!notification) |
| 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_); |
| for (auto& observer : observer_list_) |
| observer.OnNotificationRemoved(copied_id, by_user); |
| } |
| |
| 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_); |
| 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_); |
| } |
| 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 (notification_list_->SetNotificationIcon(notification_id, image)) { |
| for (auto& observer : observer_list_) |
| observer.OnNotificationUpdated(notification_id); |
| } |
| } |
| |
| void MessageCenterImpl::SetNotificationImage(const std::string& notification_id, |
| const gfx::Image& image) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (notification_list_->SetNotificationImage(notification_id, image)) { |
| for (auto& observer : observer_list_) |
| observer.OnNotificationUpdated(notification_id); |
| } |
| } |
| |
| void MessageCenterImpl::ClickOnNotification(const std::string& id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (FindVisibleNotificationById(id) == NULL) |
| return; |
| |
| lock_screen_controller_->DismissLockScreenThenExecute( |
| base::BindOnce(&MessageCenterImpl::ClickOnNotificationUnlocked, |
| base::Unretained(this), id, base::nullopt, base::nullopt), |
| base::OnceClosure()); |
| } |
| |
| void MessageCenterImpl::ClickOnNotificationButton(const std::string& id, |
| int button_index) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (!FindVisibleNotificationById(id)) |
| return; |
| |
| lock_screen_controller_->DismissLockScreenThenExecute( |
| base::BindOnce(&MessageCenterImpl::ClickOnNotificationUnlocked, |
| base::Unretained(this), id, button_index, base::nullopt), |
| base::OnceClosure()); |
| } |
| |
| 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; |
| |
| lock_screen_controller_->DismissLockScreenThenExecute( |
| base::BindOnce(&MessageCenterImpl::ClickOnNotificationUnlocked, |
| base::Unretained(this), id, button_index, reply), |
| base::OnceClosure()); |
| } |
| |
| void MessageCenterImpl::ClickOnNotificationUnlocked( |
| const std::string& id, |
| const base::Optional<int>& button_index, |
| const base::Optional<base::string16>& reply) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // This method must be called under unlocked screen. |
| DCHECK(!lock_screen_controller_->IsScreenLocked()); |
| |
| // Ensure the notificaiton is still visible. |
| if (FindVisibleNotificationById(id) == NULL) |
| return; |
| |
| if (HasMessageCenterView() && HasPopupNotifications()) |
| MarkSinglePopupAsShown(id, true); |
| for (auto& observer : observer_list_) |
| observer.OnNotificationClicked(id, button_index, reply); |
| |
| scoped_refptr<NotificationDelegate> delegate = |
| notification_list_->GetNotificationDelegate(id); |
| if (delegate) |
| delegate->Click(button_index, reply); |
| } |
| |
| void MessageCenterImpl::ClickOnSettingsButton(const std::string& id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| 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(); |
| |
| for (auto& observer : observer_list_) |
| observer.OnNotificationSettingsClicked(handled_by_delegate); |
| } |
| |
| void MessageCenterImpl::DisableNotification(const std::string& id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| 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; |
| |
| if (HasMessageCenterView()) { |
| notification_list_->MarkSinglePopupAsShown(id, mark_notification_as_read); |
| for (auto& observer : observer_list_) |
| observer.OnNotificationUpdated(id); |
| } else { |
| RemoveNotification(id, false); |
| } |
| } |
| |
| 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); |
| 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 |