ash_message_popup_collection.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // Copyright 2014 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/ash_message_popup_collection.h"
  5. #include "ash/constants/ash_features.h"
  6. #include "ash/focus_cycler.h"
  7. #include "ash/public/cpp/shelf_config.h"
  8. #include "ash/public/cpp/shelf_types.h"
  9. #include "ash/public/cpp/shell_window_ids.h"
  10. #include "ash/root_window_controller.h"
  11. #include "ash/shelf/hotseat_widget.h"
  12. #include "ash/shelf/shelf.h"
  13. #include "ash/shell.h"
  14. #include "ash/system/message_center/fullscreen_notification_blocker.h"
  15. #include "ash/system/message_center/message_center_constants.h"
  16. #include "ash/system/message_center/message_view_factory.h"
  17. #include "ash/system/message_center/metrics_utils.h"
  18. #include "ash/system/tray/tray_constants.h"
  19. #include "ash/system/tray/tray_utils.h"
  20. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  21. #include "ash/wm/work_area_insets.h"
  22. #include "base/i18n/rtl.h"
  23. #include "base/metrics/histogram_functions.h"
  24. #include "ui/compositor/compositor.h"
  25. #include "ui/display/display.h"
  26. #include "ui/display/screen.h"
  27. #include "ui/gfx/geometry/rect.h"
  28. #include "ui/message_center/public/cpp/message_center_constants.h"
  29. #include "ui/message_center/views/message_popup_view.h"
  30. #include "ui/wm/core/shadow_types.h"
  31. namespace ash {
  32. namespace {
  33. const int kToastMarginX = 7;
  34. void ReportPopupAnimationSmoothness(int smoothness) {
  35. base::UmaHistogramPercentage("Ash.NotificationPopup.AnimationSmoothness",
  36. smoothness);
  37. }
  38. } // namespace
  39. const char AshMessagePopupCollection::kMessagePopupWidgetName[] =
  40. "ash/message_center/MessagePopup";
  41. AshMessagePopupCollection::AshMessagePopupCollection(Shelf* shelf)
  42. : screen_(nullptr), shelf_(shelf), tray_bubble_height_(0) {
  43. // The order for notifications will be reversed when
  44. // IsNotificationsRefreshEnabled.
  45. if (!features::IsNotificationsRefreshEnabled())
  46. set_inverse();
  47. shelf_->AddObserver(this);
  48. }
  49. AshMessagePopupCollection::~AshMessagePopupCollection() {
  50. shelf_->RemoveObserver(this);
  51. for (views::Widget* widget : tracked_widgets_)
  52. widget->RemoveObserver(this);
  53. CHECK(!views::WidgetObserver::IsInObserverList());
  54. }
  55. void AshMessagePopupCollection::StartObserving(
  56. display::Screen* screen,
  57. const display::Display& display) {
  58. screen_ = screen;
  59. work_area_ = display.work_area();
  60. display_observer_.emplace(this);
  61. if (tray_bubble_height_ > 0)
  62. UpdateWorkArea();
  63. }
  64. void AshMessagePopupCollection::SetTrayBubbleHeight(int height) {
  65. const int old_tray_bubble_height = tray_bubble_height_;
  66. tray_bubble_height_ = height;
  67. // If the shelf is shown during auto-hide state, the distance from the edge
  68. // should be reduced by the height of shelf's shown height.
  69. if (shelf_->GetVisibilityState() == SHELF_AUTO_HIDE &&
  70. shelf_->GetAutoHideState() == SHELF_AUTO_HIDE_SHOWN) {
  71. tray_bubble_height_ -= ShelfConfig::Get()->shelf_size();
  72. }
  73. if (tray_bubble_height_ > 0)
  74. tray_bubble_height_ += message_center::kMarginBetweenPopups;
  75. else
  76. tray_bubble_height_ = 0;
  77. if (old_tray_bubble_height != tray_bubble_height_)
  78. ResetBounds();
  79. }
  80. int AshMessagePopupCollection::GetToastOriginX(
  81. const gfx::Rect& toast_bounds) const {
  82. // In Ash, RTL UI language mirrors the whole ash layout, so the toast
  83. // widgets should be at the bottom-left instead of bottom right.
  84. if (base::i18n::IsRTL())
  85. return work_area_.x() + kToastMarginX;
  86. if (IsFromLeft())
  87. return work_area_.x() + kToastMarginX;
  88. return work_area_.right() - kToastMarginX - toast_bounds.width();
  89. }
  90. int AshMessagePopupCollection::GetBaseline() const {
  91. gfx::Insets tray_bubble_insets = GetTrayBubbleInsets();
  92. int hotseat_height =
  93. shelf_->hotseat_widget()->state() == HotseatState::kExtended
  94. ? shelf_->hotseat_widget()->GetHotseatSize()
  95. : 0;
  96. return work_area_.bottom() - tray_bubble_insets.bottom() -
  97. tray_bubble_height_ - hotseat_height;
  98. }
  99. gfx::Rect AshMessagePopupCollection::GetWorkArea() const {
  100. gfx::Rect work_area_without_tray_bubble = work_area_;
  101. work_area_without_tray_bubble.set_height(
  102. work_area_without_tray_bubble.height() - tray_bubble_height_);
  103. return work_area_without_tray_bubble;
  104. }
  105. bool AshMessagePopupCollection::IsTopDown() const {
  106. return false;
  107. }
  108. bool AshMessagePopupCollection::IsFromLeft() const {
  109. return GetAlignment() == ShelfAlignment::kLeft;
  110. }
  111. bool AshMessagePopupCollection::RecomputeAlignment(
  112. const display::Display& display) {
  113. // Nothing needs to be done.
  114. return false;
  115. }
  116. void AshMessagePopupCollection::ConfigureWidgetInitParamsForContainer(
  117. views::Widget* widget,
  118. views::Widget::InitParams* init_params) {
  119. init_params->shadow_type = views::Widget::InitParams::ShadowType::kDrop;
  120. init_params->shadow_elevation = ::wm::kShadowElevationInactiveWindow;
  121. // On ash, popups go in the status container.
  122. init_params->parent = shelf_->GetWindow()->GetRootWindow()->GetChildById(
  123. kShellWindowId_ShelfContainer);
  124. // Make the widget activatable so it can receive focus when cycling through
  125. // windows (i.e. pressing ctrl + forward/back).
  126. init_params->activatable = views::Widget::InitParams::Activatable::kYes;
  127. init_params->name = kMessagePopupWidgetName;
  128. init_params->corner_radius = kMessagePopupCornerRadius;
  129. Shell::Get()->focus_cycler()->AddWidget(widget);
  130. widget->AddObserver(this);
  131. tracked_widgets_.insert(widget);
  132. }
  133. bool AshMessagePopupCollection::IsPrimaryDisplayForNotification() const {
  134. return screen_ &&
  135. GetCurrentDisplay().id() == screen_->GetPrimaryDisplay().id();
  136. }
  137. bool AshMessagePopupCollection::BlockForMixedFullscreen(
  138. const message_center::Notification& notification) const {
  139. return FullscreenNotificationBlocker::BlockForMixedFullscreen(
  140. notification, RootWindowController::ForWindow(shelf_->GetWindow())
  141. ->IsInFullscreenMode());
  142. }
  143. void AshMessagePopupCollection::NotifyPopupAdded(
  144. message_center::MessagePopupView* popup) {
  145. MessagePopupCollection::NotifyPopupAdded(popup);
  146. popup->message_view()->AddObserver(this);
  147. metrics_utils::LogPopupShown(popup->message_view()->notification_id());
  148. last_pop_up_added_ = popup;
  149. }
  150. void AshMessagePopupCollection::NotifyPopupClosed(
  151. message_center::MessagePopupView* popup) {
  152. metrics_utils::LogPopupClosed(popup);
  153. MessagePopupCollection::NotifyPopupClosed(popup);
  154. popup->message_view()->RemoveObserver(this);
  155. if (last_pop_up_added_ == popup)
  156. last_pop_up_added_ = nullptr;
  157. }
  158. void AshMessagePopupCollection::AnimationStarted() {
  159. if (popups_animating_ == 0 && last_pop_up_added_) {
  160. // Since all the popup widgets use the same compositor, we only need to set
  161. // this when the first popup shows in the animation sequence.
  162. animation_tracker_.emplace(last_pop_up_added_->GetWidget()
  163. ->GetCompositor()
  164. ->RequestNewThroughputTracker());
  165. animation_tracker_->Start(metrics_util::ForSmoothness(
  166. base::BindRepeating(&ReportPopupAnimationSmoothness)));
  167. }
  168. ++popups_animating_;
  169. }
  170. void AshMessagePopupCollection::AnimationFinished() {
  171. --popups_animating_;
  172. // Stop when all animations are finished.
  173. if (animation_tracker_ && popups_animating_ == 0) {
  174. animation_tracker_->Stop();
  175. animation_tracker_.reset();
  176. }
  177. }
  178. message_center::MessagePopupView* AshMessagePopupCollection::CreatePopup(
  179. const message_center::Notification& notification) {
  180. bool a11_feedback_on_init =
  181. notification.rich_notification_data()
  182. .should_make_spoken_feedback_for_popup_updates;
  183. return new message_center::MessagePopupView(
  184. MessageViewFactory::Create(notification, /*shown_in_popup=*/true)
  185. .release(),
  186. this, a11_feedback_on_init);
  187. }
  188. void AshMessagePopupCollection::OnSlideOut(const std::string& notification_id) {
  189. metrics_utils::LogClosedByUser(notification_id, /*is_swipe=*/true,
  190. /*is_popup=*/true);
  191. }
  192. void AshMessagePopupCollection::OnCloseButtonPressed(
  193. const std::string& notification_id) {
  194. metrics_utils::LogClosedByUser(notification_id, /*is_swipe=*/false,
  195. /*is_popup=*/true);
  196. }
  197. void AshMessagePopupCollection::OnSettingsButtonPressed(
  198. const std::string& notification_id) {
  199. metrics_utils::LogSettingsShown(notification_id, /*is_slide_controls=*/false,
  200. /*is_popup=*/true);
  201. }
  202. void AshMessagePopupCollection::OnSnoozeButtonPressed(
  203. const std::string& notification_id) {
  204. metrics_utils::LogSnoozed(notification_id, /*is_slide_controls=*/false,
  205. /*is_popup=*/true);
  206. }
  207. ShelfAlignment AshMessagePopupCollection::GetAlignment() const {
  208. return shelf_->alignment();
  209. }
  210. display::Display AshMessagePopupCollection::GetCurrentDisplay() const {
  211. return display::Screen::GetScreen()->GetDisplayNearestWindow(
  212. shelf_->GetWindow());
  213. }
  214. void AshMessagePopupCollection::UpdateWorkArea() {
  215. gfx::Rect new_work_area =
  216. WorkAreaInsets::ForWindow(shelf_->GetWindow()->GetRootWindow())
  217. ->user_work_area_bounds();
  218. if (work_area_ == new_work_area)
  219. return;
  220. work_area_ = new_work_area;
  221. ResetBounds();
  222. }
  223. ///////////////////////////////////////////////////////////////////////////////
  224. // ShelfObserver:
  225. void AshMessagePopupCollection::OnShelfWorkAreaInsetsChanged() {
  226. UpdateWorkArea();
  227. }
  228. void AshMessagePopupCollection::OnHotseatStateChanged(HotseatState old_state,
  229. HotseatState new_state) {
  230. ResetBounds();
  231. }
  232. ///////////////////////////////////////////////////////////////////////////////
  233. // display::DisplayObserver:
  234. void AshMessagePopupCollection::OnDisplayMetricsChanged(
  235. const display::Display& display,
  236. uint32_t metrics) {
  237. if (GetCurrentDisplay().id() == display.id())
  238. UpdateWorkArea();
  239. }
  240. ///////////////////////////////////////////////////////////////////////////////
  241. // views::WidgetObserver:
  242. void AshMessagePopupCollection::OnWidgetClosing(views::Widget* widget) {
  243. Shell::Get()->focus_cycler()->RemoveWidget(widget);
  244. widget->RemoveObserver(this);
  245. tracked_widgets_.erase(widget);
  246. }
  247. void AshMessagePopupCollection::OnWidgetActivationChanged(views::Widget* widget,
  248. bool active) {
  249. // Note: Each pop-up is contained in it's own widget and we need to manually
  250. // focus the contained MessageView when the widget is activated through the
  251. // FocusCycler.
  252. if (active && Shell::Get()->focus_cycler()->widget_activating() == widget)
  253. widget->GetFocusManager()->SetFocusedView(widget->GetContentsView());
  254. }
  255. } // namespace ash