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