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