123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- // Copyright 2021 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 "ash/system/message_center/notification_grouping_controller.h"
- #include "ash/system/message_center/ash_message_popup_collection.h"
- #include "ash/system/message_center/ash_notification_view.h"
- #include "ash/system/message_center/message_center_utils.h"
- #include "ash/system/message_center/metrics_utils.h"
- #include "ash/system/message_center/unified_message_center_bubble.h"
- #include "ash/system/message_center/unified_message_center_view.h"
- #include "ash/system/message_center/unified_message_list_view.h"
- #include "ash/system/unified/unified_system_tray.h"
- #include "ui/display/display.h"
- #include "ui/display/screen.h"
- #include "ui/message_center/message_center_types.h"
- #include "ui/message_center/notification_view_controller.h"
- #include "ui/message_center/public/cpp/message_center_constants.h"
- #include "ui/message_center/public/cpp/notification.h"
- #include "ui/message_center/views/message_view.h"
- using message_center::MessageCenter;
- using message_center::MessageView;
- using message_center::Notification;
- namespace ash {
- namespace {
- class GroupedNotificationList {
- public:
- GroupedNotificationList() = default;
- GroupedNotificationList(const GroupedNotificationList& other) = delete;
- GroupedNotificationList& operator=(const GroupedNotificationList& other) =
- delete;
- ~GroupedNotificationList() = default;
- void AddGroupedNotification(const std::string& notification_id,
- const std::string& parent_id) {
- if (notifications_in_parent_map_.find(parent_id) ==
- notifications_in_parent_map_.end()) {
- notifications_in_parent_map_[parent_id] = {};
- }
- child_parent_map_[notification_id] = parent_id;
- if (notification_id != parent_id)
- notifications_in_parent_map_[parent_id].insert(notification_id);
- }
- // Remove a single child notification from a grouped notification.
- void RemoveGroupedChildNotification(const std::string& notification_id) {
- DCHECK(base::Contains(child_parent_map_, notification_id));
- const std::string& parent_id = child_parent_map_[notification_id];
- notifications_in_parent_map_[parent_id].erase(notification_id);
- child_parent_map_.erase(notification_id);
- }
- // Clear the entire grouped notification with `parent_id`
- void ClearGroupedNotification(const std::string& parent_id) {
- notifications_in_parent_map_.erase(parent_id);
- std::vector<std::string> to_be_deleted;
- for (const auto& it : child_parent_map_) {
- if (it.second == parent_id)
- to_be_deleted.push_back(it.first);
- }
- for (const auto& child : to_be_deleted)
- child_parent_map_.erase(child);
- }
- const std::string& GetParentForChild(const std::string& child_id) {
- return child_parent_map_[child_id];
- }
- std::set<std::string>& GetGroupedNotificationsForParent(
- const std::string& parent_id) {
- return notifications_in_parent_map_[parent_id];
- }
- bool GroupedChildNotificationExists(const std::string& child_id) {
- return child_parent_map_.find(child_id) != child_parent_map_.end();
- }
- bool ParentNotificationExists(const std::string& parent_id) {
- return notifications_in_parent_map_.find(parent_id) !=
- notifications_in_parent_map_.end();
- }
- // Replaces all instances of `old_parent_id` with `new_parent_id` in
- // the `child_parent_map_`.
- void ReplaceParentId(const std::string& new_parent_id,
- const std::string& old_parent_id) {
- // Remove entry with `new_parent_id` as a child id and replace with
- // `old_parent_id` as a child id.
- child_parent_map_.erase(new_parent_id);
- child_parent_map_[old_parent_id] = new_parent_id;
- // Replace all occurrences of `old_parent_id` with `new_parent_id`.
- std::vector<std::string> to_be_updated;
- for (const auto& child : child_parent_map_) {
- if (child.second == old_parent_id)
- to_be_updated.push_back(child.first);
- }
- for (const auto& id : to_be_updated) {
- child_parent_map_.erase(child_parent_map_.find(id));
- child_parent_map_[id] = new_parent_id;
- }
- }
- private:
- // Map for looking up the parent `notification_id` for any given notification
- // id.
- std::map<std::string, std::string> child_parent_map_;
- // Map containing a list of child notification ids per each group parent id.
- // Used to keep track of grouped notifications which already have a parent
- // notification view.
- std::map<std::string, std::set<std::string>> notifications_in_parent_map_;
- };
- // Needs to be a static instance because we need a single instance to be shared
- // across multiple instances of `NotificationGroupingController`. When there are
- // multiple screens, each screen has it's own `MessagePopupCollection`,
- // `UnifiedSystemTray`, `NotificationGroupingController` etc.
- GroupedNotificationList& GetGroupedNotificationListInstance() {
- static base::NoDestructor<GroupedNotificationList> instance;
- return *instance;
- }
- } // namespace
- NotificationGroupingController::NotificationGroupingController(
- UnifiedSystemTray* tray)
- : tray_(tray),
- grouped_notification_list_(&GetGroupedNotificationListInstance()) {
- observer_.Observe(MessageCenter::Get());
- }
- NotificationGroupingController::~NotificationGroupingController() = default;
- void NotificationGroupingController::PopulateGroupParent(
- const std::string& notification_id) {
- DCHECK(MessageCenter::Get()
- ->FindNotificationById(notification_id)
- ->group_parent());
- MessageView* parent_view =
- GetActiveNotificationViewController()
- ? GetActiveNotificationViewController()
- ->GetMessageViewForNotificationId(notification_id)
- : nullptr;
- // TODO(crbug/1277765) Need this check to fix crbug/1275765. However, this
- // should not be necessary if the message center bubble is initialized
- // properly. Need to monitor for empty group notifications if this check is
- // hit and fix the root cause.
- if (!parent_view) {
- LOG(WARNING) << "MessageView does not exist for notification: "
- << notification_id;
- return;
- }
- if (!parent_view->IsManuallyExpandedOrCollapsed())
- parent_view->SetExpanded(false);
- std::vector<const Notification*> notifications;
- for (auto* notification : MessageCenter::Get()->GetNotifications()) {
- if (notification->notifier_id() == parent_view->notifier_id() &&
- notification->id() != parent_view->notification_id()) {
- notification->SetGroupChild();
- grouped_notification_list_->AddGroupedNotification(notification->id(),
- notification_id);
- notifications.push_back(notification);
- }
- }
- parent_view->PopulateGroupNotifications(notifications);
- }
- const std::string& NotificationGroupingController::GetParentIdForChildForTest(
- const std::string& notification_id) const {
- return grouped_notification_list_->GetParentForChild(notification_id);
- }
- void NotificationGroupingController::
- ConvertFromSingleToGroupNotificationAfterAnimation(
- const std::string& notification_id,
- std::string& parent_id,
- Notification* parent_notification) {
- auto* message_center = MessageCenter::Get();
- auto* notification_view_controller = GetActiveNotificationViewController();
- if (!notification_view_controller)
- return;
- Notification* notification =
- message_center->FindNotificationById(notification_id);
- if (!notification)
- return;
- parent_id = SetupParentNotification(parent_notification, parent_id);
- grouped_notification_list_->AddGroupedNotification(notification_id,
- parent_id);
- MessageView* parent_view =
- notification_view_controller->GetMessageViewForNotificationId(parent_id);
- if (parent_view)
- parent_view->AddGroupNotification(*notification);
- else
- message_center->ResetSinglePopup(parent_id);
- metrics_utils::LogCountOfNotificationsInOneGroup(
- grouped_notification_list_->GetGroupedNotificationsForParent(parent_id)
- .size());
- metrics_utils::LogGroupNotificationAddedType(
- metrics_utils::GroupNotificationType::GROUP_CHILD);
- }
- const std::string& NotificationGroupingController::SetupParentNotification(
- Notification* parent_notification,
- const std::string& parent_id) {
- std::unique_ptr<Notification> notification_copy =
- CreateCopyForParentNotification(*parent_notification);
- std::string new_parent_id = notification_copy->id();
- std::string old_parent_id = parent_id;
- grouped_notification_list_->AddGroupedNotification(old_parent_id,
- new_parent_id);
- // Since MessagePopupCollection uses the global MessageCenter to update its
- // animations, the update to MessageCenter in primary display will interfere
- // the animation in the secondary display. Thus, calling this functions on all
- // display is needed.
- for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) {
- auto* controller =
- message_center_utils::GetActiveNotificationViewControllerForDisplay(
- display.id());
- if (!controller)
- continue;
- controller->ConvertNotificationViewToGroupedNotificationView(
- /*ungrouped_notification_id=*/old_parent_id,
- /*new_grouped_notification_id=*/new_parent_id);
- }
- grouped_notification_list_->ReplaceParentId(
- /*new_parent_id=*/new_parent_id,
- /*old_parent_id=*/old_parent_id);
- // Add the old parent notification as a group child to the
- // newly created parent notification which will act as a
- // container for this group as long as it exists.
- notification_copy->SetGroupParent();
- parent_notification->SetGroupChild();
- Notification* new_parent_notification = notification_copy.get();
- {
- // Prevent double processing `parent_notification`'s copy, used as a group
- // parent.
- base::AutoReset<bool> reset(&adding_parent_grouped_notification_, true);
- MessageCenter::Get()->AddNotification(std::move(notification_copy));
- }
- // Record metrics for the new parent and new child added.
- metrics_utils::LogGroupNotificationAddedType(
- metrics_utils::GroupNotificationType::GROUP_PARENT);
- metrics_utils::LogGroupNotificationAddedType(
- metrics_utils::GroupNotificationType::GROUP_CHILD);
- auto* parent_view = GetActiveNotificationViewController()
- ? GetActiveNotificationViewController()
- ->GetMessageViewForNotificationId(new_parent_id)
- : nullptr;
- if (parent_view) {
- parent_view->UpdateWithNotification(*new_parent_notification);
- // Grouped notifications should start off in the collapsed state.
- parent_view->SetExpanded(false);
- parent_view->AddGroupNotification(*parent_notification);
- }
- return new_parent_notification->id();
- }
- std::unique_ptr<Notification>
- NotificationGroupingController::CreateCopyForParentNotification(
- const Notification& parent_notification) {
- // Create a copy with a timestamp that is older than the copied notification.
- // We need to set an older timestamp so that this notification will become
- // the parent notification for it's notifier_id.
- auto copy = std::make_unique<Notification>(
- message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
- parent_notification.id() +
- message_center::kIdSuffixForGroupContainerNotification,
- parent_notification.title(), parent_notification.message(),
- ui::ImageModel(), std::u16string(), parent_notification.origin_url(),
- parent_notification.notifier_id(), message_center::RichNotificationData(),
- /*delegate=*/nullptr);
- copy->set_timestamp(parent_notification.timestamp() - base::Milliseconds(1));
- copy->set_settings_button_handler(
- parent_notification.rich_notification_data().settings_button_handler);
- copy->set_fullscreen_visibility(parent_notification.fullscreen_visibility());
- copy->set_delegate(parent_notification.delegate());
- // After copying, set to be a group parent.
- copy->SetGroupParent();
- return copy;
- }
- void NotificationGroupingController::RemoveGroupedChild(
- const std::string& notification_id) {
- if (!grouped_notification_list_->GroupedChildNotificationExists(
- notification_id)) {
- return;
- }
- const std::string parent_id =
- grouped_notification_list_->GetParentForChild(notification_id);
- // Remove parent notification if we are removing the last child notification
- // in a grouped notification.
- auto grouped_notifications =
- grouped_notification_list_->GetGroupedNotificationsForParent(parent_id);
- if (GetActiveNotificationViewController() &&
- GetActiveNotificationViewController()->GetMessageViewForNotificationId(
- parent_id) &&
- grouped_notifications.size() == 1) {
- MessageCenter::Get()->RemoveNotification(parent_id, true);
- return;
- }
- grouped_notification_list_->RemoveGroupedChildNotification(notification_id);
- }
- message_center::NotificationViewController*
- NotificationGroupingController::GetActiveNotificationViewController() {
- if (tray_->IsMessageCenterBubbleShown()) {
- auto* message_list_view = tray_->message_center_bubble()
- ->message_center_view()
- ->message_list_view();
- if (message_list_view)
- return message_list_view;
- }
- return tray_->GetMessagePopupCollection();
- }
- void NotificationGroupingController::OnNotificationAdded(
- const std::string& notification_id) {
- // Do not double process a notification that was re-added as a grouped parent.
- if (adding_parent_grouped_notification_)
- return;
- auto* message_center = MessageCenter::Get();
- Notification* notification =
- message_center->FindNotificationById(notification_id);
- // We only need to process notifications that are children of an
- // existing group. So do nothing otherwise.
- if (!notification)
- return;
- if (!notification->group_child())
- return;
- Notification* parent_notification =
- message_center->FindParentNotification(notification);
- std::string parent_id = parent_notification->id();
- auto* parent_view =
- GetActiveNotificationViewController()
- ? static_cast<AshNotificationView*>(
- GetActiveNotificationViewController()
- ->GetMessageViewForNotificationId(parent_id))
- : nullptr;
- // If we are creating a new notification group for this `notifier_id`,
- // we must create a copy of the designated parent notification and
- // use it to set up a container notification which will hold all
- // notifications for this group.
- if (!grouped_notification_list_->ParentNotificationExists(parent_id)) {
- if (parent_view) {
- parent_view->AnimateSingleToGroup(this, notification_id, parent_id);
- return;
- }
- Notification* parent_notification =
- MessageCenter::Get()->FindNotificationById(parent_id);
- parent_id = SetupParentNotification(parent_notification, parent_id);
- }
- grouped_notification_list_->AddGroupedNotification(notification_id,
- parent_id);
- if (parent_view) {
- parent_view->AddGroupNotification(*notification);
- if (message_center->FindPopupNotificationById(parent_id))
- message_center->ResetPopupTimer(parent_id);
- } else {
- message_center->ResetSinglePopup(parent_id);
- }
- metrics_utils::LogCountOfNotificationsInOneGroup(
- grouped_notification_list_->GetGroupedNotificationsForParent(parent_id)
- .size());
- metrics_utils::LogGroupNotificationAddedType(
- metrics_utils::GroupNotificationType::GROUP_CHILD);
- }
- void NotificationGroupingController::OnNotificationDisplayed(
- const std::string& notification_id,
- const message_center::DisplaySource source) {
- if (grouped_notification_list_->ParentNotificationExists(notification_id))
- PopulateGroupParent(notification_id);
- }
- void NotificationGroupingController::OnNotificationRemoved(
- const std::string& notification_id,
- bool by_user) {
- auto* message_center = MessageCenter::Get();
- if (grouped_notification_list_->GroupedChildNotificationExists(
- notification_id)) {
- const std::string parent_id =
- grouped_notification_list_->GetParentForChild(notification_id);
- RemoveGroupedChild(notification_id);
- if (message_center->FindPopupNotificationById(parent_id))
- message_center->ResetPopupTimer(parent_id);
- metrics_utils::LogCountOfNotificationsInOneGroup(
- grouped_notification_list_->GetGroupedNotificationsForParent(parent_id)
- .size());
- }
- if (grouped_notification_list_->ParentNotificationExists(notification_id)) {
- std::vector<std::string> to_be_deleted;
- auto grouped_notifications =
- grouped_notification_list_->GetGroupedNotificationsForParent(
- notification_id);
- std::copy(grouped_notifications.begin(), grouped_notifications.end(),
- std::back_inserter(to_be_deleted));
- grouped_notification_list_->ClearGroupedNotification(notification_id);
- for (const auto& id : to_be_deleted)
- message_center->RemoveNotification(id, by_user);
- }
- }
- } // namespace ash
|