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_item.h"
16 #include "ash/system/tray/tray_constants.h"
17 #include "ash/test/ash_test_base.h"
18 #include "ash/wm/window_util.h"
19 #include "base/run_loop.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "ui/aura/window.h"
22 #include "ui/base/ui_base_types.h"
23 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
24 #include "ui/events/test/event_generator.h"
25 #include "ui/gfx/geometry/rect.h"
26 #include "ui/views/controls/label.h"
27 #include "ui/views/layout/fill_layout.h"
28 #include "ui/views/view.h"
29 #include "ui/views/widget/widget.h"
30 #include "ui/views/widget/widget_delegate.h"
33 #include "base/win/windows_version.h"
41 SystemTray
* GetSystemTray() {
42 return Shell::GetPrimaryRootWindowController()->shelf()->
43 status_area_widget()->system_tray();
46 // Trivial item implementation that tracks its views for testing.
47 class TestItem
: public SystemTrayItem
{
49 TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL
) {}
51 virtual views::View
* CreateTrayView(user::LoginStatus status
) OVERRIDE
{
52 tray_view_
= new views::View
;
53 // Add a label so it has non-zero width.
54 tray_view_
->SetLayoutManager(new views::FillLayout
);
55 tray_view_
->AddChildView(new views::Label(base::UTF8ToUTF16("Tray")));
59 virtual views::View
* CreateDefaultView(user::LoginStatus status
) OVERRIDE
{
60 default_view_
= new views::View
;
61 default_view_
->SetLayoutManager(new views::FillLayout
);
62 default_view_
->AddChildView(new views::Label(base::UTF8ToUTF16("Default")));
66 virtual views::View
* CreateDetailedView(user::LoginStatus status
) OVERRIDE
{
67 detailed_view_
= new views::View
;
68 detailed_view_
->SetLayoutManager(new views::FillLayout
);
69 detailed_view_
->AddChildView(
70 new views::Label(base::UTF8ToUTF16("Detailed")));
71 return detailed_view_
;
74 virtual views::View
* CreateNotificationView(
75 user::LoginStatus status
) OVERRIDE
{
76 notification_view_
= new views::View
;
77 return notification_view_
;
80 virtual void DestroyTrayView() OVERRIDE
{
84 virtual void DestroyDefaultView() OVERRIDE
{
88 virtual void DestroyDetailedView() OVERRIDE
{
89 detailed_view_
= NULL
;
92 virtual void DestroyNotificationView() OVERRIDE
{
93 notification_view_
= NULL
;
96 virtual void UpdateAfterLoginStatusChange(
97 user::LoginStatus status
) OVERRIDE
{
100 views::View
* tray_view() const { return tray_view_
; }
101 views::View
* default_view() const { return default_view_
; }
102 views::View
* detailed_view() const { return detailed_view_
; }
103 views::View
* notification_view() const { return notification_view_
; }
106 views::View
* tray_view_
;
107 views::View
* default_view_
;
108 views::View
* detailed_view_
;
109 views::View
* notification_view_
;
112 // Trivial item implementation that returns NULL from tray/default/detailed
113 // view creation methods.
114 class TestNoViewItem
: public SystemTrayItem
{
116 TestNoViewItem() : SystemTrayItem(GetSystemTray()) {}
118 virtual views::View
* CreateTrayView(user::LoginStatus status
) OVERRIDE
{
122 virtual views::View
* CreateDefaultView(user::LoginStatus status
) OVERRIDE
{
126 virtual views::View
* CreateDetailedView(user::LoginStatus status
) OVERRIDE
{
130 virtual views::View
* CreateNotificationView(
131 user::LoginStatus status
) OVERRIDE
{
135 virtual void DestroyTrayView() OVERRIDE
{}
136 virtual void DestroyDefaultView() OVERRIDE
{}
137 virtual void DestroyDetailedView() OVERRIDE
{}
138 virtual void DestroyNotificationView() OVERRIDE
{}
139 virtual void UpdateAfterLoginStatusChange(
140 user::LoginStatus status
) OVERRIDE
{
144 class ModalWidgetDelegate
: public views::WidgetDelegateView
{
146 ModalWidgetDelegate() {}
147 virtual ~ModalWidgetDelegate() {}
149 virtual views::View
* GetContentsView() OVERRIDE
{ return this; }
150 virtual ui::ModalType
GetModalType() const OVERRIDE
{
151 return ui::MODAL_TYPE_SYSTEM
;
155 DISALLOW_COPY_AND_ASSIGN(ModalWidgetDelegate
);
160 typedef AshTestBase SystemTrayTest
;
162 TEST_F(SystemTrayTest
, SystemTrayDefaultView
) {
163 SystemTray
* tray
= GetSystemTray();
164 ASSERT_TRUE(tray
->GetWidget());
166 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
168 // Ensure that closing the bubble destroys it.
169 ASSERT_TRUE(tray
->CloseSystemBubble());
170 RunAllPendingInMessageLoop();
171 ASSERT_FALSE(tray
->CloseSystemBubble());
174 // Opening and closing the bubble should change the coloring of the tray.
175 TEST_F(SystemTrayTest
, SystemTrayColoring
) {
176 SystemTray
* tray
= GetSystemTray();
177 ASSERT_TRUE(tray
->GetWidget());
178 // At the beginning the tray coloring is not active.
179 ASSERT_FALSE(tray
->draw_background_as_active());
181 // Showing the system bubble should show the background as active.
182 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
183 ASSERT_TRUE(tray
->draw_background_as_active());
185 // Closing the system menu should change the coloring back to normal.
186 ASSERT_TRUE(tray
->CloseSystemBubble());
187 RunAllPendingInMessageLoop();
188 ASSERT_FALSE(tray
->draw_background_as_active());
191 // Closing the system bubble through an alignment change should change the
192 // system tray coloring back to normal.
193 TEST_F(SystemTrayTest
, SystemTrayColoringAfterAlignmentChange
) {
194 SystemTray
* tray
= GetSystemTray();
195 ASSERT_TRUE(tray
->GetWidget());
196 ShelfLayoutManager
* manager
=
197 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
198 manager
->SetAlignment(SHELF_ALIGNMENT_BOTTOM
);
199 // At the beginning the tray coloring is not active.
200 ASSERT_FALSE(tray
->draw_background_as_active());
202 // Showing the system bubble should show the background as active.
203 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
204 ASSERT_TRUE(tray
->draw_background_as_active());
206 // Changing the alignment should close the system bubble and change the
208 manager
->SetAlignment(SHELF_ALIGNMENT_LEFT
);
209 ASSERT_FALSE(tray
->draw_background_as_active());
210 RunAllPendingInMessageLoop();
211 // The bubble should already be closed by now.
212 ASSERT_FALSE(tray
->CloseSystemBubble());
215 TEST_F(SystemTrayTest
, SystemTrayTestItems
) {
216 SystemTray
* tray
= GetSystemTray();
217 ASSERT_TRUE(tray
->GetWidget());
219 TestItem
* test_item
= new TestItem
;
220 TestItem
* detailed_item
= new TestItem
;
221 tray
->AddTrayItem(test_item
);
222 tray
->AddTrayItem(detailed_item
);
224 // Check items have been added
225 const std::vector
<SystemTrayItem
*>& items
= tray
->GetTrayItems();
227 std::find(items
.begin(), items
.end(), test_item
) != items
.end());
229 std::find(items
.begin(), items
.end(), detailed_item
) != items
.end());
231 // Ensure the tray views are created.
232 ASSERT_TRUE(test_item
->tray_view() != NULL
);
233 ASSERT_TRUE(detailed_item
->tray_view() != NULL
);
235 // Ensure a default views are created.
236 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
237 ASSERT_TRUE(test_item
->default_view() != NULL
);
238 ASSERT_TRUE(detailed_item
->default_view() != NULL
);
240 // Show the detailed view, ensure it's created and the default view destroyed.
241 tray
->ShowDetailedView(detailed_item
, 0, false, BUBBLE_CREATE_NEW
);
242 RunAllPendingInMessageLoop();
243 ASSERT_TRUE(test_item
->default_view() == NULL
);
244 ASSERT_TRUE(detailed_item
->detailed_view() != NULL
);
246 // Show the default view, ensure it's created and the detailed view destroyed.
247 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
248 RunAllPendingInMessageLoop();
249 ASSERT_TRUE(test_item
->default_view() != NULL
);
250 ASSERT_TRUE(detailed_item
->detailed_view() == NULL
);
253 TEST_F(SystemTrayTest
, SystemTrayNoViewItems
) {
254 SystemTray
* tray
= GetSystemTray();
255 ASSERT_TRUE(tray
->GetWidget());
257 // Verify that no crashes occur on items lacking some views.
258 TestNoViewItem
* no_view_item
= new TestNoViewItem
;
259 tray
->AddTrayItem(no_view_item
);
260 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
261 tray
->ShowDetailedView(no_view_item
, 0, false, BUBBLE_USE_EXISTING
);
262 RunAllPendingInMessageLoop();
265 TEST_F(SystemTrayTest
, TrayWidgetAutoResizes
) {
266 SystemTray
* tray
= GetSystemTray();
267 ASSERT_TRUE(tray
->GetWidget());
269 // Add an initial tray item so that the tray gets laid out correctly.
270 TestItem
* initial_item
= new TestItem
;
271 tray
->AddTrayItem(initial_item
);
273 gfx::Size initial_size
= tray
->GetWidget()->GetWindowBoundsInScreen().size();
275 TestItem
* new_item
= new TestItem
;
276 tray
->AddTrayItem(new_item
);
278 gfx::Size new_size
= tray
->GetWidget()->GetWindowBoundsInScreen().size();
280 // Adding the new item should change the size of the tray.
281 EXPECT_NE(initial_size
.ToString(), new_size
.ToString());
283 // Hiding the tray view of the new item should also change the size of the
285 new_item
->tray_view()->SetVisible(false);
286 EXPECT_EQ(initial_size
.ToString(),
287 tray
->GetWidget()->GetWindowBoundsInScreen().size().ToString());
289 new_item
->tray_view()->SetVisible(true);
290 EXPECT_EQ(new_size
.ToString(),
291 tray
->GetWidget()->GetWindowBoundsInScreen().size().ToString());
294 TEST_F(SystemTrayTest
, SystemTrayNotifications
) {
295 SystemTray
* tray
= GetSystemTray();
296 ASSERT_TRUE(tray
->GetWidget());
298 TestItem
* test_item
= new TestItem
;
299 TestItem
* detailed_item
= new TestItem
;
300 tray
->AddTrayItem(test_item
);
301 tray
->AddTrayItem(detailed_item
);
303 // Ensure the tray views are created.
304 ASSERT_TRUE(test_item
->tray_view() != NULL
);
305 ASSERT_TRUE(detailed_item
->tray_view() != NULL
);
307 // Ensure a notification view is created.
308 tray
->ShowNotificationView(test_item
);
309 ASSERT_TRUE(test_item
->notification_view() != NULL
);
311 // Show the default view, notification view should remain.
312 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
313 RunAllPendingInMessageLoop();
314 ASSERT_TRUE(test_item
->notification_view() != NULL
);
316 // Show the detailed view, ensure the notification view remains.
317 tray
->ShowDetailedView(detailed_item
, 0, false, BUBBLE_CREATE_NEW
);
318 RunAllPendingInMessageLoop();
319 ASSERT_TRUE(detailed_item
->detailed_view() != NULL
);
320 ASSERT_TRUE(test_item
->notification_view() != NULL
);
322 // Hide the detailed view, ensure the notification view still exists.
323 ASSERT_TRUE(tray
->CloseSystemBubble());
324 RunAllPendingInMessageLoop();
325 ASSERT_TRUE(detailed_item
->detailed_view() == NULL
);
326 ASSERT_TRUE(test_item
->notification_view() != NULL
);
329 TEST_F(SystemTrayTest
, BubbleCreationTypesTest
) {
330 SystemTray
* tray
= GetSystemTray();
331 ASSERT_TRUE(tray
->GetWidget());
333 TestItem
* test_item
= new TestItem
;
334 tray
->AddTrayItem(test_item
);
336 // Ensure the tray views are created.
337 ASSERT_TRUE(test_item
->tray_view() != NULL
);
339 // Show the default view, ensure the notification view is destroyed.
340 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
341 RunAllPendingInMessageLoop();
343 views::Widget
* widget
= test_item
->default_view()->GetWidget();
344 gfx::Rect bubble_bounds
= widget
->GetWindowBoundsInScreen();
346 tray
->ShowDetailedView(test_item
, 0, true, BUBBLE_USE_EXISTING
);
347 RunAllPendingInMessageLoop();
349 EXPECT_FALSE(test_item
->default_view());
351 EXPECT_EQ(bubble_bounds
.ToString(), test_item
->detailed_view()->GetWidget()->
352 GetWindowBoundsInScreen().ToString());
353 EXPECT_EQ(widget
, test_item
->detailed_view()->GetWidget());
355 tray
->ShowDefaultView(BUBBLE_USE_EXISTING
);
356 RunAllPendingInMessageLoop();
358 EXPECT_EQ(bubble_bounds
.ToString(), test_item
->default_view()->GetWidget()->
359 GetWindowBoundsInScreen().ToString());
360 EXPECT_EQ(widget
, test_item
->default_view()->GetWidget());
363 // Tests that the tray is laid out properly and is fully contained within
365 TEST_F(SystemTrayTest
, TrayBoundsInWidget
) {
366 ShelfLayoutManager
* manager
=
367 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
368 StatusAreaWidget
* widget
=
369 Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
370 SystemTray
* tray
= widget
->system_tray();
372 // Test in bottom alignment.
373 manager
->SetAlignment(SHELF_ALIGNMENT_BOTTOM
);
374 gfx::Rect window_bounds
= widget
->GetWindowBoundsInScreen();
375 gfx::Rect tray_bounds
= tray
->GetBoundsInScreen();
376 EXPECT_TRUE(window_bounds
.bottom() >= tray_bounds
.bottom());
377 EXPECT_TRUE(window_bounds
.right() >= tray_bounds
.right());
378 EXPECT_TRUE(window_bounds
.x() >= tray_bounds
.x());
379 EXPECT_TRUE(window_bounds
.y() >= tray_bounds
.y());
381 // Test in the left alignment.
382 manager
->SetAlignment(SHELF_ALIGNMENT_LEFT
);
383 window_bounds
= widget
->GetWindowBoundsInScreen();
384 tray_bounds
= tray
->GetBoundsInScreen();
385 EXPECT_TRUE(window_bounds
.bottom() >= tray_bounds
.bottom());
386 EXPECT_TRUE(window_bounds
.right() >= tray_bounds
.right());
387 EXPECT_TRUE(window_bounds
.x() >= tray_bounds
.x());
388 EXPECT_TRUE(window_bounds
.y() >= tray_bounds
.y());
390 // Test in the right alignment.
391 manager
->SetAlignment(SHELF_ALIGNMENT_LEFT
);
392 window_bounds
= widget
->GetWindowBoundsInScreen();
393 tray_bounds
= tray
->GetBoundsInScreen();
394 EXPECT_TRUE(window_bounds
.bottom() >= tray_bounds
.bottom());
395 EXPECT_TRUE(window_bounds
.right() >= tray_bounds
.right());
396 EXPECT_TRUE(window_bounds
.x() >= tray_bounds
.x());
397 EXPECT_TRUE(window_bounds
.y() >= tray_bounds
.y());
400 TEST_F(SystemTrayTest
, PersistentBubble
) {
401 SystemTray
* tray
= GetSystemTray();
402 ASSERT_TRUE(tray
->GetWidget());
404 TestItem
* test_item
= new TestItem
;
405 tray
->AddTrayItem(test_item
);
407 scoped_ptr
<aura::Window
> window(CreateTestWindowInShellWithId(0));
409 // Tests for usual default view.
410 // Activating window.
411 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
412 ASSERT_TRUE(tray
->HasSystemBubble());
413 wm::ActivateWindow(window
.get());
414 base::RunLoop().RunUntilIdle();
415 ASSERT_FALSE(tray
->HasSystemBubble());
417 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
418 ASSERT_TRUE(tray
->HasSystemBubble());
420 ui::test::EventGenerator
generator(Shell::GetPrimaryRootWindow(),
422 generator
.ClickLeftButton();
423 ASSERT_FALSE(tray
->HasSystemBubble());
426 // Same tests for persistent default view.
427 tray
->ShowPersistentDefaultView();
428 ASSERT_TRUE(tray
->HasSystemBubble());
429 wm::ActivateWindow(window
.get());
430 base::RunLoop().RunUntilIdle();
431 ASSERT_TRUE(tray
->HasSystemBubble());
434 ui::test::EventGenerator
generator(Shell::GetPrimaryRootWindow(),
436 generator
.ClickLeftButton();
437 ASSERT_TRUE(tray
->HasSystemBubble());
441 #if defined(OS_CHROMEOS)
442 // Accessibility/Settings tray items are available only on cros.
443 #define MAYBE_WithSystemModal WithSystemModal
445 #define MAYBE_WithSystemModal DISABLED_WithSystemModal
447 TEST_F(SystemTrayTest
, MAYBE_WithSystemModal
) {
448 // Check if the accessibility item is created even with system modal
450 Shell::GetInstance()->accessibility_delegate()->SetVirtualKeyboardEnabled(
452 views::Widget
* widget
= views::Widget::CreateWindowWithContextAndBounds(
453 new ModalWidgetDelegate(),
454 Shell::GetPrimaryRootWindow(),
455 gfx::Rect(0, 0, 100, 100));
458 SystemTray
* tray
= GetSystemTray();
459 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
461 ASSERT_TRUE(tray
->HasSystemBubble());
462 const views::View
* accessibility
=
463 tray
->GetSystemBubble()->bubble_view()->GetViewByID(
464 test::kAccessibilityTrayItemViewId
);
465 ASSERT_TRUE(accessibility
);
466 EXPECT_TRUE(accessibility
->visible());
467 EXPECT_FALSE(tray
->GetSystemBubble()->bubble_view()->GetViewByID(
468 test::kSettingsTrayItemViewId
));
472 tray
->ShowDefaultView(BUBBLE_CREATE_NEW
);
473 // System modal is gone. The bubble should now contains settings
475 accessibility
= tray
->GetSystemBubble()->bubble_view()->GetViewByID(
476 test::kAccessibilityTrayItemViewId
);
477 ASSERT_TRUE(accessibility
);
478 EXPECT_TRUE(accessibility
->visible());
480 const views::View
* settings
=
481 tray
->GetSystemBubble()->bubble_view()->GetViewByID(
482 test::kSettingsTrayItemViewId
);
483 ASSERT_TRUE(settings
);
484 EXPECT_TRUE(settings
->visible());
487 // Tests that if SetVisible(true) is called while animating to hidden that the
488 // tray becomes visible, and stops animating to hidden.
489 TEST_F(SystemTrayTest
, SetVisibleDuringHideAnimation
) {
490 SystemTray
* tray
= GetSystemTray();
491 ASSERT_TRUE(tray
->visible());
493 scoped_ptr
<ui::ScopedAnimationDurationScaleMode
> animation_duration
;
494 animation_duration
.reset(
495 new ui::ScopedAnimationDurationScaleMode(
496 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
));
497 tray
->SetVisible(false);
498 EXPECT_TRUE(tray
->visible());
499 EXPECT_EQ(0.0f
, tray
->layer()->GetTargetOpacity());
501 tray
->SetVisible(true);
502 animation_duration
.reset();
503 tray
->layer()->GetAnimator()->StopAnimating();
504 EXPECT_TRUE(tray
->visible());
505 EXPECT_EQ(1.0f
, tray
->layer()->GetTargetOpacity());