1 // Copyright (c) 2012 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.
5 #include "ash/system/tray/system_tray.h"
9 #include "ash/accessibility_delegate.h"
10 #include "ash/root_window_controller.h"
11 #include "ash/shelf/shelf_layout_manager.h"
12 #include "ash/shelf/shelf_widget.h"
13 #include "ash/shell.h"
14 #include "ash/system/status_area_widget.h"
15 #include "ash/system/tray/system_tray_bubble.h"
16 #include "ash/system/tray/system_tray_item.h"
17 #include "ash/system/tray/tray_constants.h"
18 #include "ash/system/tray/tray_popup_item_container.h"
19 #include "ash/test/ash_test_base.h"
20 #include "ash/wm/window_util.h"
21 #include "base/run_loop.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "ui/aura/window.h"
24 #include "ui/base/ui_base_types.h"
25 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
26 #include "ui/events/test/event_generator.h"
27 #include "ui/gfx/geometry/point.h"
28 #include "ui/gfx/geometry/rect.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/layout/fill_layout.h"
31 #include "ui/views/view.h"
32 #include "ui/views/widget/widget.h"
33 #include "ui/views/widget/widget_delegate.h"
36 #include "base/win/windows_version.h"
44 SystemTray
* GetSystemTray() {
45 return Shell::GetPrimaryRootWindowController()->shelf()->
46 status_area_widget()->system_tray();
49 // Trivial item implementation that tracks its views for testing.
50 class TestItem
: public SystemTrayItem
{
52 TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL
) {}
54 views::View
* CreateTrayView(user::LoginStatus status
) override
{
55 tray_view_
= new views::View
;
56 // Add a label so it has non-zero width.
57 tray_view_
->SetLayoutManager(new views::FillLayout
);
58 tray_view_
->AddChildView(new views::Label(base::UTF8ToUTF16("Tray")));
62 views::View
* CreateDefaultView(user::LoginStatus status
) override
{
63 default_view_
= new views::View
;
64 default_view_
->SetLayoutManager(new views::FillLayout
);
65 default_view_
->AddChildView(new views::Label(base::UTF8ToUTF16("Default")));
69 views::View
* CreateDetailedView(user::LoginStatus status
) override
{
70 detailed_view_
= new views::View
;
71 detailed_view_
->SetLayoutManager(new views::FillLayout
);
72 detailed_view_
->AddChildView(
73 new views::Label(base::UTF8ToUTF16("Detailed")));
74 return detailed_view_
;
77 views::View
* CreateNotificationView(user::LoginStatus status
) override
{
78 notification_view_
= new views::View
;
79 return notification_view_
;
82 void DestroyTrayView() override
{ tray_view_
= NULL
; }
84 void DestroyDefaultView() override
{ default_view_
= NULL
; }
86 void DestroyDetailedView() override
{ detailed_view_
= NULL
; }
88 void DestroyNotificationView() override
{ notification_view_
= NULL
; }
90 void UpdateAfterLoginStatusChange(user::LoginStatus status
) override
{}
92 views::View
* tray_view() const { return tray_view_
; }
93 views::View
* default_view() const { return default_view_
; }
94 views::View
* detailed_view() const { return detailed_view_
; }
95 views::View
* notification_view() const { return notification_view_
; }
98 views::View
* tray_view_
;
99 views::View
* default_view_
;
100 views::View
* detailed_view_
;
101 views::View
* notification_view_
;
104 // Trivial item implementation that returns NULL from tray/default/detailed
105 // view creation methods.
106 class TestNoViewItem
: public SystemTrayItem
{
108 TestNoViewItem() : SystemTrayItem(GetSystemTray()) {}
110 views::View
* CreateTrayView(user::LoginStatus status
) override
{
114 views::View
* CreateDefaultView(user::LoginStatus status
) override
{
118 views::View
* CreateDetailedView(user::LoginStatus status
) override
{
122 views::View
* CreateNotificationView(user::LoginStatus status
) override
{
126 void DestroyTrayView() override
{}
127 void DestroyDefaultView() override
{}
128 void DestroyDetailedView() override
{}
129 void DestroyNotificationView() override
{}
130 void UpdateAfterLoginStatusChange(user::LoginStatus status
) override
{}
133 class ModalWidgetDelegate
: public views::WidgetDelegateView
{
135 ModalWidgetDelegate() {}
136 ~ModalWidgetDelegate() override
{}
138 views::View
* GetContentsView() override
{ return this; }
139 ui::ModalType
GetModalType() const override
{ return ui::MODAL_TYPE_SYSTEM
; }
142 DISALLOW_COPY_AND_ASSIGN(ModalWidgetDelegate
);
147 typedef AshTestBase SystemTrayTest
;
149 TEST_F(SystemTrayTest
, SystemTrayDefaultView
) {
150 SystemTray
* tray
= GetSystemTray();
151 ASSERT_TRUE(tray
->GetWidget());
153 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
155 // Ensure that closing the bubble destroys it.
156 ASSERT_TRUE(tray
->CloseSystemBubble());
157 RunAllPendingInMessageLoop();
158 ASSERT_FALSE(tray
->CloseSystemBubble());
161 // Opening and closing the bubble should change the coloring of the tray.
162 TEST_F(SystemTrayTest
, SystemTrayColoring
) {
163 SystemTray
* tray
= GetSystemTray();
164 ASSERT_TRUE(tray
->GetWidget());
165 // At the beginning the tray coloring is not active.
166 ASSERT_FALSE(tray
->draw_background_as_active());
168 // Showing the system bubble should show the background as active.
169 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
170 ASSERT_TRUE(tray
->draw_background_as_active());
172 // Closing the system menu should change the coloring back to normal.
173 ASSERT_TRUE(tray
->CloseSystemBubble());
174 RunAllPendingInMessageLoop();
175 ASSERT_FALSE(tray
->draw_background_as_active());
178 // Closing the system bubble through an alignment change should change the
179 // system tray coloring back to normal.
180 TEST_F(SystemTrayTest
, SystemTrayColoringAfterAlignmentChange
) {
181 SystemTray
* tray
= GetSystemTray();
182 ASSERT_TRUE(tray
->GetWidget());
183 ShelfLayoutManager
* manager
=
184 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
185 manager
->SetAlignment(SHELF_ALIGNMENT_BOTTOM
);
186 // At the beginning the tray coloring is not active.
187 ASSERT_FALSE(tray
->draw_background_as_active());
189 // Showing the system bubble should show the background as active.
190 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
191 ASSERT_TRUE(tray
->draw_background_as_active());
193 // Changing the alignment should close the system bubble and change the
195 manager
->SetAlignment(SHELF_ALIGNMENT_LEFT
);
196 ASSERT_FALSE(tray
->draw_background_as_active());
197 RunAllPendingInMessageLoop();
198 // The bubble should already be closed by now.
199 ASSERT_FALSE(tray
->CloseSystemBubble());
202 TEST_F(SystemTrayTest
, SystemTrayTestItems
) {
203 SystemTray
* tray
= GetSystemTray();
204 ASSERT_TRUE(tray
->GetWidget());
206 TestItem
* test_item
= new TestItem
;
207 TestItem
* detailed_item
= new TestItem
;
208 tray
->AddTrayItem(test_item
);
209 tray
->AddTrayItem(detailed_item
);
211 // Check items have been added
212 const std::vector
<SystemTrayItem
*>& items
= tray
->GetTrayItems();
214 std::find(items
.begin(), items
.end(), test_item
) != items
.end());
216 std::find(items
.begin(), items
.end(), detailed_item
) != items
.end());
218 // Ensure the tray views are created.
219 ASSERT_TRUE(test_item
->tray_view() != NULL
);
220 ASSERT_TRUE(detailed_item
->tray_view() != NULL
);
222 // Ensure a default views are created.
223 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
224 ASSERT_TRUE(test_item
->default_view() != NULL
);
225 ASSERT_TRUE(detailed_item
->default_view() != NULL
);
227 // Show the detailed view, ensure it's created and the default view destroyed.
228 tray
->ShowDetailedView(detailed_item
, 0, false, BUBBLE_CREATE_NEW
);
229 RunAllPendingInMessageLoop();
230 ASSERT_TRUE(test_item
->default_view() == NULL
);
231 ASSERT_TRUE(detailed_item
->detailed_view() != NULL
);
233 // Show the default view, ensure it's created and the detailed view destroyed.
234 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
235 RunAllPendingInMessageLoop();
236 ASSERT_TRUE(test_item
->default_view() != NULL
);
237 ASSERT_TRUE(detailed_item
->detailed_view() == NULL
);
240 TEST_F(SystemTrayTest
, SystemTrayNoViewItems
) {
241 SystemTray
* tray
= GetSystemTray();
242 ASSERT_TRUE(tray
->GetWidget());
244 // Verify that no crashes occur on items lacking some views.
245 TestNoViewItem
* no_view_item
= new TestNoViewItem
;
246 tray
->AddTrayItem(no_view_item
);
247 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
248 tray
->ShowDetailedView(no_view_item
, 0, false, BUBBLE_USE_EXISTING
);
249 RunAllPendingInMessageLoop();
252 TEST_F(SystemTrayTest
, TrayWidgetAutoResizes
) {
253 SystemTray
* tray
= GetSystemTray();
254 ASSERT_TRUE(tray
->GetWidget());
256 // Add an initial tray item so that the tray gets laid out correctly.
257 TestItem
* initial_item
= new TestItem
;
258 tray
->AddTrayItem(initial_item
);
260 gfx::Size initial_size
= tray
->GetWidget()->GetWindowBoundsInScreen().size();
262 TestItem
* new_item
= new TestItem
;
263 tray
->AddTrayItem(new_item
);
265 gfx::Size new_size
= tray
->GetWidget()->GetWindowBoundsInScreen().size();
267 // Adding the new item should change the size of the tray.
268 EXPECT_NE(initial_size
.ToString(), new_size
.ToString());
270 // Hiding the tray view of the new item should also change the size of the
272 new_item
->tray_view()->SetVisible(false);
273 EXPECT_EQ(initial_size
.ToString(),
274 tray
->GetWidget()->GetWindowBoundsInScreen().size().ToString());
276 new_item
->tray_view()->SetVisible(true);
277 EXPECT_EQ(new_size
.ToString(),
278 tray
->GetWidget()->GetWindowBoundsInScreen().size().ToString());
281 TEST_F(SystemTrayTest
, SystemTrayNotifications
) {
282 SystemTray
* tray
= GetSystemTray();
283 ASSERT_TRUE(tray
->GetWidget());
285 TestItem
* test_item
= new TestItem
;
286 TestItem
* detailed_item
= new TestItem
;
287 tray
->AddTrayItem(test_item
);
288 tray
->AddTrayItem(detailed_item
);
290 // Ensure the tray views are created.
291 ASSERT_TRUE(test_item
->tray_view() != NULL
);
292 ASSERT_TRUE(detailed_item
->tray_view() != NULL
);
294 // Ensure a notification view is created.
295 tray
->ShowNotificationView(test_item
);
296 ASSERT_TRUE(test_item
->notification_view() != NULL
);
298 // Show the default view, notification view should remain.
299 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
300 RunAllPendingInMessageLoop();
301 ASSERT_TRUE(test_item
->notification_view() != NULL
);
303 // Show the detailed view, ensure the notification view remains.
304 tray
->ShowDetailedView(detailed_item
, 0, false, BUBBLE_CREATE_NEW
);
305 RunAllPendingInMessageLoop();
306 ASSERT_TRUE(detailed_item
->detailed_view() != NULL
);
307 ASSERT_TRUE(test_item
->notification_view() != NULL
);
309 // Hide the detailed view, ensure the notification view still exists.
310 ASSERT_TRUE(tray
->CloseSystemBubble());
311 RunAllPendingInMessageLoop();
312 ASSERT_TRUE(detailed_item
->detailed_view() == NULL
);
313 ASSERT_TRUE(test_item
->notification_view() != NULL
);
316 TEST_F(SystemTrayTest
, BubbleCreationTypesTest
) {
317 SystemTray
* tray
= GetSystemTray();
318 ASSERT_TRUE(tray
->GetWidget());
320 TestItem
* test_item
= new TestItem
;
321 tray
->AddTrayItem(test_item
);
323 // Ensure the tray views are created.
324 ASSERT_TRUE(test_item
->tray_view() != NULL
);
326 // Show the default view, ensure the notification view is destroyed.
327 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
328 RunAllPendingInMessageLoop();
330 views::Widget
* widget
= test_item
->default_view()->GetWidget();
331 gfx::Rect bubble_bounds
= widget
->GetWindowBoundsInScreen();
333 tray
->ShowDetailedView(test_item
, 0, true, BUBBLE_USE_EXISTING
);
334 RunAllPendingInMessageLoop();
336 EXPECT_FALSE(test_item
->default_view());
338 EXPECT_EQ(bubble_bounds
.ToString(), test_item
->detailed_view()->GetWidget()->
339 GetWindowBoundsInScreen().ToString());
340 EXPECT_EQ(widget
, test_item
->detailed_view()->GetWidget());
342 tray
->ShowDefaultView(BUBBLE_USE_EXISTING
);
343 RunAllPendingInMessageLoop();
345 EXPECT_EQ(bubble_bounds
.ToString(), test_item
->default_view()->GetWidget()->
346 GetWindowBoundsInScreen().ToString());
347 EXPECT_EQ(widget
, test_item
->default_view()->GetWidget());
350 // Tests that the tray is laid out properly and is fully contained within
352 TEST_F(SystemTrayTest
, TrayBoundsInWidget
) {
353 ShelfLayoutManager
* manager
=
354 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
355 StatusAreaWidget
* widget
=
356 Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
357 SystemTray
* tray
= widget
->system_tray();
359 // Test in bottom alignment.
360 manager
->SetAlignment(SHELF_ALIGNMENT_BOTTOM
);
361 gfx::Rect window_bounds
= widget
->GetWindowBoundsInScreen();
362 gfx::Rect tray_bounds
= tray
->GetBoundsInScreen();
363 EXPECT_TRUE(window_bounds
.bottom() >= tray_bounds
.bottom());
364 EXPECT_TRUE(window_bounds
.right() >= tray_bounds
.right());
365 EXPECT_TRUE(window_bounds
.x() >= tray_bounds
.x());
366 EXPECT_TRUE(window_bounds
.y() >= tray_bounds
.y());
368 // Test in the left alignment.
369 manager
->SetAlignment(SHELF_ALIGNMENT_LEFT
);
370 window_bounds
= widget
->GetWindowBoundsInScreen();
371 tray_bounds
= tray
->GetBoundsInScreen();
372 EXPECT_TRUE(window_bounds
.bottom() >= tray_bounds
.bottom());
373 EXPECT_TRUE(window_bounds
.right() >= tray_bounds
.right());
374 EXPECT_TRUE(window_bounds
.x() >= tray_bounds
.x());
375 EXPECT_TRUE(window_bounds
.y() >= tray_bounds
.y());
377 // Test in the right alignment.
378 manager
->SetAlignment(SHELF_ALIGNMENT_LEFT
);
379 window_bounds
= widget
->GetWindowBoundsInScreen();
380 tray_bounds
= tray
->GetBoundsInScreen();
381 EXPECT_TRUE(window_bounds
.bottom() >= tray_bounds
.bottom());
382 EXPECT_TRUE(window_bounds
.right() >= tray_bounds
.right());
383 EXPECT_TRUE(window_bounds
.x() >= tray_bounds
.x());
384 EXPECT_TRUE(window_bounds
.y() >= tray_bounds
.y());
387 TEST_F(SystemTrayTest
, PersistentBubble
) {
388 SystemTray
* tray
= GetSystemTray();
389 ASSERT_TRUE(tray
->GetWidget());
391 TestItem
* test_item
= new TestItem
;
392 tray
->AddTrayItem(test_item
);
394 scoped_ptr
<aura::Window
> window(CreateTestWindowInShellWithId(0));
396 // Tests for usual default view.
397 // Activating window.
398 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
399 ASSERT_TRUE(tray
->HasSystemBubble());
400 wm::ActivateWindow(window
.get());
401 base::RunLoop().RunUntilIdle();
402 ASSERT_FALSE(tray
->HasSystemBubble());
404 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
405 ASSERT_TRUE(tray
->HasSystemBubble());
407 ui::test::EventGenerator
generator(Shell::GetPrimaryRootWindow(),
409 generator
.ClickLeftButton();
410 ASSERT_FALSE(tray
->HasSystemBubble());
413 // Same tests for persistent default view.
414 tray
->ShowPersistentDefaultView();
415 ASSERT_TRUE(tray
->HasSystemBubble());
416 wm::ActivateWindow(window
.get());
417 base::RunLoop().RunUntilIdle();
418 ASSERT_TRUE(tray
->HasSystemBubble());
421 ui::test::EventGenerator
generator(Shell::GetPrimaryRootWindow(),
423 generator
.ClickLeftButton();
424 ASSERT_TRUE(tray
->HasSystemBubble());
428 #if defined(OS_CHROMEOS)
429 // Accessibility/Settings tray items are available only on cros.
430 #define MAYBE_WithSystemModal WithSystemModal
432 #define MAYBE_WithSystemModal DISABLED_WithSystemModal
434 TEST_F(SystemTrayTest
, MAYBE_WithSystemModal
) {
435 // Check if the accessibility item is created even with system modal
437 Shell::GetInstance()->accessibility_delegate()->SetVirtualKeyboardEnabled(
439 views::Widget
* widget
= views::Widget::CreateWindowWithContextAndBounds(
440 new ModalWidgetDelegate(),
441 Shell::GetPrimaryRootWindow(),
442 gfx::Rect(0, 0, 100, 100));
445 SystemTray
* tray
= GetSystemTray();
446 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
448 ASSERT_TRUE(tray
->HasSystemBubble());
449 const views::View
* accessibility
=
450 tray
->GetSystemBubble()->bubble_view()->GetViewByID(
451 test::kAccessibilityTrayItemViewId
);
452 ASSERT_TRUE(accessibility
);
453 EXPECT_TRUE(accessibility
->visible());
454 EXPECT_FALSE(tray
->GetSystemBubble()->bubble_view()->GetViewByID(
455 test::kSettingsTrayItemViewId
));
459 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
460 // System modal is gone. The bubble should now contains settings
462 accessibility
= tray
->GetSystemBubble()->bubble_view()->GetViewByID(
463 test::kAccessibilityTrayItemViewId
);
464 ASSERT_TRUE(accessibility
);
465 EXPECT_TRUE(accessibility
->visible());
467 const views::View
* settings
=
468 tray
->GetSystemBubble()->bubble_view()->GetViewByID(
469 test::kSettingsTrayItemViewId
);
470 ASSERT_TRUE(settings
);
471 EXPECT_TRUE(settings
->visible());
474 // Tests that if SetVisible(true) is called while animating to hidden that the
475 // tray becomes visible, and stops animating to hidden.
476 TEST_F(SystemTrayTest
, SetVisibleDuringHideAnimation
) {
477 SystemTray
* tray
= GetSystemTray();
478 ASSERT_TRUE(tray
->visible());
480 scoped_ptr
<ui::ScopedAnimationDurationScaleMode
> animation_duration
;
481 animation_duration
.reset(
482 new ui::ScopedAnimationDurationScaleMode(
483 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
));
484 tray
->SetVisible(false);
485 EXPECT_TRUE(tray
->visible());
486 EXPECT_EQ(0.0f
, tray
->layer()->GetTargetOpacity());
488 tray
->SetVisible(true);
489 animation_duration
.reset();
490 tray
->layer()->GetAnimator()->StopAnimating();
491 EXPECT_TRUE(tray
->visible());
492 EXPECT_EQ(1.0f
, tray
->layer()->GetTargetOpacity());
495 #if defined(OS_CHROMEOS)
496 // Tests that touch on an item in the system bubble triggers it to become
498 TEST_F(SystemTrayTest
, TrayPopupItemContainerTouchFeedback
) {
499 SystemTray
* tray
= GetSystemTray();
500 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
502 TrayPopupItemContainer
* view
=
503 static_cast<TrayPopupItemContainer
*>(tray
->GetSystemBubble()->
504 bubble_view()->child_at(0));
505 EXPECT_FALSE(view
->active());
507 ui::test::EventGenerator
generator(Shell::GetPrimaryRootWindow());
508 generator
.set_current_location(view
->GetBoundsInScreen().CenterPoint());
509 generator
.PressTouch();
510 EXPECT_TRUE(view
->active());
512 generator
.ReleaseTouch();
513 EXPECT_FALSE(view
->active());
516 // Tests that touch events on an item in the system bubble cause it to stop
518 TEST_F(SystemTrayTest
, TrayPopupItemContainerTouchFeedbackCancellation
) {
519 SystemTray
* tray
= GetSystemTray();
520 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
522 TrayPopupItemContainer
* view
=
523 static_cast<TrayPopupItemContainer
*>(tray
->GetSystemBubble()->
524 bubble_view()->child_at(0));
525 EXPECT_FALSE(view
->active());
527 gfx::Rect view_bounds
= view
->GetBoundsInScreen();
528 ui::test::EventGenerator
generator(Shell::GetPrimaryRootWindow());
529 generator
.set_current_location(view_bounds
.CenterPoint());
530 generator
.PressTouch();
531 EXPECT_TRUE(view
->active());
533 gfx::Point
move_point(view_bounds
.x(), view_bounds
.CenterPoint().y());
534 generator
.MoveTouch(move_point
);
535 EXPECT_FALSE(view
->active());
537 generator
.set_current_location(move_point
);
538 generator
.ReleaseTouch();
539 EXPECT_FALSE(view
->active());
541 #endif // OS_CHROMEOS