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