910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// 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 <optional>
#include <set>
#include <string>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "base/time/time.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"
#include "ui/message_center/public/cpp/notifier_id.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "base/metrics/histogram_functions.h"
#endif  //  BUILDFLAG(IS_CHROMEOS)

namespace message_center {
namespace {

bool IsNotificationsGroupingEnabled() {
#if BUILDFLAG(IS_CHROMEOS)
  return true;
#else
  return false;
#endif  // BUILDFLAG(IS_CHROMEOS)
}

#if BUILDFLAG(IS_CHROMEOS)

ScopedNotificationLimitOverrider* g_limit_overrider_instance_ = nullptr;

// Constants -------------------------------------------------------------------

// Indicates the notification count limit.
// NOTE: Used only when the notification limit feature is enabled.
constexpr int kChromeOSNotificationLimit = 75;

// Target notification count for the cleaning task triggered when the
// notification count exceeds `kChromeOSNotificationLimit`. This value is
// lower than `kChromeOSNotificationLimit` to reduce the frequency of hitting
// the limit. Because of unremovable notifications, the actual count after
// cleaning could exceed this target count.
// NOTE: Used only when the notification limit feature is enabled.
constexpr int kNotificationTargetCountAfterRemoval = 65;

// Helpers ---------------------------------------------------------------------

int GetNotificationLimit() {
  return g_limit_overrider_instance_
             ? g_limit_overrider_instance_->overriding_limit
             : kChromeOSNotificationLimit;
}

int GetTargetCountAfterRemoval() {
  return g_limit_overrider_instance_
             ? g_limit_overrider_instance_->overriding_target_count
             : kNotificationTargetCountAfterRemoval;
}

#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// MessageCenterImpl

MessageCenterImpl::MessageCenterImpl(
    std::unique_ptr<LockScreenController> lock_screen_controller)
    : lock_screen_controller_(std::move(lock_screen_controller)),
      popup_timers_controller_(std::make_unique<PopupTimersController>(this)),
      notifications_grouping_enabled_(IsNotificationsGroupingEnabled()),
      stats_collector_(this) {
  notification_list_ = std::make_unique<NotificationList>(this);
}

MessageCenterImpl::~MessageCenterImpl() = default;

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::Contains(blockers_, blocker)) {
    return;
  }

  blocker->AddObserver(this);
  blockers_.push_back(blocker);
  OnBlockingStateChanged(blocker);
}

void MessageCenterImpl::RemoveNotificationBlocker(
    NotificationBlocker* blocker) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  auto iter = std::ranges::find(blockers_, blocker);
  if (iter == blockers_.end()) {
    return;
  }
  blocker->RemoveObserver(this);
  blockers_.erase(iter);
  OnBlockingStateChanged(blocker);
}

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) {
    observer_list_.Notify(&MessageCenterObserver::OnNotificationUpdated,
                          notification_id);
  }
  observer_list_.Notify(&MessageCenterObserver::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) {
      observer_list_.Notify(&MessageCenterObserver::OnNotificationUpdated, id);
    }

    for (Notification* notification : GetPopupNotifications()) {
      MarkSinglePopupAsShown(notification->id(), false);
    }
  }

  observer_list_.Notify(&MessageCenterObserver::OnCenterVisibilityChanged,
                        visibility);
}

bool MessageCenterImpl::IsMessageCenterVisible() const {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return visible_;
}

ExpandState MessageCenterImpl::GetNotificationExpandState(
    const std::string& id) {
  return notification_list_->GetNotificationExpandState(id);
}

void MessageCenterImpl::SetNotificationExpandState(
    const std::string& id,
    const ExpandState expand_state) {
  DCHECK(FindVisibleNotificationById(id));

  notification_list_->SetNotificationExpandState(id, expand_state);
}

void MessageCenterImpl::OnSetExpanded(const std::string& id, bool expanded) {
  scoped_refptr<NotificationDelegate> delegate =
      notification_list_->GetNotificationDelegate(id);

  if (delegate) {
    delegate->ExpandStateChanged(expanded);
  }
}

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

bool MessageCenterImpl::IsSpokenFeedbackEnabled() const {
  return spoken_feedback_enabled_;
}

Notification* MessageCenterImpl::FindNotificationById(const std::string& id) {
  return notification_list_->GetNotificationById(id);
}

Notification* MessageCenterImpl::FindParentNotification(
    Notification* notification) {
  // For a notification to have a parent notification, they must have identical
  // notifier_ids. To make sure that the notifications come from
  // the same website for the same user. Also make sure to only group
  // notifications from web pages with valid origin urls. For system
  // notifications, currently we only group privacy indicators notification.
  // For ARC notifications, only group them when the flag
  // IsRenderArcNotificationsByChromeEnabled() is enabled.
  bool is_privacy_indicators_notification = false;
  bool render_arc_notifications_by_chrome = false;
#if BUILDFLAG(IS_CHROMEOS)
  is_privacy_indicators_notification =
      notification->notifier_id().id == ash::kPrivacyIndicatorsNotifierId;
  render_arc_notifications_by_chrome =
      ash::features::IsRenderArcNotificationsByChromeEnabled();
#endif  // BUILDFLAG(IS_CHROMEOS)

  if (!is_privacy_indicators_notification &&
      (notification->origin_url().is_empty() ||
       notification->notifier_id().type != NotifierType::WEB_PAGE) &&
      notification->notifier_id().type != NotifierType::ARC_APPLICATION) {
    return nullptr;
  }

  NotificationList::Notifications notifications =
      notification_list_->GetNotificationsByNotifierId(
          notification->notifier_id());

  // Handle ARC notification grouping in Chrome
  if (notification->notifier_id().type == NotifierType::ARC_APPLICATION) {
    // If render_arc_notifications_by_chrome flag is not enabled,
    // use Android grouping and do not apply grouping rules from the chrome
    // side.
    if (!render_arc_notifications_by_chrome) {
      return nullptr;
    }

    // To stay consistent with Android, ARC notifications with group key
    // are grouped using notifier_id() where id and group keys are checked.
    // For ARC notifications without a group key,
    // only group them when there are more than 4 notifications
    if (!notification->notifier_id().group_key.has_value()) {
      if (notifications.size() < 4) {
        return nullptr;
      }
      for (Notification* n : notifications) {
        if (n->group_parent() || n->group_child()) {
          continue;
        }
        n->SetGroupChild();
      }
    }
  }

  auto parent_notification_it = std::ranges::find_if(
      notifications,
      [](Notification* notification) { return notification->group_parent(); });

  // If there's already a notification assigned to be the group parent,
  // returns that notification immediately.
  if (parent_notification_it != notifications.cend()) {
    return *parent_notification_it;
  }

  // Otherwise, the parent notification should be the oldest one. Since
  // `notifications` keeps notifications ordered with the most recent one in
  // the front, the oldest one should be the last in the list.
  return notifications.size() ? *notifications.rbegin() : nullptr;
}

Notification* MessageCenterImpl::FindPopupNotificationById(
    const std::string& id) {
  auto notifications = GetPopupNotifications();
  auto notification = std::ranges::find(notifications, id, &Notification::id);

  return notification == notifications.end() ? nullptr : *notification;
}

Notification* MessageCenterImpl::FindVisibleNotificationById(
    const std::string& id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  const auto& notifications = GetVisibleNotifications();
  for (Notification* 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);
}

NotificationList::Notifications MessageCenterImpl::GetNotifications() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return notification_list_->GetNotifications();
}

const NotificationList::Notifications&
MessageCenterImpl::GetVisibleNotifications() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return visible_notifications_;
}

NotificationList::Notifications
MessageCenterImpl::GetVisibleNotificationsWithoutBlocker(
    const NotificationBlocker* blocker) const {
  return notification_list_->GetVisibleNotificationsWithoutBlocker(blockers_,
                                                                   blocker);
}

NotificationList::PopupNotifications
MessageCenterImpl::GetPopupNotifications() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return notification_list_->GetPopupNotifications(blockers_, nullptr);
}

NotificationList::PopupNotifications
MessageCenterImpl::GetPopupNotificationsWithoutBlocker(
    const NotificationBlocker& blocker) const {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return notification_list_->GetPopupNotificationsWithoutBlocker(blockers_,
                                                                 blocker);
}

//------------------------------------------------------------------------------
// Client code interface.
void MessageCenterImpl::AddNotification(
    std::unique_ptr<Notification> notification) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(notification);

  notification->set_allow_group(notifications_grouping_enabled_);

  const std::string id = notification->id();
  for (NotificationBlocker* blocker : blockers_) {
    blocker->CheckState();
  }

  // Sometimes the notifications 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.
  if (notification_list_->GetNotificationById(id)) {
    UpdateNotification(id, std::move(notification));
    return;
  }

  if (auto* const parent = FindParentNotification(notification.get());
      notification->allow_group() && parent && !notification->group_parent()) {
    parent->SetGroupParent();
    notification->SetGroupChild();
  }

  notification_list_->AddNotification(std::move(notification));

  visible_notifications_ =
      notification_list_->GetVisibleNotifications(blockers_);
  observer_list_.Notify(&MessageCenterObserver::OnNotificationAdded, id);

#if BUILDFLAG(IS_CHROMEOS)
  ScheduleCleaningTaskIfCountOverLimit();
#endif  // BUILDFLAG(IS_CHROMEOS)
}

void MessageCenterImpl::UpdateNotification(
    const std::string& old_id,
    std::unique_ptr<Notification> new_notification) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  for (NotificationBlocker* blocker : blockers_) {
    blocker->CheckState();
  }

  auto* old_notification = notification_list_->GetNotificationById(old_id);
  if (old_notification &&
      old_notification->notifier_id() == new_notification->notifier_id()) {
    // Copy grouping metadata to the new notification.
    if (old_notification->group_parent()) {
      new_notification->SetGroupParent();
    }
    if (old_notification->group_child()) {
      new_notification->SetGroupChild();
    }
  }

  std::string new_id = new_notification->id();
  notification_list_->UpdateNotificationMessage(old_id,
                                                std::move(new_notification));
  visible_notifications_ =
      notification_list_->GetVisibleNotifications(blockers_);
  for (MessageCenterObserver& 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 = notification_list_->GetNotificationById(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);

  // Remove notification before calling the Close method in case it calls
  // RemoveNotification reentrantly.
  notification_list_->RemoveNotification(copied_id);

  if (delegate.get()) {
    delegate->Close(by_user);
  }

  visible_notifications_ =
      notification_list_->GetVisibleNotifications(blockers_);
  for (MessageCenterObserver& 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 (Notification* 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 (Notification* notification : notifications) {
    if (!remove_pinned && notification->pinned()) {
      continue;
    }

    ids.insert(notification->id());
    scoped_refptr<NotificationDelegate> delegate = notification->delegate();

    // Remove notification before calling the Close method in case it calls
    // RemoveNotification reentrantly.
    notification_list_->RemoveNotification(notification->id());

    if (delegate.get()) {
      delegate->Close(by_user);
    }
  }

  if (!ids.empty()) {
    visible_notifications_ =
        notification_list_->GetVisibleNotifications(blockers_);
  }
  for (const auto& id : ids) {
    for (MessageCenterObserver& observer : observer_list_) {
      observer.OnNotificationRemoved(id, by_user);
    }
  }
}

void MessageCenterImpl::SetNotificationIcon(const std::string& notification_id,
                                            const ui::ImageModel& image) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (notification_list_->SetNotificationIcon(notification_id, image)) {
    for (MessageCenterObserver& 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 (MessageCenterObserver& observer : observer_list_) {
      observer.OnNotificationUpdated(notification_id);
    }
  }
}

void MessageCenterImpl::ClickOnNotification(const std::string& id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (!FindVisibleNotificationById(id)) {
    return;
  }

  if (lock_screen_controller_->IsNotificationAllowedOnLockScreen(id)) {
    ClickOnNotificationUnlocked(id, std::nullopt, std::nullopt);
    return;
  }

  lock_screen_controller_->DismissLockScreenThenExecute(
      base::BindOnce(&MessageCenterImpl::ClickOnNotificationUnlocked,
                     base::Unretained(this), id, std::nullopt, std::nullopt),
      base::OnceClosure());
}

void MessageCenterImpl::ClickOnNotificationButton(const std::string& id,
                                                  int button_index) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (!FindVisibleNotificationById(id)) {
    return;
  }

  if (lock_screen_controller_->IsNotificationAllowedOnLockScreen(id)) {
    ClickOnNotificationUnlocked(id, button_index, std::nullopt);
    return;
  }

  lock_screen_controller_->DismissLockScreenThenExecute(
      base::BindOnce(&MessageCenterImpl::ClickOnNotificationUnlocked,
                     base::Unretained(this), id, button_index, std::nullopt),
      base::OnceClosure());
}

void MessageCenterImpl::ClickOnNotificationButtonWithReply(
    const std::string& id,
    int button_index,
    std::u16string_view reply) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (!FindVisibleNotificationById(id)) {
    return;
  }

  if (lock_screen_controller_->IsNotificationAllowedOnLockScreen(id)) {
    ClickOnNotificationUnlocked(id, button_index, std::u16string(reply));
    return;
  }

  lock_screen_controller_->DismissLockScreenThenExecute(
      base::BindOnce(&MessageCenterImpl::ClickOnNotificationUnlocked,
                     base::Unretained(this), id, button_index,
                     std::u16string(reply)),
      base::OnceClosure());
}

void MessageCenterImpl::ClickOnNotificationUnlocked(
    const std::string& id,
    const std::optional<int>& button_index,
    const std::optional<std::u16string>& reply) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // This method must be called under unlocked screen or if the notification is
  // allowed on the lock screen.
  DCHECK(lock_screen_controller_->IsNotificationAllowedOnLockScreen(id) ||
         !lock_screen_controller_->IsScreenLocked());

  // Ensure the notification is still visible.
  if (!FindVisibleNotificationById(id)) {
    return;
  }

  if (HasMessageCenterView() && HasPopupNotifications()) {
    MarkSinglePopupAsShown(id, true);
  }
  for (MessageCenterObserver& observer : observer_list_) {
    observer.OnNotificationClicked(id, button_index, reply);
  }

  scoped_refptr<NotificationDelegate> delegate =
      notification_list_->GetNotificationDelegate(id);
  if (delegate) {
    delegate->Click(button_index, reply);
  }

  if (const Notification* notification =
          notification_list_->GetNotificationById(id);
      notification && notification->rich_notification_data().remove_on_click) {
    RemoveNotification(id, /*by_user=*/true);
  }
}

#if BUILDFLAG(IS_CHROMEOS)
void MessageCenterImpl::ScheduleCleaningTaskIfCountOverLimit() {
  if (!ash::features::IsNotificationLimitEnabled() ||
      notification_list_->size() <= GetNotificationLimit()) {
    return;
  }

  if (!overlimit_handler_timer_.IsRunning()) {
    overlimit_handler_timer_.Start(
        FROM_HERE, base::TimeDelta(), /*receiver=*/this,
        &MessageCenterImpl::RemoveNotificationsIfOverLimit);
  }
}

void MessageCenterImpl::RemoveNotificationsIfOverLimit() {
  CHECK(ash::features::IsNotificationLimitEnabled());

  if (int notification_count = notification_list_->size();
      notification_count > GetNotificationLimit()) {
    for (const std::string& id :
         notification_list_->GetTopKRemovableNotificationIds(
             notification_count - GetTargetCountAfterRemoval())) {
      RemoveNotification(id, /*by_user=*/false);
    }

    base::UmaHistogramBoolean("Ash.Notification.RemovedByLimitEnforcement",
                              true);
  }
}

#endif  // BUILDFLAG(IS_CHROMEOS)

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->delegate() &&
      (notification->rich_notification_data().settings_button_handler ==
       SettingsButtonHandler::DELEGATE);
  if (handled_by_delegate) {
    notification->delegate()->SettingsClick();
  }

  for (MessageCenterObserver& observer : observer_list_) {
    observer.OnNotificationSettingsClicked(handled_by_delegate);
  }
}

void MessageCenterImpl::ClickOnSnoozeButton(const std::string& id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  Notification* notification = notification_list_->GetNotificationById(id);

  bool handled_by_delegate =
      notification && notification_list_->GetNotificationDelegate(id);
  if (handled_by_delegate) {
    notification->delegate()->SnoozeButtonClicked();
  }
}

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 (!FindNotificationById(id)) {
    return;
  }

  if (HasMessageCenterView()) {
    notification_list_->MarkSinglePopupAsShown(id, mark_notification_as_read);
    for (MessageCenterObserver& observer : observer_list_) {
      observer.OnNotificationUpdated(id);
      observer.OnNotificationPopupShown(id, mark_notification_as_read);
    }
  } else {
    RemoveNotification(id, false);
  }
}

void MessageCenterImpl::ResetPopupTimer(const std::string& id) {
  DCHECK(FindPopupNotificationById(id));

  popup_timers_controller_->CancelTimer(id);
  popup_timers_controller_->StartTimer(
      id, popup_timers_controller_->GetTimeoutForNotification(
              FindPopupNotificationById(id)));
}

void MessageCenterImpl::ResetSinglePopup(const std::string& id) {
  notification_list_->ResetSinglePopup(id);
  for (MessageCenterObserver& observer : observer_list_) {
    observer.OnNotificationUpdated(id);
  }
}

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)) {
    return;
  }

  if (HasPopupNotifications()) {
    notification_list_->MarkSinglePopupAsDisplayed(id);
  }
  scoped_refptr<NotificationDelegate> delegate =
      notification_list_->GetNotificationDelegate(id);
  for (MessageCenterObserver& observer : observer_list_) {
    observer.OnNotificationDisplayed(id, source);
  }
}

void MessageCenterImpl::SetQuietMode(bool in_quiet_mode,
                                     QuietModeSourceType type) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (in_quiet_mode != notification_list_->quiet_mode()) {
    last_quiet_mode_change_source_type_ = type;
    notification_list_->SetQuietMode(in_quiet_mode);
    for (MessageCenterObserver& observer : observer_list_) {
      observer.OnQuietModeChanged(in_quiet_mode);
    }
  }
  quiet_mode_timer_.Stop();
}

QuietModeSourceType MessageCenterImpl::GetLastQuietModeChangeSourceType()
    const {
  return last_quiet_mode_change_source_type_;
}

void MessageCenterImpl::SetSpokenFeedbackEnabled(bool enabled) {
  spoken_feedback_enabled_ = enabled;
}

void MessageCenterImpl::EnterQuietModeWithExpire(
    const base::TimeDelta& expires_in) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (!quiet_mode_timer_.IsRunning()) {
    notification_list_->SetQuietMode(true);
    for (MessageCenterObserver& observer : observer_list_) {
      observer.OnQuietModeChanged(true);
    }
  }

  // This will restart the timer if it is already running.
  quiet_mode_timer_.Start(
      FROM_HERE, expires_in,
      base::BindOnce(&MessageCenterImpl::SetQuietMode, base::Unretained(this),
                     false, QuietModeSourceType::kUserAction));
}

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 std::u16string& MessageCenterImpl::GetSystemNotificationAppName() const {
  return system_notification_app_name_;
}

void MessageCenterImpl::SetSystemNotificationAppName(
    const std::u16string& name) {
  system_notification_app_name_ = name;
}

void MessageCenterImpl::OnMessageViewHovered(
    const std::string& notification_id) {
  for (MessageCenterObserver& observer : observer_list_) {
    observer.OnMessageViewHovered(notification_id);
  }
}

void MessageCenterImpl::DisableTimersForTest() {
  popup_timers_controller_.reset();
}

// ScopedNotificationLimitOverrider --------------------------------------------

#if BUILDFLAG(IS_CHROMEOS)
ScopedNotificationLimitOverrider::ScopedNotificationLimitOverrider(
    size_t limit,
    size_t target_count)
    : overriding_limit(limit), overriding_target_count(target_count) {
  CHECK(!g_limit_overrider_instance_);
  g_limit_overrider_instance_ = this;
}

ScopedNotificationLimitOverrider::~ScopedNotificationLimitOverrider() {
  CHECK(g_limit_overrider_instance_);
  g_limit_overrider_instance_ = nullptr;
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace message_center