unified_message_list_view.cc 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. // Copyright 2018 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/unified_message_list_view.h"
  5. #include <string>
  6. #include "ash/bubble/bubble_constants.h"
  7. #include "ash/constants/ash_features.h"
  8. #include "ash/public/cpp/metrics_util.h"
  9. #include "ash/system/message_center/ash_notification_view.h"
  10. #include "ash/system/message_center/message_center_constants.h"
  11. #include "ash/system/message_center/message_center_style.h"
  12. #include "ash/system/message_center/message_center_utils.h"
  13. #include "ash/system/message_center/message_view_factory.h"
  14. #include "ash/system/message_center/metrics_utils.h"
  15. #include "ash/system/message_center/notification_swipe_control_view.h"
  16. #include "ash/system/message_center/unified_message_center_view.h"
  17. #include "ash/system/tray/tray_constants.h"
  18. #include "ash/system/unified/unified_system_tray_model.h"
  19. #include "base/auto_reset.h"
  20. #include "base/callback_forward.h"
  21. #include "base/callback_helpers.h"
  22. #include "base/memory/scoped_refptr.h"
  23. #include "base/metrics/histogram_functions.h"
  24. #include "base/metrics/histogram_macros.h"
  25. #include "base/time/time.h"
  26. #include "ui/compositor/compositor.h"
  27. #include "ui/gfx/animation/linear_animation.h"
  28. #include "ui/gfx/canvas.h"
  29. #include "ui/message_center/notification_view_controller.h"
  30. #include "ui/message_center/public/cpp/notification_types.h"
  31. #include "ui/message_center/views/message_view.h"
  32. #include "ui/views/animation/animation_delegate_views.h"
  33. #include "ui/views/background.h"
  34. #include "ui/views/border.h"
  35. #include "ui/views/layout/box_layout.h"
  36. #include "ui/views/layout/fill_layout.h"
  37. #include "ui/views/widget/widget.h"
  38. using message_center::MessageCenter;
  39. using message_center::MessageView;
  40. using message_center::Notification;
  41. namespace ash {
  42. namespace {
  43. constexpr base::TimeDelta kClosingAnimationDuration = base::Milliseconds(320);
  44. constexpr base::TimeDelta kClearAllStackedAnimationDuration =
  45. base::Milliseconds(40);
  46. constexpr base::TimeDelta kClearAllVisibleAnimationDuration =
  47. base::Milliseconds(160);
  48. constexpr char kMessageViewContainerClassName[] = "MessageViewContainer";
  49. constexpr char kMoveDownAnimationSmoothnessHistogramName[] =
  50. "Ash.Notification.MoveDown.AnimationSmoothness";
  51. constexpr char kClearAllStackedAnimationSmoothnessHistogramName[] =
  52. "Ash.Notification.ClearAllStacked.AnimationSmoothness";
  53. constexpr char kClearAllVisibleAnimationSmoothnessHistogramName[] =
  54. "Ash.Notification.ClearAllVisible.AnimationSmoothness";
  55. constexpr char kExpandOrCollapseAnimationSmoothnessHistogramName[] =
  56. "Ash.Notification.ExpandOrCollapse.AnimationSmoothness";
  57. void RecordAnimationSmoothness(const std::string& histogram_name,
  58. int smoothness) {
  59. base::UmaHistogramPercentage(histogram_name, smoothness);
  60. }
  61. void SetupThroughputTrackerForAnimationSmoothness(
  62. views::Widget* widget,
  63. absl::optional<ui::ThroughputTracker>& tracker,
  64. const char* histogram_name) {
  65. // `widget` may not exist in tests.
  66. if (!widget)
  67. return;
  68. tracker.emplace(widget->GetCompositor()->RequestNewThroughputTracker());
  69. tracker->Start(ash::metrics_util::ForSmoothness(
  70. base::BindRepeating(&RecordAnimationSmoothness, histogram_name)));
  71. }
  72. } // namespace
  73. // Container view of notification and swipe control.
  74. // All children of UnifiedMessageListView should be MessageViewContainer.
  75. class UnifiedMessageListView::MessageViewContainer
  76. : public MessageView::Observer,
  77. public views::View {
  78. public:
  79. MessageViewContainer(MessageView* message_view,
  80. UnifiedMessageListView* list_view)
  81. : message_view_(message_view),
  82. list_view_(list_view),
  83. control_view_(new NotificationSwipeControlView(message_view)) {
  84. message_view_->AddObserver(this);
  85. if (!features::IsNotificationsRefreshEnabled()) {
  86. message_view_->SetBackground(
  87. views::CreateSolidBackground(SK_ColorTRANSPARENT));
  88. }
  89. SetLayoutManager(std::make_unique<views::FillLayout>());
  90. AddChildView(control_view_);
  91. AddChildView(message_view_);
  92. }
  93. MessageViewContainer(const MessageViewContainer&) = delete;
  94. MessageViewContainer& operator=(const MessageViewContainer&) = delete;
  95. ~MessageViewContainer() override { message_view_->RemoveObserver(this); }
  96. base::TimeDelta GetBoundsAnimationDuration() const {
  97. auto* notification = MessageCenter::Get()->FindNotificationById(
  98. message_view()->notification_id());
  99. if (!notification)
  100. return base::Milliseconds(0);
  101. return message_view()->GetBoundsAnimationDuration(*notification);
  102. }
  103. // Update the border and background corners based on if the notification is
  104. // at the top or the bottom. If `force_update` is true, ignore previous states
  105. // and always update the border.
  106. void UpdateBorder(bool is_top, bool is_bottom, bool force_update) {
  107. if (is_top_ == is_top && is_bottom_ == is_bottom && !force_update)
  108. return;
  109. is_top_ = is_top;
  110. is_bottom_ = is_bottom;
  111. // We do not need to have a separate border with rounded corners with the
  112. // new UI. This is because the entire scroll view has rounded corners now.
  113. if (!features::IsNotificationsRefreshEnabled()) {
  114. message_view_->SetBorder(
  115. is_bottom ? views::NullBorder()
  116. : views::CreateSolidSidedBorder(
  117. gfx::Insets::TLBR(
  118. 0, 0, kUnifiedNotificationSeparatorThickness, 0),
  119. message_center_style::kSeperatorColor));
  120. }
  121. int message_center_notification_corner_radius =
  122. features::IsNotificationsRefreshEnabled()
  123. ? kMessageCenterNotificationCornerRadius
  124. : 0;
  125. const int top_radius = is_top ? kBubbleCornerRadius
  126. : message_center_notification_corner_radius;
  127. const int bottom_radius = is_bottom
  128. ? kBubbleCornerRadius
  129. : message_center_notification_corner_radius;
  130. message_view_->UpdateCornerRadius(top_radius, bottom_radius);
  131. control_view_->UpdateCornerRadius(top_radius, bottom_radius);
  132. }
  133. // Reset rounding the corner of the view. This is called when we end a slide.
  134. void ResetCornerRadius() {
  135. need_update_corner_radius_ = true;
  136. int corner_radius = features::IsNotificationsRefreshEnabled()
  137. ? kMessageCenterNotificationCornerRadius
  138. : 0;
  139. message_view_->UpdateCornerRadius(corner_radius, corner_radius);
  140. }
  141. // Collapses the notification if its state haven't changed manually by a user.
  142. void Collapse() {
  143. if (!message_view_->IsManuallyExpandedOrCollapsed())
  144. message_view_->SetExpanded(false);
  145. }
  146. // Check if the notification is manually expanded / collapsed before and
  147. // restores the state.
  148. void LoadExpandedState(UnifiedSystemTrayModel* model, bool is_latest) {
  149. DCHECK(model);
  150. base::AutoReset<bool> scoped_reset(&loading_expanded_state_, true);
  151. absl::optional<bool> manually_expanded =
  152. model->GetNotificationExpanded(GetNotificationId());
  153. if (manually_expanded.has_value()) {
  154. message_view_->SetExpanded(manually_expanded.value());
  155. message_view_->SetManuallyExpandedOrCollapsed(true);
  156. } else {
  157. // Expand the latest notification, and collapse all other notifications.
  158. message_view_->SetExpanded(is_latest &&
  159. message_view_->IsAutoExpandingAllowed());
  160. }
  161. }
  162. // Stores if the notification is manually expanded or collapsed so that we can
  163. // restore that when UnifiedSystemTray is reopened.
  164. void StoreExpandedState(UnifiedSystemTrayModel* model) {
  165. DCHECK(model);
  166. if (message_view_->IsManuallyExpandedOrCollapsed()) {
  167. model->SetNotificationExpanded(GetNotificationId(),
  168. message_view_->IsExpanded());
  169. }
  170. }
  171. void SlideOutAndClose() {
  172. is_slid_out_programatically = true;
  173. message_view_->SlideOutAndClose(1 /* direction */);
  174. }
  175. std::string GetNotificationId() const {
  176. return message_view_->notification_id();
  177. }
  178. void UpdateWithNotification(const Notification& notification) {
  179. message_view_->UpdateWithNotification(notification);
  180. }
  181. void CloseSwipeControl() { message_view_->CloseSwipeControl(); }
  182. // Returns if the notification is pinned i.e. can be removed manually.
  183. bool IsPinned() const {
  184. return message_view_->GetMode() == MessageView::Mode::PINNED;
  185. }
  186. // Returns the direction that the notification is swiped out. If swiped to the
  187. // left, it returns -1 and if sipwed to the right, it returns 1. By default
  188. // (i.e. the notification is removed but not by touch gesture), it returns 1.
  189. int GetSlideDirection() const {
  190. return message_view_->GetSlideAmount() < 0 ? -1 : 1;
  191. }
  192. // Allows UnifiedMessageListView to force preferred size to change during
  193. // animations.
  194. void TriggerPreferredSizeChangedForAnimation() {
  195. views::View::PreferredSizeChanged();
  196. }
  197. // views::View:
  198. void ChildPreferredSizeChanged(views::View* child) override {
  199. // If we've already been removed, ignore new child size changes.
  200. if (is_removed_)
  201. return;
  202. // PreferredSizeChanged will trigger
  203. // UnifiedMessageListView::ChildPreferredSizeChanged.
  204. base::ScopedClosureRunner defer_preferred_size_changed(base::BindOnce(
  205. &MessageViewContainer::PreferredSizeChanged, base::Unretained(this)));
  206. if (!features::IsNotificationsRefreshEnabled())
  207. return;
  208. // Ignore non user triggered expand/collapses.
  209. if (loading_expanded_state_)
  210. return;
  211. auto* notification = MessageCenter::Get()->FindNotificationById(
  212. message_view()->notification_id());
  213. if (!notification)
  214. return;
  215. needs_bounds_animation_ = true;
  216. }
  217. gfx::Size CalculatePreferredSize() const override {
  218. if (list_view_->IsAnimatingExpandOrCollapseContainer(this)) {
  219. // Width should never change, only height.
  220. return gfx::Size(list_view_->message_view_width_,
  221. gfx::Tween::IntValueBetween(
  222. list_view_->GetCurrentValue(),
  223. start_bounds_.height(), target_bounds_.height()));
  224. }
  225. return gfx::Size(list_view_->message_view_width_, target_bounds_.height());
  226. }
  227. const char* GetClassName() const override {
  228. return kMessageViewContainerClassName;
  229. }
  230. // MessageView::Observer:
  231. void OnSlideChanged(const std::string& notification_id) override {
  232. control_view_->UpdateButtonsVisibility();
  233. if (!features::IsNotificationsRefreshEnabled() ||
  234. notification_id != GetNotificationId() ||
  235. message_view_->GetSlideAmount() == 0 || !need_update_corner_radius_) {
  236. return;
  237. }
  238. need_update_corner_radius_ = false;
  239. message_view_->UpdateCornerRadius(kBubbleCornerRadius, kBubbleCornerRadius);
  240. // Also update `above_view_`'s bottom and `below_view_`'s top corner radius
  241. // when sliding.
  242. size_t index = list_view_->GetIndexOf(this).value();
  243. auto list_child_views = list_view_->children();
  244. above_view_ = (index == 0) ? nullptr : AsMVC(list_child_views[index - 1]);
  245. if (above_view_)
  246. above_view_->message_view()->UpdateCornerRadius(
  247. kMessageCenterNotificationCornerRadius, kBubbleCornerRadius);
  248. below_view_ = (index == list_child_views.size() - 1)
  249. ? nullptr
  250. : AsMVC(list_child_views[index + 1]);
  251. if (below_view_)
  252. below_view_->message_view()->UpdateCornerRadius(
  253. kBubbleCornerRadius, kMessageCenterNotificationCornerRadius);
  254. }
  255. void OnSlideEnded(const std::string& notification_id) override {
  256. if (!features::IsNotificationsRefreshEnabled() ||
  257. notification_id != GetNotificationId())
  258. return;
  259. absl::optional<size_t> index = list_view_->GetIndexOf(this);
  260. if (!index.has_value())
  261. return;
  262. auto list_child_views = list_view_->children();
  263. above_view_ = (index == size_t{0})
  264. ? nullptr
  265. : AsMVC(list_child_views[index.value() - 1]);
  266. below_view_ = (index == list_child_views.size() - 1)
  267. ? nullptr
  268. : AsMVC(list_child_views[index.value() + 1]);
  269. // Reset the corner radius of views to their normal state.
  270. ResetCornerRadius();
  271. if (above_view_ && !above_view_->is_slid_out())
  272. above_view_->ResetCornerRadius();
  273. if (below_view_ && !below_view_->is_slid_out())
  274. below_view_->ResetCornerRadius();
  275. }
  276. void OnPreSlideOut(const std::string& notification_id) override {
  277. if (!is_slid_out_programatically) {
  278. metrics_utils::LogClosedByUser(notification_id, /*is_swipe=*/true,
  279. /*is_popup=*/false);
  280. }
  281. }
  282. void OnSlideOut(const std::string& notification_id) override {
  283. is_slid_out_ = true;
  284. set_is_removed();
  285. list_view_->OnNotificationSlidOut();
  286. }
  287. gfx::Rect start_bounds() const { return start_bounds_; }
  288. gfx::Rect target_bounds() const { return target_bounds_; }
  289. bool is_removed() const { return is_removed_; }
  290. void ResetNeedsBoundsAnimation() { needs_bounds_animation_ = false; }
  291. bool needs_bounds_animation() const { return needs_bounds_animation_; }
  292. void set_start_bounds(const gfx::Rect& start_bounds) {
  293. start_bounds_ = start_bounds;
  294. }
  295. void set_target_bounds(const gfx::Rect& ideal_bounds) {
  296. target_bounds_ = ideal_bounds;
  297. }
  298. void set_is_removed() { is_removed_ = true; }
  299. bool is_slid_out() { return is_slid_out_; }
  300. MessageView* message_view() { return message_view_; }
  301. const MessageView* message_view() const { return message_view_; }
  302. private:
  303. // The bounds that the container starts animating from. If not animating, it's
  304. // ignored.
  305. gfx::Rect start_bounds_;
  306. // The final bounds of the container. If not animating, it's same as the
  307. // actual bounds().
  308. gfx::Rect target_bounds_;
  309. // True when the notification is removed and during slide out animation.
  310. bool is_removed_ = false;
  311. // True if the notification is slid out completely.
  312. bool is_slid_out_ = false;
  313. // True if the notification is slid out through SlideOutAndClose()
  314. // programagically. False if slid out manually by the user.
  315. bool is_slid_out_programatically = false;
  316. // Keeps track if this view is at the top or bottom of `list_view_`. Storing
  317. // this to prevent unnecessary update.
  318. bool is_top_ = false;
  319. bool is_bottom_ = false;
  320. // Whether expanded state is being set programmatically. Used to prevent
  321. // animating programmatic expands which occur on open.
  322. bool loading_expanded_state_ = false;
  323. // Set to flag the view as requiring an expand or collapse animation.
  324. bool needs_bounds_animation_ = false;
  325. // The views directly above or below this view in the list. Used to update
  326. // corner radius when sliding.
  327. MessageViewContainer* above_view_ = nullptr;
  328. MessageViewContainer* below_view_ = nullptr;
  329. // `need_update_corner_radius_` indicates that we need to update the corner
  330. // radius of the view when sliding.
  331. bool need_update_corner_radius_ = true;
  332. MessageView* const message_view_;
  333. UnifiedMessageListView* const list_view_;
  334. NotificationSwipeControlView* const control_view_;
  335. };
  336. UnifiedMessageListView::UnifiedMessageListView(
  337. UnifiedMessageCenterView* message_center_view,
  338. scoped_refptr<UnifiedSystemTrayModel> model)
  339. : views::AnimationDelegateViews(this),
  340. message_center_view_(message_center_view),
  341. model_(model),
  342. animation_(std::make_unique<gfx::LinearAnimation>(this)),
  343. is_notifications_refresh_enabled_(
  344. features::IsNotificationsRefreshEnabled()),
  345. message_view_width_(is_notifications_refresh_enabled_
  346. ? kTrayMenuWidth - (2 * kMessageCenterPadding)
  347. : kTrayMenuWidth) {
  348. message_center_observation_.Observe(MessageCenter::Get());
  349. animation_->SetCurrentValue(1.0);
  350. if (!is_notifications_refresh_enabled_) {
  351. SetBackground(views::CreateSolidBackground(
  352. message_center_style::kSwipeControlBackgroundColor));
  353. }
  354. }
  355. UnifiedMessageListView::~UnifiedMessageListView() {
  356. DCHECK(model_);
  357. model_->ClearNotificationChanges();
  358. for (auto* view : children())
  359. AsMVC(view)->StoreExpandedState(model_.get());
  360. }
  361. void UnifiedMessageListView::Init() {
  362. DCHECK(model_);
  363. bool is_latest = true;
  364. for (auto* notification :
  365. message_center_utils::GetSortedNotificationsWithOwnView()) {
  366. auto* view =
  367. new MessageViewContainer(CreateMessageView(*notification), this);
  368. view->LoadExpandedState(model_.get(), is_latest);
  369. // The insertion order for notifications will be reversed when
  370. // is_notifications_refresh_enabled_.
  371. AddChildViewAt(view,
  372. is_notifications_refresh_enabled_ ? children().size() : 0);
  373. MessageCenter::Get()->DisplayedNotification(
  374. notification->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER);
  375. is_latest = false;
  376. }
  377. UpdateBorders(/*force_update=*/true);
  378. UpdateBounds();
  379. }
  380. void UnifiedMessageListView::ClearAllWithAnimation() {
  381. if (state_ == State::CLEAR_ALL_STACKED || state_ == State::CLEAR_ALL_VISIBLE)
  382. return;
  383. ResetBounds();
  384. UMA_HISTOGRAM_COUNTS_100("ChromeOS.SystemTray.NotificationsRemovedByClearAll",
  385. children().size());
  386. // Record a ClosedByClearAll metric for each notification dismissed.
  387. for (auto* child : children()) {
  388. auto* view = AsMVC(child);
  389. metrics_utils::LogClosedByClearAll(view->GetNotificationId());
  390. }
  391. {
  392. base::AutoReset<bool> auto_reset(&ignore_notification_remove_, true);
  393. message_center::MessageCenter::Get()->RemoveAllNotifications(
  394. true /* by_user */,
  395. message_center::MessageCenter::RemoveType::NON_PINNED);
  396. }
  397. state_ = State::CLEAR_ALL_STACKED;
  398. UpdateClearAllAnimation();
  399. if (state_ != State::IDLE)
  400. StartAnimation();
  401. }
  402. std::vector<message_center::Notification*>
  403. UnifiedMessageListView::GetAllNotifications() const {
  404. std::vector<message_center::Notification*> notifications;
  405. for (views::View* view : children()) {
  406. // The view may be present in the view hierarchy, but deleted in the message
  407. // center.
  408. auto* notification = MessageCenter::Get()->FindVisibleNotificationById(
  409. AsMVC(view)->GetNotificationId());
  410. if (notification)
  411. notifications.insert(notifications.begin(), notification);
  412. }
  413. return notifications;
  414. }
  415. std::vector<std::string> UnifiedMessageListView::GetAllNotificationIds() const {
  416. std::vector<std::string> notifications;
  417. for (views::View* view : children()) {
  418. notifications.insert(notifications.begin(),
  419. AsMVC(view)->GetNotificationId());
  420. }
  421. return notifications;
  422. }
  423. std::vector<message_center::Notification*>
  424. UnifiedMessageListView::GetNotificationsAboveY(int y_offset) const {
  425. std::vector<message_center::Notification*> notifications;
  426. for (views::View* view : children()) {
  427. const int bottom_limit =
  428. view->bounds().y() + kNotificationIconStackThreshold;
  429. if (bottom_limit <= y_offset) {
  430. auto* notification = MessageCenter::Get()->FindVisibleNotificationById(
  431. AsMVC(view)->GetNotificationId());
  432. if (notification)
  433. notifications.insert(notifications.begin(), notification);
  434. }
  435. }
  436. return notifications;
  437. }
  438. std::vector<message_center::Notification*>
  439. UnifiedMessageListView::GetNotificationsBelowY(int y_offset) const {
  440. std::vector<message_center::Notification*> notifications;
  441. for (views::View* view : children()) {
  442. const int bottom_limit =
  443. view->bounds().y() + kNotificationIconStackThreshold;
  444. if (bottom_limit >= y_offset) {
  445. auto* notification = MessageCenter::Get()->FindVisibleNotificationById(
  446. AsMVC(view)->GetNotificationId());
  447. if (notification)
  448. notifications.push_back(notification);
  449. }
  450. }
  451. return notifications;
  452. }
  453. std::vector<std::string> UnifiedMessageListView::GetNotificationIdsAboveY(
  454. int y_offset) const {
  455. std::vector<std::string> notifications;
  456. for (views::View* view : children()) {
  457. const int bottom_limit =
  458. view->bounds().y() + kNotificationIconStackThreshold;
  459. if (bottom_limit > y_offset)
  460. continue;
  461. notifications.insert(notifications.begin(),
  462. AsMVC(view)->GetNotificationId());
  463. }
  464. return notifications;
  465. }
  466. std::vector<std::string> UnifiedMessageListView::GetNotificationIdsBelowY(
  467. int y_offset) const {
  468. std::vector<std::string> notifications;
  469. for (views::View* view : children()) {
  470. const int top_of_notification = view->bounds().y();
  471. if (top_of_notification < y_offset)
  472. continue;
  473. notifications.insert(notifications.begin(),
  474. AsMVC(view)->GetNotificationId());
  475. }
  476. return notifications;
  477. }
  478. int UnifiedMessageListView::GetTotalNotificationCount() const {
  479. return static_cast<int>(children().size());
  480. }
  481. int UnifiedMessageListView::GetTotalPinnedNotificationCount() const {
  482. int count = 0;
  483. for (auto* child : children()) {
  484. if (AsMVC(child)->IsPinned())
  485. count++;
  486. }
  487. return count;
  488. }
  489. bool UnifiedMessageListView::IsAnimating() const {
  490. return animation_->is_animating();
  491. }
  492. bool UnifiedMessageListView::IsAnimatingExpandOrCollapseContainer(
  493. const views::View* view) const {
  494. if (!view || !expand_or_collapsing_container_)
  495. return false;
  496. DCHECK_EQ(kMessageViewContainerClassName, view->GetClassName())
  497. << view->GetClassName() << " is not a " << kMessageViewContainerClassName;
  498. const MessageViewContainer* message_view_container = AsMVC(view);
  499. return message_view_container == expand_or_collapsing_container_;
  500. }
  501. void UnifiedMessageListView::ChildPreferredSizeChanged(views::View* child) {
  502. if (ignore_size_change_)
  503. return;
  504. // No State::EXPAND_OR_COLLAPSE animation in the old UI.
  505. if (!features::IsNotificationsRefreshEnabled()) {
  506. ResetBounds();
  507. return;
  508. }
  509. auto* message_view_container = AsMVC(child);
  510. // Immediately complete the old expand/collapse animation. It will be snapped
  511. // to the target bounds when UpdateBounds() is called. If the other animations
  512. // are occurring, prefer them over expand/collapse.
  513. if (message_view_container->needs_bounds_animation() &&
  514. (state_ == State::IDLE || state_ == State::EXPAND_OR_COLLAPSE)) {
  515. if (animation_->is_animating()) {
  516. // Finish the previous expand animation instantly.
  517. animation_->End();
  518. }
  519. expand_or_collapsing_container_ = message_view_container;
  520. expand_or_collapsing_container_->ResetNeedsBoundsAnimation();
  521. UpdateBounds();
  522. state_ = State::EXPAND_OR_COLLAPSE;
  523. StartAnimation();
  524. return;
  525. }
  526. if (state_ == State::EXPAND_OR_COLLAPSE)
  527. return;
  528. ResetBounds();
  529. }
  530. void UnifiedMessageListView::PreferredSizeChanged() {
  531. views::View::PreferredSizeChanged();
  532. if (message_center_view_)
  533. message_center_view_->ListPreferredSizeChanged();
  534. }
  535. void UnifiedMessageListView::Layout() {
  536. for (auto* child : children()) {
  537. auto* view = AsMVC(child);
  538. if (state_ == State::IDLE) {
  539. view->SetBoundsRect(view->target_bounds());
  540. continue;
  541. }
  542. view->SetBoundsRect(gfx::Tween::RectValueBetween(
  543. GetCurrentValue(), view->start_bounds(), view->target_bounds()));
  544. }
  545. }
  546. gfx::Rect UnifiedMessageListView::GetNotificationBounds(
  547. const std::string& notification_id) const {
  548. const MessageViewContainer* child = nullptr;
  549. if (!notification_id.empty())
  550. child = GetNotificationById(notification_id);
  551. return child ? child->bounds() : GetLastNotificationBounds();
  552. }
  553. gfx::Rect UnifiedMessageListView::GetLastNotificationBounds() const {
  554. return children().empty() ? gfx::Rect() : children().back()->bounds();
  555. }
  556. gfx::Rect UnifiedMessageListView::GetNotificationBoundsBelowY(
  557. int y_offset) const {
  558. const auto it = std::find_if(children().cbegin(), children().cend(),
  559. [y_offset](const views::View* v) {
  560. return v->bounds().bottom() >= y_offset;
  561. });
  562. return (it == children().cend()) ? gfx::Rect() : (*it)->bounds();
  563. }
  564. gfx::Size UnifiedMessageListView::CalculatePreferredSize() const {
  565. if (state_ == State::IDLE)
  566. return gfx::Size(message_view_width_, target_height_);
  567. return gfx::Size(message_view_width_,
  568. gfx::Tween::IntValueBetween(GetCurrentValue(), start_height_,
  569. target_height_));
  570. }
  571. const char* UnifiedMessageListView::GetClassName() const {
  572. return "UnifiedMessageListView";
  573. }
  574. void UnifiedMessageListView::AnimateResize() {
  575. // TODO(crbug/1330026): Refactor UnifiedMessageListView animations to use
  576. // animation builder instead of the existing layout based animations.
  577. }
  578. message_center::MessageView*
  579. UnifiedMessageListView::GetMessageViewForNotificationId(const std::string& id) {
  580. auto it =
  581. std::find_if(children().begin(), children().end(), [&](auto* child) {
  582. DCHECK(child->GetClassName() == kMessageViewContainerClassName);
  583. return static_cast<MessageViewContainer*>(child)
  584. ->message_view()
  585. ->notification_id() == id;
  586. });
  587. if (it == children().end())
  588. return nullptr;
  589. return static_cast<MessageViewContainer*>(*it)->message_view();
  590. }
  591. void UnifiedMessageListView::ConvertNotificationViewToGroupedNotificationView(
  592. const std::string& ungrouped_notification_id,
  593. const std::string& new_grouped_notification_id) {
  594. auto* message_view =
  595. GetMessageViewForNotificationId(ungrouped_notification_id);
  596. if (message_view)
  597. message_view->set_notification_id(new_grouped_notification_id);
  598. }
  599. void UnifiedMessageListView::ConvertGroupedNotificationViewToNotificationView(
  600. const std::string& grouped_notification_id,
  601. const std::string& new_single_notification_id) {
  602. GetMessageViewForNotificationId(grouped_notification_id)
  603. ->set_notification_id(new_single_notification_id);
  604. }
  605. void UnifiedMessageListView::OnNotificationAdded(const std::string& id) {
  606. auto* notification = MessageCenter::Get()->FindVisibleNotificationById(id);
  607. if (!notification)
  608. return;
  609. // A group child notification should be added to its parent's message view.
  610. if (is_notifications_refresh_enabled_ && notification->group_child())
  611. return;
  612. InterruptClearAll();
  613. // Collapse all notifications before adding new one.
  614. CollapseAllNotifications();
  615. // We only need to update if we already have a message view associated with
  616. // the notification. This happens when we convert a single notification to a
  617. // group notification, ConvertNotificationViewToGroupedNotificationView()
  618. // changes the id of the single notification to the parent's.
  619. if (is_notifications_refresh_enabled_ && GetNotificationById(id)) {
  620. OnNotificationUpdated(id);
  621. return;
  622. }
  623. // Find the correct index to insert the new notification based on the sorted
  624. // order.
  625. auto child_views = children();
  626. size_t index_to_insert = child_views.size();
  627. for (size_t i = 0; i < child_views.size(); ++i) {
  628. MessageViewContainer* message_view =
  629. static_cast<MessageViewContainer*>(child_views[i]);
  630. auto* child_notification =
  631. MessageCenter::Get()->FindVisibleNotificationById(
  632. message_view->GetNotificationId());
  633. if (!child_notification)
  634. break;
  635. // The insertion order for notifications will be reversed when
  636. // is_notifications_refresh_enabled_.
  637. if ((is_notifications_refresh_enabled_ &&
  638. !message_center_utils::CompareNotifications(child_notification,
  639. notification)) ||
  640. (!is_notifications_refresh_enabled_ &&
  641. !message_center_utils::CompareNotifications(notification,
  642. child_notification))) {
  643. index_to_insert = i;
  644. break;
  645. }
  646. }
  647. auto* view = CreateMessageView(*notification);
  648. view->SetExpanded(view->IsAutoExpandingAllowed());
  649. AddChildViewAt(new MessageViewContainer(view, this), index_to_insert);
  650. UpdateBorders(/*force_update=*/false);
  651. ResetBounds();
  652. }
  653. void UnifiedMessageListView::OnNotificationRemoved(const std::string& id,
  654. bool by_user) {
  655. if (ignore_notification_remove_)
  656. return;
  657. // The corresponding MessageView may have already been deleted after being
  658. // manually slid out.
  659. auto* child = GetNotificationById(id);
  660. if (!child)
  661. return;
  662. InterruptClearAll();
  663. ResetBounds();
  664. child->set_is_removed();
  665. // If the MessageView is slid out, then do nothing here. The MOVE_DOWN
  666. // animation will be started in OnNotificationSlidOut().
  667. if (!child->is_slid_out())
  668. child->SlideOutAndClose();
  669. }
  670. void UnifiedMessageListView::OnNotificationSlidOut() {
  671. DeleteRemovedNotifications();
  672. // |message_center_view_| can be null in tests.
  673. if (message_center_view_)
  674. message_center_view_->OnNotificationSlidOut();
  675. state_ = State::MOVE_DOWN;
  676. UpdateBounds();
  677. StartAnimation();
  678. }
  679. void UnifiedMessageListView::OnNotificationUpdated(const std::string& id) {
  680. auto* notification = MessageCenter::Get()->FindVisibleNotificationById(id);
  681. if (!notification)
  682. return;
  683. InterruptClearAll();
  684. // The corresponding MessageView may have been slid out and deleted, so just
  685. // ignore this update as the notification will soon be deleted.
  686. auto* child = GetNotificationById(id);
  687. if (!child)
  688. return;
  689. child->UpdateWithNotification(*notification);
  690. ResetBounds();
  691. }
  692. void UnifiedMessageListView::OnSlideStarted(
  693. const std::string& notification_id) {
  694. // When the swipe control for |notification_id| is shown, hide all other swipe
  695. // controls.
  696. for (auto* child : children()) {
  697. auto* view = AsMVC(child);
  698. if (view->GetNotificationId() != notification_id)
  699. view->CloseSwipeControl();
  700. }
  701. }
  702. void UnifiedMessageListView::OnCloseButtonPressed(
  703. const std::string& notification_id) {
  704. metrics_utils::LogClosedByUser(notification_id, /*is_swipe=*/false,
  705. /*is_popup=*/false);
  706. }
  707. void UnifiedMessageListView::OnSettingsButtonPressed(
  708. const std::string& notification_id) {
  709. metrics_utils::LogSettingsShown(notification_id, /*is_slide_controls=*/false,
  710. /*is_popup=*/false);
  711. }
  712. void UnifiedMessageListView::OnSnoozeButtonPressed(
  713. const std::string& notification_id) {
  714. metrics_utils::LogSnoozed(notification_id, /*is_slide_controls=*/false,
  715. /*is_popup=*/false);
  716. }
  717. void UnifiedMessageListView::AnimationEnded(const gfx::Animation* animation) {
  718. if (throughput_tracker_) {
  719. // Reset `throughput_tracker_` to reset animation metrics recording.
  720. throughput_tracker_->Stop();
  721. throughput_tracker_.reset();
  722. }
  723. // This is also called from AnimationCanceled().
  724. // TODO(crbug/1272104): Can we do better? If we are interrupting an animation,
  725. // this does not look good.
  726. animation_->SetCurrentValue(1.0);
  727. PreferredSizeChanged();
  728. switch (state_) {
  729. case State::IDLE:
  730. case State::EXPAND_OR_COLLAPSE:
  731. expand_or_collapsing_container_ = nullptr;
  732. [[fallthrough]];
  733. case State::MOVE_DOWN:
  734. state_ = State::IDLE;
  735. break;
  736. case State::CLEAR_ALL_STACKED:
  737. case State::CLEAR_ALL_VISIBLE:
  738. DeleteRemovedNotifications();
  739. UpdateClearAllAnimation();
  740. break;
  741. }
  742. UpdateBorders(/*force_update=*/false);
  743. if (state_ != State::IDLE)
  744. StartAnimation();
  745. }
  746. void UnifiedMessageListView::AnimationProgressed(
  747. const gfx::Animation* animation) {
  748. if (state_ == State::EXPAND_OR_COLLAPSE)
  749. expand_or_collapsing_container_->TriggerPreferredSizeChangedForAnimation();
  750. PreferredSizeChanged();
  751. }
  752. void UnifiedMessageListView::AnimationCanceled(
  753. const gfx::Animation* animation) {
  754. AnimationEnded(animation);
  755. }
  756. MessageView* UnifiedMessageListView::CreateMessageView(
  757. const Notification& notification) {
  758. auto* message_view =
  759. MessageViewFactory::Create(notification, /*shown_in_popup=*/false)
  760. .release();
  761. ConfigureMessageView(message_view);
  762. return message_view;
  763. }
  764. void UnifiedMessageListView::ConfigureMessageView(
  765. message_center::MessageView* message_view) {
  766. // Setting grouped notifications as nested is handled in
  767. // `AshNotificationView`.
  768. auto* notification = MessageCenter::Get()->FindNotificationById(
  769. message_view->notification_id());
  770. // `notification` may not exist in tests.
  771. if (notification && !notification->group_child())
  772. message_view->SetIsNested();
  773. message_view_multi_source_observation_.AddObservation(message_view);
  774. // `message_center_view_` may not exist in tests.
  775. if (message_center_view_)
  776. message_center_view_->ConfigureMessageView(message_view);
  777. }
  778. std::vector<message_center::Notification*>
  779. UnifiedMessageListView::GetStackedNotifications() const {
  780. return message_center_view_->GetStackedNotifications();
  781. }
  782. std::vector<std::string>
  783. UnifiedMessageListView::GetNonVisibleNotificationIdsInViewHierarchy() const {
  784. return message_center_view_->GetNonVisibleNotificationIdsInViewHierarchy();
  785. }
  786. // static
  787. const UnifiedMessageListView::MessageViewContainer*
  788. UnifiedMessageListView::AsMVC(const views::View* v) {
  789. return static_cast<const MessageViewContainer*>(v);
  790. }
  791. // static
  792. UnifiedMessageListView::MessageViewContainer* UnifiedMessageListView::AsMVC(
  793. views::View* v) {
  794. return static_cast<MessageViewContainer*>(v);
  795. }
  796. const UnifiedMessageListView::MessageViewContainer*
  797. UnifiedMessageListView::GetNotificationById(const std::string& id) const {
  798. const auto i = std::find_if(
  799. children().cbegin(), children().cend(),
  800. [id](const auto* v) { return AsMVC(v)->GetNotificationId() == id; });
  801. return (i == children().cend()) ? nullptr : AsMVC(*i);
  802. }
  803. UnifiedMessageListView::MessageViewContainer*
  804. UnifiedMessageListView::GetNextRemovableNotification() {
  805. if (is_notifications_refresh_enabled_) {
  806. const auto i =
  807. std::find_if(children().rbegin(), children().rend(),
  808. [](const auto* v) { return !AsMVC(v)->IsPinned(); });
  809. return (i == children().rend()) ? nullptr : AsMVC(*i);
  810. }
  811. const auto i =
  812. std::find_if(children().cbegin(), children().cend(),
  813. [](const auto* v) { return !AsMVC(v)->IsPinned(); });
  814. return (i == children().cend()) ? nullptr : AsMVC(*i);
  815. }
  816. void UnifiedMessageListView::CollapseAllNotifications() {
  817. base::AutoReset<bool> auto_reset(&ignore_size_change_, true);
  818. for (auto* child : children())
  819. AsMVC(child)->Collapse();
  820. }
  821. void UnifiedMessageListView::UpdateBorders(bool force_update) {
  822. // The top notification is drawn with rounded corners when the stacking bar
  823. // is not shown.
  824. bool is_top = state_ != State::MOVE_DOWN;
  825. if (!is_notifications_refresh_enabled_)
  826. is_top = is_top && children().size() == 1;
  827. for (auto* child : children()) {
  828. AsMVC(child)->UpdateBorder(is_top, child == children().back(),
  829. force_update);
  830. is_top = false;
  831. }
  832. }
  833. void UnifiedMessageListView::UpdateBounds() {
  834. int y = 0;
  835. for (auto* child : children()) {
  836. auto* view = AsMVC(child);
  837. // Height is taken from preferred size, which is calculated based on the
  838. // tween and animation state when animations are occurring. So views which
  839. // are animating will provide the correct interpolated height here.
  840. const int height = view->GetHeightForWidth(message_view_width_);
  841. const int direction = view->GetSlideDirection();
  842. if (y > 0 && is_notifications_refresh_enabled_)
  843. y += kMessageListNotificationSpacing;
  844. view->set_start_bounds(view->target_bounds());
  845. view->set_target_bounds(view->is_removed()
  846. ? gfx::Rect(message_view_width_ * direction, y,
  847. message_view_width_, height)
  848. : gfx::Rect(0, y, message_view_width_, height));
  849. y += height;
  850. }
  851. start_height_ = target_height_;
  852. target_height_ = y;
  853. }
  854. void UnifiedMessageListView::ResetBounds() {
  855. DeleteRemovedNotifications();
  856. UpdateBounds();
  857. state_ = State::IDLE;
  858. if (animation_->is_animating())
  859. animation_->End();
  860. else
  861. PreferredSizeChanged();
  862. }
  863. void UnifiedMessageListView::InterruptClearAll() {
  864. if (state_ != State::CLEAR_ALL_STACKED && state_ != State::CLEAR_ALL_VISIBLE)
  865. return;
  866. for (auto* child : children()) {
  867. auto* view = AsMVC(child);
  868. if (!view->IsPinned())
  869. view->set_is_removed();
  870. }
  871. DeleteRemovedNotifications();
  872. }
  873. void UnifiedMessageListView::DeleteRemovedNotifications() {
  874. DCHECK(model_);
  875. views::View::Views removed_views;
  876. std::copy_if(children().cbegin(), children().cend(),
  877. std::back_inserter(removed_views),
  878. [](const auto* v) { return AsMVC(v)->is_removed(); });
  879. {
  880. base::AutoReset<bool> auto_reset(&is_deleting_removed_notifications_, true);
  881. for (auto* view : removed_views) {
  882. model_->RemoveNotificationExpanded(AsMVC(view)->GetNotificationId());
  883. message_view_multi_source_observation_.RemoveObservation(
  884. AsMVC(view)->message_view());
  885. delete view;
  886. }
  887. }
  888. UpdateBorders(/*force_update=*/false);
  889. }
  890. void UnifiedMessageListView::StartAnimation() {
  891. DCHECK_NE(state_, State::IDLE);
  892. base::TimeDelta animation_duration;
  893. switch (state_) {
  894. case State::IDLE:
  895. break;
  896. case State::MOVE_DOWN:
  897. SetupThroughputTrackerForAnimationSmoothness(
  898. GetWidget(), throughput_tracker_,
  899. kMoveDownAnimationSmoothnessHistogramName);
  900. animation_duration = kClosingAnimationDuration;
  901. break;
  902. case State::CLEAR_ALL_STACKED:
  903. SetupThroughputTrackerForAnimationSmoothness(
  904. GetWidget(), throughput_tracker_,
  905. kClearAllStackedAnimationSmoothnessHistogramName);
  906. animation_duration = kClearAllStackedAnimationDuration;
  907. break;
  908. case State::CLEAR_ALL_VISIBLE:
  909. SetupThroughputTrackerForAnimationSmoothness(
  910. GetWidget(), throughput_tracker_,
  911. kClearAllVisibleAnimationSmoothnessHistogramName);
  912. animation_duration = kClearAllVisibleAnimationDuration;
  913. break;
  914. case State::EXPAND_OR_COLLAPSE:
  915. SetupThroughputTrackerForAnimationSmoothness(
  916. GetWidget(), throughput_tracker_,
  917. kExpandOrCollapseAnimationSmoothnessHistogramName);
  918. DCHECK(expand_or_collapsing_container_);
  919. animation_duration =
  920. expand_or_collapsing_container_
  921. ? expand_or_collapsing_container_->GetBoundsAnimationDuration()
  922. : base::Milliseconds(
  923. kLargeImageExpandAndCollapseAnimationDuration);
  924. break;
  925. }
  926. animation_->SetDuration(animation_duration);
  927. animation_->Start();
  928. }
  929. void UnifiedMessageListView::UpdateClearAllAnimation() {
  930. DCHECK(state_ == State::CLEAR_ALL_STACKED ||
  931. state_ == State::CLEAR_ALL_VISIBLE);
  932. auto* view = GetNextRemovableNotification();
  933. if (view)
  934. view->set_is_removed();
  935. if (state_ == State::CLEAR_ALL_STACKED) {
  936. const auto non_visible_notification_ids =
  937. GetNonVisibleNotificationIdsInViewHierarchy();
  938. if (view && non_visible_notification_ids.size() > 0) {
  939. // Immediately remove all notifications that are outside of the scrollable
  940. // window.
  941. for (const auto& id : non_visible_notification_ids) {
  942. auto* message_view_container = GetNotificationById(id);
  943. if (message_view_container)
  944. message_view_container->set_is_removed();
  945. }
  946. DeleteRemovedNotifications();
  947. UpdateBounds();
  948. start_height_ = target_height_;
  949. for (auto* child : children()) {
  950. auto* view = AsMVC(child);
  951. view->set_start_bounds(view->target_bounds());
  952. }
  953. PreferredSizeChanged();
  954. } else {
  955. state_ = State::CLEAR_ALL_VISIBLE;
  956. }
  957. }
  958. if (state_ == State::CLEAR_ALL_VISIBLE) {
  959. UpdateBounds();
  960. if (view || start_height_ != target_height_)
  961. state_ = State::CLEAR_ALL_VISIBLE;
  962. else
  963. state_ = State::IDLE;
  964. }
  965. }
  966. double UnifiedMessageListView::GetCurrentValue() const {
  967. gfx::Tween::Type tween;
  968. switch (state_) {
  969. case State::IDLE:
  970. // No animations are used for State::IDLE.
  971. NOTREACHED();
  972. tween = gfx::Tween::LINEAR;
  973. break;
  974. case State::CLEAR_ALL_STACKED:
  975. case State::MOVE_DOWN:
  976. tween = gfx::Tween::FAST_OUT_SLOW_IN;
  977. break;
  978. case State::CLEAR_ALL_VISIBLE:
  979. tween = gfx::Tween::EASE_IN;
  980. break;
  981. case State::EXPAND_OR_COLLAPSE:
  982. tween = gfx::Tween::FAST_OUT_SLOW_IN_3;
  983. break;
  984. }
  985. return gfx::Tween::CalculateValue(tween, animation_->GetCurrentValue());
  986. }
  987. } // namespace ash