notification_grouping_controller.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/system/message_center/notification_grouping_controller.h"
  5. #include "ash/system/message_center/ash_message_popup_collection.h"
  6. #include "ash/system/message_center/ash_notification_view.h"
  7. #include "ash/system/message_center/message_center_utils.h"
  8. #include "ash/system/message_center/metrics_utils.h"
  9. #include "ash/system/message_center/unified_message_center_bubble.h"
  10. #include "ash/system/message_center/unified_message_center_view.h"
  11. #include "ash/system/message_center/unified_message_list_view.h"
  12. #include "ash/system/unified/unified_system_tray.h"
  13. #include "ui/display/display.h"
  14. #include "ui/display/screen.h"
  15. #include "ui/message_center/message_center_types.h"
  16. #include "ui/message_center/notification_view_controller.h"
  17. #include "ui/message_center/public/cpp/message_center_constants.h"
  18. #include "ui/message_center/public/cpp/notification.h"
  19. #include "ui/message_center/views/message_view.h"
  20. using message_center::MessageCenter;
  21. using message_center::MessageView;
  22. using message_center::Notification;
  23. namespace ash {
  24. namespace {
  25. class GroupedNotificationList {
  26. public:
  27. GroupedNotificationList() = default;
  28. GroupedNotificationList(const GroupedNotificationList& other) = delete;
  29. GroupedNotificationList& operator=(const GroupedNotificationList& other) =
  30. delete;
  31. ~GroupedNotificationList() = default;
  32. void AddGroupedNotification(const std::string& notification_id,
  33. const std::string& parent_id) {
  34. if (notifications_in_parent_map_.find(parent_id) ==
  35. notifications_in_parent_map_.end()) {
  36. notifications_in_parent_map_[parent_id] = {};
  37. }
  38. child_parent_map_[notification_id] = parent_id;
  39. if (notification_id != parent_id)
  40. notifications_in_parent_map_[parent_id].insert(notification_id);
  41. }
  42. // Remove a single child notification from a grouped notification.
  43. void RemoveGroupedChildNotification(const std::string& notification_id) {
  44. DCHECK(base::Contains(child_parent_map_, notification_id));
  45. const std::string& parent_id = child_parent_map_[notification_id];
  46. notifications_in_parent_map_[parent_id].erase(notification_id);
  47. child_parent_map_.erase(notification_id);
  48. }
  49. // Clear the entire grouped notification with `parent_id`
  50. void ClearGroupedNotification(const std::string& parent_id) {
  51. notifications_in_parent_map_.erase(parent_id);
  52. std::vector<std::string> to_be_deleted;
  53. for (const auto& it : child_parent_map_) {
  54. if (it.second == parent_id)
  55. to_be_deleted.push_back(it.first);
  56. }
  57. for (const auto& child : to_be_deleted)
  58. child_parent_map_.erase(child);
  59. }
  60. const std::string& GetParentForChild(const std::string& child_id) {
  61. return child_parent_map_[child_id];
  62. }
  63. std::set<std::string>& GetGroupedNotificationsForParent(
  64. const std::string& parent_id) {
  65. return notifications_in_parent_map_[parent_id];
  66. }
  67. bool GroupedChildNotificationExists(const std::string& child_id) {
  68. return child_parent_map_.find(child_id) != child_parent_map_.end();
  69. }
  70. bool ParentNotificationExists(const std::string& parent_id) {
  71. return notifications_in_parent_map_.find(parent_id) !=
  72. notifications_in_parent_map_.end();
  73. }
  74. // Replaces all instances of `old_parent_id` with `new_parent_id` in
  75. // the `child_parent_map_`.
  76. void ReplaceParentId(const std::string& new_parent_id,
  77. const std::string& old_parent_id) {
  78. // Remove entry with `new_parent_id` as a child id and replace with
  79. // `old_parent_id` as a child id.
  80. child_parent_map_.erase(new_parent_id);
  81. child_parent_map_[old_parent_id] = new_parent_id;
  82. // Replace all occurrences of `old_parent_id` with `new_parent_id`.
  83. std::vector<std::string> to_be_updated;
  84. for (const auto& child : child_parent_map_) {
  85. if (child.second == old_parent_id)
  86. to_be_updated.push_back(child.first);
  87. }
  88. for (const auto& id : to_be_updated) {
  89. child_parent_map_.erase(child_parent_map_.find(id));
  90. child_parent_map_[id] = new_parent_id;
  91. }
  92. }
  93. private:
  94. // Map for looking up the parent `notification_id` for any given notification
  95. // id.
  96. std::map<std::string, std::string> child_parent_map_;
  97. // Map containing a list of child notification ids per each group parent id.
  98. // Used to keep track of grouped notifications which already have a parent
  99. // notification view.
  100. std::map<std::string, std::set<std::string>> notifications_in_parent_map_;
  101. };
  102. // Needs to be a static instance because we need a single instance to be shared
  103. // across multiple instances of `NotificationGroupingController`. When there are
  104. // multiple screens, each screen has it's own `MessagePopupCollection`,
  105. // `UnifiedSystemTray`, `NotificationGroupingController` etc.
  106. GroupedNotificationList& GetGroupedNotificationListInstance() {
  107. static base::NoDestructor<GroupedNotificationList> instance;
  108. return *instance;
  109. }
  110. } // namespace
  111. NotificationGroupingController::NotificationGroupingController(
  112. UnifiedSystemTray* tray)
  113. : tray_(tray),
  114. grouped_notification_list_(&GetGroupedNotificationListInstance()) {
  115. observer_.Observe(MessageCenter::Get());
  116. }
  117. NotificationGroupingController::~NotificationGroupingController() = default;
  118. void NotificationGroupingController::PopulateGroupParent(
  119. const std::string& notification_id) {
  120. DCHECK(MessageCenter::Get()
  121. ->FindNotificationById(notification_id)
  122. ->group_parent());
  123. MessageView* parent_view =
  124. GetActiveNotificationViewController()
  125. ? GetActiveNotificationViewController()
  126. ->GetMessageViewForNotificationId(notification_id)
  127. : nullptr;
  128. // TODO(crbug/1277765) Need this check to fix crbug/1275765. However, this
  129. // should not be necessary if the message center bubble is initialized
  130. // properly. Need to monitor for empty group notifications if this check is
  131. // hit and fix the root cause.
  132. if (!parent_view) {
  133. LOG(WARNING) << "MessageView does not exist for notification: "
  134. << notification_id;
  135. return;
  136. }
  137. if (!parent_view->IsManuallyExpandedOrCollapsed())
  138. parent_view->SetExpanded(false);
  139. std::vector<const Notification*> notifications;
  140. for (auto* notification : MessageCenter::Get()->GetNotifications()) {
  141. if (notification->notifier_id() == parent_view->notifier_id() &&
  142. notification->id() != parent_view->notification_id()) {
  143. notification->SetGroupChild();
  144. grouped_notification_list_->AddGroupedNotification(notification->id(),
  145. notification_id);
  146. notifications.push_back(notification);
  147. }
  148. }
  149. parent_view->PopulateGroupNotifications(notifications);
  150. }
  151. const std::string& NotificationGroupingController::GetParentIdForChildForTest(
  152. const std::string& notification_id) const {
  153. return grouped_notification_list_->GetParentForChild(notification_id);
  154. }
  155. void NotificationGroupingController::
  156. ConvertFromSingleToGroupNotificationAfterAnimation(
  157. const std::string& notification_id,
  158. std::string& parent_id,
  159. Notification* parent_notification) {
  160. auto* message_center = MessageCenter::Get();
  161. auto* notification_view_controller = GetActiveNotificationViewController();
  162. if (!notification_view_controller)
  163. return;
  164. Notification* notification =
  165. message_center->FindNotificationById(notification_id);
  166. if (!notification)
  167. return;
  168. parent_id = SetupParentNotification(parent_notification, parent_id);
  169. grouped_notification_list_->AddGroupedNotification(notification_id,
  170. parent_id);
  171. MessageView* parent_view =
  172. notification_view_controller->GetMessageViewForNotificationId(parent_id);
  173. if (parent_view)
  174. parent_view->AddGroupNotification(*notification);
  175. else
  176. message_center->ResetSinglePopup(parent_id);
  177. metrics_utils::LogCountOfNotificationsInOneGroup(
  178. grouped_notification_list_->GetGroupedNotificationsForParent(parent_id)
  179. .size());
  180. metrics_utils::LogGroupNotificationAddedType(
  181. metrics_utils::GroupNotificationType::GROUP_CHILD);
  182. }
  183. const std::string& NotificationGroupingController::SetupParentNotification(
  184. Notification* parent_notification,
  185. const std::string& parent_id) {
  186. std::unique_ptr<Notification> notification_copy =
  187. CreateCopyForParentNotification(*parent_notification);
  188. std::string new_parent_id = notification_copy->id();
  189. std::string old_parent_id = parent_id;
  190. grouped_notification_list_->AddGroupedNotification(old_parent_id,
  191. new_parent_id);
  192. // Since MessagePopupCollection uses the global MessageCenter to update its
  193. // animations, the update to MessageCenter in primary display will interfere
  194. // the animation in the secondary display. Thus, calling this functions on all
  195. // display is needed.
  196. for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) {
  197. auto* controller =
  198. message_center_utils::GetActiveNotificationViewControllerForDisplay(
  199. display.id());
  200. if (!controller)
  201. continue;
  202. controller->ConvertNotificationViewToGroupedNotificationView(
  203. /*ungrouped_notification_id=*/old_parent_id,
  204. /*new_grouped_notification_id=*/new_parent_id);
  205. }
  206. grouped_notification_list_->ReplaceParentId(
  207. /*new_parent_id=*/new_parent_id,
  208. /*old_parent_id=*/old_parent_id);
  209. // Add the old parent notification as a group child to the
  210. // newly created parent notification which will act as a
  211. // container for this group as long as it exists.
  212. notification_copy->SetGroupParent();
  213. parent_notification->SetGroupChild();
  214. Notification* new_parent_notification = notification_copy.get();
  215. {
  216. // Prevent double processing `parent_notification`'s copy, used as a group
  217. // parent.
  218. base::AutoReset<bool> reset(&adding_parent_grouped_notification_, true);
  219. MessageCenter::Get()->AddNotification(std::move(notification_copy));
  220. }
  221. // Record metrics for the new parent and new child added.
  222. metrics_utils::LogGroupNotificationAddedType(
  223. metrics_utils::GroupNotificationType::GROUP_PARENT);
  224. metrics_utils::LogGroupNotificationAddedType(
  225. metrics_utils::GroupNotificationType::GROUP_CHILD);
  226. auto* parent_view = GetActiveNotificationViewController()
  227. ? GetActiveNotificationViewController()
  228. ->GetMessageViewForNotificationId(new_parent_id)
  229. : nullptr;
  230. if (parent_view) {
  231. parent_view->UpdateWithNotification(*new_parent_notification);
  232. // Grouped notifications should start off in the collapsed state.
  233. parent_view->SetExpanded(false);
  234. parent_view->AddGroupNotification(*parent_notification);
  235. }
  236. return new_parent_notification->id();
  237. }
  238. std::unique_ptr<Notification>
  239. NotificationGroupingController::CreateCopyForParentNotification(
  240. const Notification& parent_notification) {
  241. // Create a copy with a timestamp that is older than the copied notification.
  242. // We need to set an older timestamp so that this notification will become
  243. // the parent notification for it's notifier_id.
  244. auto copy = std::make_unique<Notification>(
  245. message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
  246. parent_notification.id() +
  247. message_center::kIdSuffixForGroupContainerNotification,
  248. parent_notification.title(), parent_notification.message(),
  249. ui::ImageModel(), std::u16string(), parent_notification.origin_url(),
  250. parent_notification.notifier_id(), message_center::RichNotificationData(),
  251. /*delegate=*/nullptr);
  252. copy->set_timestamp(parent_notification.timestamp() - base::Milliseconds(1));
  253. copy->set_settings_button_handler(
  254. parent_notification.rich_notification_data().settings_button_handler);
  255. copy->set_fullscreen_visibility(parent_notification.fullscreen_visibility());
  256. copy->set_delegate(parent_notification.delegate());
  257. // After copying, set to be a group parent.
  258. copy->SetGroupParent();
  259. return copy;
  260. }
  261. void NotificationGroupingController::RemoveGroupedChild(
  262. const std::string& notification_id) {
  263. if (!grouped_notification_list_->GroupedChildNotificationExists(
  264. notification_id)) {
  265. return;
  266. }
  267. const std::string parent_id =
  268. grouped_notification_list_->GetParentForChild(notification_id);
  269. // Remove parent notification if we are removing the last child notification
  270. // in a grouped notification.
  271. auto grouped_notifications =
  272. grouped_notification_list_->GetGroupedNotificationsForParent(parent_id);
  273. if (GetActiveNotificationViewController() &&
  274. GetActiveNotificationViewController()->GetMessageViewForNotificationId(
  275. parent_id) &&
  276. grouped_notifications.size() == 1) {
  277. MessageCenter::Get()->RemoveNotification(parent_id, true);
  278. return;
  279. }
  280. grouped_notification_list_->RemoveGroupedChildNotification(notification_id);
  281. }
  282. message_center::NotificationViewController*
  283. NotificationGroupingController::GetActiveNotificationViewController() {
  284. if (tray_->IsMessageCenterBubbleShown()) {
  285. auto* message_list_view = tray_->message_center_bubble()
  286. ->message_center_view()
  287. ->message_list_view();
  288. if (message_list_view)
  289. return message_list_view;
  290. }
  291. return tray_->GetMessagePopupCollection();
  292. }
  293. void NotificationGroupingController::OnNotificationAdded(
  294. const std::string& notification_id) {
  295. // Do not double process a notification that was re-added as a grouped parent.
  296. if (adding_parent_grouped_notification_)
  297. return;
  298. auto* message_center = MessageCenter::Get();
  299. Notification* notification =
  300. message_center->FindNotificationById(notification_id);
  301. // We only need to process notifications that are children of an
  302. // existing group. So do nothing otherwise.
  303. if (!notification)
  304. return;
  305. if (!notification->group_child())
  306. return;
  307. Notification* parent_notification =
  308. message_center->FindParentNotification(notification);
  309. std::string parent_id = parent_notification->id();
  310. auto* parent_view =
  311. GetActiveNotificationViewController()
  312. ? static_cast<AshNotificationView*>(
  313. GetActiveNotificationViewController()
  314. ->GetMessageViewForNotificationId(parent_id))
  315. : nullptr;
  316. // If we are creating a new notification group for this `notifier_id`,
  317. // we must create a copy of the designated parent notification and
  318. // use it to set up a container notification which will hold all
  319. // notifications for this group.
  320. if (!grouped_notification_list_->ParentNotificationExists(parent_id)) {
  321. if (parent_view) {
  322. parent_view->AnimateSingleToGroup(this, notification_id, parent_id);
  323. return;
  324. }
  325. Notification* parent_notification =
  326. MessageCenter::Get()->FindNotificationById(parent_id);
  327. parent_id = SetupParentNotification(parent_notification, parent_id);
  328. }
  329. grouped_notification_list_->AddGroupedNotification(notification_id,
  330. parent_id);
  331. if (parent_view) {
  332. parent_view->AddGroupNotification(*notification);
  333. if (message_center->FindPopupNotificationById(parent_id))
  334. message_center->ResetPopupTimer(parent_id);
  335. } else {
  336. message_center->ResetSinglePopup(parent_id);
  337. }
  338. metrics_utils::LogCountOfNotificationsInOneGroup(
  339. grouped_notification_list_->GetGroupedNotificationsForParent(parent_id)
  340. .size());
  341. metrics_utils::LogGroupNotificationAddedType(
  342. metrics_utils::GroupNotificationType::GROUP_CHILD);
  343. }
  344. void NotificationGroupingController::OnNotificationDisplayed(
  345. const std::string& notification_id,
  346. const message_center::DisplaySource source) {
  347. if (grouped_notification_list_->ParentNotificationExists(notification_id))
  348. PopulateGroupParent(notification_id);
  349. }
  350. void NotificationGroupingController::OnNotificationRemoved(
  351. const std::string& notification_id,
  352. bool by_user) {
  353. auto* message_center = MessageCenter::Get();
  354. if (grouped_notification_list_->GroupedChildNotificationExists(
  355. notification_id)) {
  356. const std::string parent_id =
  357. grouped_notification_list_->GetParentForChild(notification_id);
  358. RemoveGroupedChild(notification_id);
  359. if (message_center->FindPopupNotificationById(parent_id))
  360. message_center->ResetPopupTimer(parent_id);
  361. metrics_utils::LogCountOfNotificationsInOneGroup(
  362. grouped_notification_list_->GetGroupedNotificationsForParent(parent_id)
  363. .size());
  364. }
  365. if (grouped_notification_list_->ParentNotificationExists(notification_id)) {
  366. std::vector<std::string> to_be_deleted;
  367. auto grouped_notifications =
  368. grouped_notification_list_->GetGroupedNotificationsForParent(
  369. notification_id);
  370. std::copy(grouped_notifications.begin(), grouped_notifications.end(),
  371. std::back_inserter(to_be_deleted));
  372. grouped_notification_list_->ClearGroupedNotification(notification_id);
  373. for (const auto& id : to_be_deleted)
  374. message_center->RemoveNotification(id, by_user);
  375. }
  376. }
  377. } // namespace ash