1 // Copyright 2016 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/toast/toast_manager_impl.h"
6
7 #include "ash/public/cpp/shelf_config.h"
8 #include "ash/screen_util.h"
9 #include "ash/session/session_controller_impl.h"
10 #include "ash/shelf/shelf.h"
11 #include "ash/shell.h"
12 #include "ash/test/ash_test_base.h"
13 #include "ash/wm/work_area_insets.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string16.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "components/session_manager/session_manager_types.h"
19 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
20 #include "ui/display/manager/display_manager.h"
21 #include "ui/views/widget/widget.h"
22
23 namespace ash {
24
25 class DummyEvent : public ui::Event {
26 public:
DummyEvent()27 DummyEvent() : Event(ui::ET_UNKNOWN, base::TimeTicks(), 0) {}
28 ~DummyEvent() override = default;
29 };
30
31 class ToastManagerImplTest : public AshTestBase {
32 public:
33 ToastManagerImplTest() = default;
34 ~ToastManagerImplTest() override = default;
35
36 private:
SetUp()37 void SetUp() override {
38 AshTestBase::SetUp();
39
40 manager_ = Shell::Get()->toast_manager();
41
42 manager_->ResetSerialForTesting();
43 EXPECT_EQ(0, GetToastSerial());
44
45 // Start in the ACTIVE (logged-in) state.
46 ChangeLockState(false);
47 }
48
49 protected:
manager()50 ToastManagerImpl* manager() { return manager_; }
51
GetToastSerial()52 int GetToastSerial() { return manager_->serial_for_testing(); }
53
GetCurrentOverlay()54 ToastOverlay* GetCurrentOverlay() {
55 return manager_->GetCurrentOverlayForTesting();
56 }
57
GetCurrentWidget()58 views::Widget* GetCurrentWidget() {
59 ToastOverlay* overlay = GetCurrentOverlay();
60 return overlay ? overlay->widget_for_testing() : nullptr;
61 }
62
GetDismissButton()63 ToastOverlayButton* GetDismissButton() {
64 ToastOverlay* overlay = GetCurrentOverlay();
65 DCHECK(overlay);
66 return overlay->dismiss_button_for_testing();
67 }
68
GetCurrentText()69 base::string16 GetCurrentText() {
70 ToastOverlay* overlay = GetCurrentOverlay();
71 return overlay ? overlay->text_ : base::string16();
72 }
73
GetCurrentDismissText()74 base::Optional<base::string16> GetCurrentDismissText() {
75 ToastOverlay* overlay = GetCurrentOverlay();
76 return overlay ? overlay->dismiss_text_ : base::string16();
77 }
78
ClickDismissButton()79 void ClickDismissButton() {
80 ToastOverlay* overlay = GetCurrentOverlay();
81 if (overlay)
82 overlay->ClickDismissButtonForTesting(DummyEvent());
83 }
84
ShowToast(const std::string & text,int32_t duration,bool visible_on_lock_screen=false)85 std::string ShowToast(const std::string& text,
86 int32_t duration,
87 bool visible_on_lock_screen = false) {
88 std::string id = "TOAST_ID_" + base::NumberToString(serial_++);
89 manager()->Show(ToastData(id, base::ASCIIToUTF16(text), duration,
90 base::string16(), visible_on_lock_screen));
91 return id;
92 }
93
ShowToastWithDismiss(const std::string & text,int32_t duration,const base::Optional<std::string> & dismiss_text)94 std::string ShowToastWithDismiss(
95 const std::string& text,
96 int32_t duration,
97 const base::Optional<std::string>& dismiss_text) {
98 base::Optional<base::string16> localized_dismiss;
99 if (dismiss_text.has_value())
100 localized_dismiss = base::ASCIIToUTF16(dismiss_text.value());
101
102 std::string id = "TOAST_ID_" + base::NumberToString(serial_++);
103 manager()->Show(
104 ToastData(id, base::ASCIIToUTF16(text), duration, localized_dismiss));
105 return id;
106 }
107
CancelToast(const std::string & id)108 void CancelToast(const std::string& id) { manager()->Cancel(id); }
109
ChangeLockState(bool lock)110 void ChangeLockState(bool lock) {
111 SessionInfo info;
112 info.state = lock ? session_manager::SessionState::LOCKED
113 : session_manager::SessionState::ACTIVE;
114 Shell::Get()->session_controller()->SetSessionInfo(info);
115 }
116
117 private:
118 ToastManagerImpl* manager_ = nullptr;
119 unsigned int serial_ = 0;
120
121 DISALLOW_COPY_AND_ASSIGN(ToastManagerImplTest);
122 };
123
TEST_F(ToastManagerImplTest,ShowAndCloseAutomatically)124 TEST_F(ToastManagerImplTest, ShowAndCloseAutomatically) {
125 ShowToast("DUMMY", 10);
126
127 EXPECT_EQ(1, GetToastSerial());
128
129 while (GetCurrentOverlay() != nullptr)
130 base::RunLoop().RunUntilIdle();
131 }
132
TEST_F(ToastManagerImplTest,ShowAndCloseManually)133 TEST_F(ToastManagerImplTest, ShowAndCloseManually) {
134 ShowToast("DUMMY", ToastData::kInfiniteDuration);
135
136 EXPECT_EQ(1, GetToastSerial());
137
138 EXPECT_FALSE(GetCurrentWidget()->GetLayer()->GetAnimator()->is_animating());
139
140 ClickDismissButton();
141
142 EXPECT_EQ(nullptr, GetCurrentOverlay());
143 }
144
145 // TODO(crbug.com/959781): Test is flaky.
TEST_F(ToastManagerImplTest,DISABLED_ShowAndCloseManuallyDuringAnimation)146 TEST_F(ToastManagerImplTest, DISABLED_ShowAndCloseManuallyDuringAnimation) {
147 ui::ScopedAnimationDurationScaleMode slow_animation_duration(
148 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
149
150 ShowToast("DUMMY", ToastData::kInfiniteDuration);
151 EXPECT_TRUE(GetCurrentWidget()->GetLayer()->GetAnimator()->is_animating());
152 base::RunLoop().RunUntilIdle();
153
154 EXPECT_EQ(1, GetToastSerial());
155 EXPECT_TRUE(GetCurrentWidget()->GetLayer()->GetAnimator()->is_animating());
156
157 // Try to close it during animation.
158 ClickDismissButton();
159
160 while (GetCurrentWidget()->GetLayer()->GetAnimator()->is_animating())
161 base::RunLoop().RunUntilIdle();
162
163 // Toast isn't closed.
164 EXPECT_TRUE(GetCurrentOverlay() != nullptr);
165 }
166
167 // TODO(crbug.com/959781): Test is flaky.
TEST_F(ToastManagerImplTest,DISABLED_NullMessageHasNoDismissButton)168 TEST_F(ToastManagerImplTest, DISABLED_NullMessageHasNoDismissButton) {
169 ShowToastWithDismiss("DUMMY", 10, base::Optional<std::string>());
170 base::RunLoop().RunUntilIdle();
171 EXPECT_FALSE(GetDismissButton());
172 }
173
174 // TODO(crbug.com/959781): Test is flaky.
TEST_F(ToastManagerImplTest,DISABLED_QueueMessage)175 TEST_F(ToastManagerImplTest, DISABLED_QueueMessage) {
176 ShowToast("DUMMY1", 10);
177 ShowToast("DUMMY2", 10);
178 ShowToast("DUMMY3", 10);
179
180 EXPECT_EQ(1, GetToastSerial());
181 EXPECT_EQ(base::ASCIIToUTF16("DUMMY1"), GetCurrentText());
182
183 while (GetToastSerial() != 2)
184 base::RunLoop().RunUntilIdle();
185
186 EXPECT_EQ(base::ASCIIToUTF16("DUMMY2"), GetCurrentText());
187
188 while (GetToastSerial() != 3)
189 base::RunLoop().RunUntilIdle();
190
191 EXPECT_EQ(base::ASCIIToUTF16("DUMMY3"), GetCurrentText());
192 }
193
TEST_F(ToastManagerImplTest,PositionWithVisibleBottomShelf)194 TEST_F(ToastManagerImplTest, PositionWithVisibleBottomShelf) {
195 Shelf* shelf = GetPrimaryShelf();
196 EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
197 EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
198
199 ShowToast("DUMMY", ToastData::kInfiniteDuration);
200 EXPECT_EQ(1, GetToastSerial());
201
202 gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen();
203 gfx::Rect root_bounds =
204 screen_util::GetDisplayBoundsWithShelf(shelf->GetWindow());
205
206 EXPECT_TRUE(toast_bounds.Intersects(
207 GetPrimaryWorkAreaInsets()->user_work_area_bounds()));
208 EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1);
209
210 gfx::Rect shelf_bounds = shelf->GetIdealBounds();
211 EXPECT_FALSE(toast_bounds.Intersects(shelf_bounds));
212 EXPECT_EQ(shelf_bounds.y() - ToastOverlay::kOffset, toast_bounds.bottom());
213 EXPECT_EQ(
214 root_bounds.bottom() - shelf_bounds.height() - ToastOverlay::kOffset,
215 toast_bounds.bottom());
216 }
217
TEST_F(ToastManagerImplTest,PositionWithAutoHiddenBottomShelf)218 TEST_F(ToastManagerImplTest, PositionWithAutoHiddenBottomShelf) {
219 std::unique_ptr<aura::Window> window(
220 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
221
222 Shelf* shelf = GetPrimaryShelf();
223 EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
224 shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
225 EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
226
227 ShowToast("DUMMY", ToastData::kInfiniteDuration);
228 EXPECT_EQ(1, GetToastSerial());
229
230 gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen();
231 gfx::Rect root_bounds =
232 screen_util::GetDisplayBoundsWithShelf(shelf->GetWindow());
233
234 EXPECT_TRUE(toast_bounds.Intersects(
235 GetPrimaryWorkAreaInsets()->user_work_area_bounds()));
236 EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1);
237 EXPECT_EQ(root_bounds.bottom() -
238 ShelfConfig::Get()->hidden_shelf_in_screen_portion() -
239 ToastOverlay::kOffset,
240 toast_bounds.bottom());
241 }
242
TEST_F(ToastManagerImplTest,PositionWithHiddenBottomShelf)243 TEST_F(ToastManagerImplTest, PositionWithHiddenBottomShelf) {
244 Shelf* shelf = GetPrimaryShelf();
245 EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
246 shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlwaysHidden);
247 EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
248
249 ShowToast("DUMMY", ToastData::kInfiniteDuration);
250 EXPECT_EQ(1, GetToastSerial());
251
252 gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen();
253 gfx::Rect root_bounds =
254 screen_util::GetDisplayBoundsWithShelf(shelf->GetWindow());
255
256 EXPECT_TRUE(toast_bounds.Intersects(
257 GetPrimaryWorkAreaInsets()->user_work_area_bounds()));
258 EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1);
259 EXPECT_EQ(root_bounds.bottom() - ToastOverlay::kOffset,
260 toast_bounds.bottom());
261 }
262
TEST_F(ToastManagerImplTest,PositionWithVisibleLeftShelf)263 TEST_F(ToastManagerImplTest, PositionWithVisibleLeftShelf) {
264 Shelf* shelf = GetPrimaryShelf();
265 EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
266 shelf->SetAlignment(ShelfAlignment::kLeft);
267
268 ShowToast("DUMMY", ToastData::kInfiniteDuration);
269 EXPECT_EQ(1, GetToastSerial());
270
271 gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen();
272 gfx::RectF precise_toast_bounds(toast_bounds);
273 gfx::Rect root_bounds =
274 screen_util::GetDisplayBoundsWithShelf(shelf->GetWindow());
275
276 EXPECT_TRUE(toast_bounds.Intersects(
277 GetPrimaryWorkAreaInsets()->user_work_area_bounds()));
278 EXPECT_EQ(root_bounds.bottom() - ToastOverlay::kOffset,
279 toast_bounds.bottom());
280
281 gfx::Rect shelf_bounds = shelf->GetIdealBounds();
282 EXPECT_FALSE(toast_bounds.Intersects(shelf_bounds));
283 EXPECT_NEAR(
284 shelf_bounds.right() + (root_bounds.width() - shelf_bounds.width()) / 2.0,
285 precise_toast_bounds.CenterPoint().x(), 1.f /* accepted error */);
286 }
287
TEST_F(ToastManagerImplTest,PositionWithUnifiedDesktop)288 TEST_F(ToastManagerImplTest, PositionWithUnifiedDesktop) {
289 display_manager()->SetUnifiedDesktopEnabled(true);
290 UpdateDisplay("1000x500,0+600-100x500");
291
292 Shelf* shelf = GetPrimaryShelf();
293 EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
294 EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
295
296 ShowToast("DUMMY", ToastData::kInfiniteDuration);
297 EXPECT_EQ(1, GetToastSerial());
298
299 gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen();
300 gfx::Rect root_bounds =
301 screen_util::GetDisplayBoundsWithShelf(shelf->GetWindow());
302
303 EXPECT_TRUE(toast_bounds.Intersects(
304 GetPrimaryWorkAreaInsets()->user_work_area_bounds()));
305 EXPECT_TRUE(root_bounds.Contains(toast_bounds));
306 EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1);
307
308 gfx::Rect shelf_bounds = shelf->GetIdealBounds();
309 EXPECT_FALSE(toast_bounds.Intersects(shelf_bounds));
310 EXPECT_EQ(shelf_bounds.y() - ToastOverlay::kOffset, toast_bounds.bottom());
311 EXPECT_EQ(
312 root_bounds.bottom() - shelf_bounds.height() - ToastOverlay::kOffset,
313 toast_bounds.bottom());
314 }
315
TEST_F(ToastManagerImplTest,CancelToast)316 TEST_F(ToastManagerImplTest, CancelToast) {
317 std::string id1 = ShowToast("TEXT1", ToastData::kInfiniteDuration);
318 std::string id2 = ShowToast("TEXT2", ToastData::kInfiniteDuration);
319 std::string id3 = ShowToast("TEXT3", ToastData::kInfiniteDuration);
320
321 // Confirm that the first toast is shown.
322 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
323 // Cancel the queued toast.
324 CancelToast(id2);
325 // Confirm that the shown toast is still visible.
326 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
327 // Cancel the shown toast.
328 CancelToast(id1);
329 // Confirm that the next toast is visible.
330 EXPECT_EQ(base::ASCIIToUTF16("TEXT3"), GetCurrentText());
331 // Cancel the shown toast.
332 CancelToast(id3);
333 // Confirm that the shown toast disappears.
334 EXPECT_FALSE(GetCurrentOverlay());
335 // Confirm that only 1 toast is shown.
336 EXPECT_EQ(2, GetToastSerial());
337 }
338
TEST_F(ToastManagerImplTest,ShowToastOnLockScreen)339 TEST_F(ToastManagerImplTest, ShowToastOnLockScreen) {
340 // Simulate device lock.
341 ChangeLockState(true);
342
343 // Trying to show a toast.
344 std::string id1 = ShowToast("TEXT1", ToastData::kInfiniteDuration);
345 // Confirm that it's not visible because it's queued.
346 EXPECT_EQ(nullptr, GetCurrentOverlay());
347
348 // Simulate device unlock.
349 ChangeLockState(false);
350 EXPECT_TRUE(GetCurrentOverlay());
351 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
352 }
353
TEST_F(ToastManagerImplTest,ShowSupportedToastOnLockScreen)354 TEST_F(ToastManagerImplTest, ShowSupportedToastOnLockScreen) {
355 // Simulate device lock.
356 ChangeLockState(true);
357
358 // Trying to show a toast.
359 std::string id1 = ShowToast("TEXT1", ToastData::kInfiniteDuration,
360 /*visible_on_lock_screen=*/true);
361 // Confirm it's visible and not queued.
362 EXPECT_NE(nullptr, GetCurrentOverlay());
363 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
364
365 // Simulate device unlock.
366 ChangeLockState(false);
367 // Confirm that the toast is still visible.
368 EXPECT_NE(nullptr, GetCurrentOverlay());
369 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
370 }
371
TEST_F(ToastManagerImplTest,DeferToastByLockScreen)372 TEST_F(ToastManagerImplTest, DeferToastByLockScreen) {
373 // Show a toast.
374 std::string id1 = ShowToast("TEXT1", ToastData::kInfiniteDuration,
375 /*visible_on_lock_screen=*/true);
376 EXPECT_NE(nullptr, GetCurrentOverlay());
377 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
378
379 // Simulate device lock.
380 ChangeLockState(true);
381 // Confirm that it gets hidden.
382 EXPECT_NE(nullptr, GetCurrentOverlay());
383 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
384
385 // Simulate device unlock.
386 ChangeLockState(false);
387 // Confirm that it gets visible again.
388 EXPECT_NE(nullptr, GetCurrentOverlay());
389 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
390 }
391
TEST_F(ToastManagerImplTest,NotDeferToastForLockScreen)392 TEST_F(ToastManagerImplTest, NotDeferToastForLockScreen) {
393 // Show a toast.
394 std::string id1 = ShowToast("TEXT1", ToastData::kInfiniteDuration,
395 /*visible_on_lock_screen=*/false);
396 EXPECT_NE(nullptr, GetCurrentOverlay());
397 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
398
399 // Simulate device lock.
400 ChangeLockState(true);
401 // Confirm that it gets hidden.
402 EXPECT_EQ(nullptr, GetCurrentOverlay());
403
404 // Simulate device unlock.
405 ChangeLockState(false);
406 // Confirm that it gets visible again.
407 EXPECT_NE(nullptr, GetCurrentOverlay());
408 EXPECT_EQ(base::ASCIIToUTF16("TEXT1"), GetCurrentText());
409 }
410
411 } // namespace ash
412