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/command_line.h"
22 #include "base/run_loop.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "ui/aura/window.h"
25 #include "ui/base/ui_base_switches.h"
26 #include "ui/base/ui_base_types.h"
27 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
28 #include "ui/events/test/event_generator.h"
29 #include "ui/gfx/geometry/point.h"
30 #include "ui/gfx/geometry/rect.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/layout/fill_layout.h"
33 #include "ui/views/view.h"
34 #include "ui/views/widget/widget.h"
35 #include "ui/views/widget/widget_delegate.h"
38 #include "base/win/windows_version.h"
46 SystemTray* GetSystemTray() {
47 return Shell::GetPrimaryRootWindowController()->shelf()->
48 status_area_widget()->system_tray();
51 // Trivial item implementation that tracks its views for testing.
52 class TestItem : public SystemTrayItem {
54 TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL) {}
56 views::View* CreateTrayView(user::LoginStatus status) override {
57 tray_view_ = new views::View;
58 // Add a label so it has non-zero width.
59 tray_view_->SetLayoutManager(new views::FillLayout);
60 tray_view_->AddChildView(new views::Label(base::UTF8ToUTF16("Tray")));
64 views::View* CreateDefaultView(user::LoginStatus status) override {
65 default_view_ = new views::View;
66 default_view_->SetLayoutManager(new views::FillLayout);
67 default_view_->AddChildView(new views::Label(base::UTF8ToUTF16("Default")));
71 views::View* CreateDetailedView(user::LoginStatus status) override {
72 detailed_view_ = new views::View;
73 detailed_view_->SetLayoutManager(new views::FillLayout);
74 detailed_view_->AddChildView(
75 new views::Label(base::UTF8ToUTF16("Detailed")));
76 return detailed_view_;
79 views::View* CreateNotificationView(user::LoginStatus status) override {
80 notification_view_ = new views::View;
81 return notification_view_;
84 void DestroyTrayView() override { tray_view_ = NULL; }
86 void DestroyDefaultView() override { default_view_ = NULL; }
88 void DestroyDetailedView() override { detailed_view_ = NULL; }
90 void DestroyNotificationView() override { notification_view_ = NULL; }
92 void UpdateAfterLoginStatusChange(user::LoginStatus status) override {}
94 views::View* tray_view() const { return tray_view_; }
95 views::View* default_view() const { return default_view_; }
96 views::View* detailed_view() const { return detailed_view_; }
97 views::View* notification_view() const { return notification_view_; }
100 views::View* tray_view_;
101 views::View* default_view_;
102 views::View* detailed_view_;
103 views::View* notification_view_;
106 // Trivial item implementation that returns NULL from tray/default/detailed
107 // view creation methods.
108 class TestNoViewItem : public SystemTrayItem {
110 TestNoViewItem() : SystemTrayItem(GetSystemTray()) {}
112 views::View* CreateTrayView(user::LoginStatus status) override {
116 views::View* CreateDefaultView(user::LoginStatus status) override {
120 views::View* CreateDetailedView(user::LoginStatus status) override {
124 views::View* CreateNotificationView(user::LoginStatus status) override {
128 void DestroyTrayView() override {}
129 void DestroyDefaultView() override {}
130 void DestroyDetailedView() override {}
131 void DestroyNotificationView() override {}
132 void UpdateAfterLoginStatusChange(user::LoginStatus status) override {}
135 class ModalWidgetDelegate : public views::WidgetDelegateView {
137 ModalWidgetDelegate() {}
138 ~ModalWidgetDelegate() override {}
140 views::View* GetContentsView() override { return this; }
141 ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_SYSTEM; }
144 DISALLOW_COPY_AND_ASSIGN(ModalWidgetDelegate);
149 class SystemTrayTest : public AshTestBase {
152 ~SystemTrayTest() override {}
154 void SetUp() override {
155 base::CommandLine::ForCurrentProcess()->AppendSwitch(
156 switches::kEnableTouchFeedback);
157 test::AshTestBase::SetUp();
161 DISALLOW_COPY_AND_ASSIGN(SystemTrayTest);
164 TEST_F(SystemTrayTest, SystemTrayDefaultView) {
165 SystemTray* tray = GetSystemTray();
166 ASSERT_TRUE(tray->GetWidget());
168 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
170 // Ensure that closing the bubble destroys it.
171 ASSERT_TRUE(tray->CloseSystemBubble());
172 RunAllPendingInMessageLoop();
173 ASSERT_FALSE(tray->CloseSystemBubble());
176 // Opening and closing the bubble should change the coloring of the tray.
177 TEST_F(SystemTrayTest, SystemTrayColoring) {
178 SystemTray* tray = GetSystemTray();
179 ASSERT_TRUE(tray->GetWidget());
180 // At the beginning the tray coloring is not active.
181 ASSERT_FALSE(tray->draw_background_as_active());
183 // Showing the system bubble should show the background as active.
184 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
185 ASSERT_TRUE(tray->draw_background_as_active());
187 // Closing the system menu should change the coloring back to normal.
188 ASSERT_TRUE(tray->CloseSystemBubble());
189 RunAllPendingInMessageLoop();
190 ASSERT_FALSE(tray->draw_background_as_active());
193 // Closing the system bubble through an alignment change should change the
194 // system tray coloring back to normal.
195 TEST_F(SystemTrayTest, SystemTrayColoringAfterAlignmentChange) {
196 SystemTray* tray = GetSystemTray();
197 ASSERT_TRUE(tray->GetWidget());
198 ShelfLayoutManager* manager =
199 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
200 manager->SetAlignment(SHELF_ALIGNMENT_BOTTOM);
201 // At the beginning the tray coloring is not active.
202 ASSERT_FALSE(tray->draw_background_as_active());
204 // Showing the system bubble should show the background as active.
205 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
206 ASSERT_TRUE(tray->draw_background_as_active());
208 // Changing the alignment should close the system bubble and change the
210 manager->SetAlignment(SHELF_ALIGNMENT_LEFT);
211 ASSERT_FALSE(tray->draw_background_as_active());
212 RunAllPendingInMessageLoop();
213 // The bubble should already be closed by now.
214 ASSERT_FALSE(tray->CloseSystemBubble());
217 TEST_F(SystemTrayTest, SystemTrayTestItems) {
218 SystemTray* tray = GetSystemTray();
219 ASSERT_TRUE(tray->GetWidget());
221 TestItem* test_item = new TestItem;
222 TestItem* detailed_item = new TestItem;
223 tray->AddTrayItem(test_item);
224 tray->AddTrayItem(detailed_item);
226 // Check items have been added
227 const std::vector<SystemTrayItem*>& items = tray->GetTrayItems();
229 std::find(items.begin(), items.end(), test_item) != items.end());
231 std::find(items.begin(), items.end(), detailed_item) != items.end());
233 // Ensure the tray views are created.
234 ASSERT_TRUE(test_item->tray_view() != NULL);
235 ASSERT_TRUE(detailed_item->tray_view() != NULL);
237 // Ensure a default views are created.
238 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
239 ASSERT_TRUE(test_item->default_view() != NULL);
240 ASSERT_TRUE(detailed_item->default_view() != NULL);
242 // Show the detailed view, ensure it's created and the default view destroyed.
243 tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW);
244 RunAllPendingInMessageLoop();
245 ASSERT_TRUE(test_item->default_view() == NULL);
246 ASSERT_TRUE(detailed_item->detailed_view() != NULL);
248 // Show the default view, ensure it's created and the detailed view destroyed.
249 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
250 RunAllPendingInMessageLoop();
251 ASSERT_TRUE(test_item->default_view() != NULL);
252 ASSERT_TRUE(detailed_item->detailed_view() == NULL);
255 TEST_F(SystemTrayTest, SystemTrayNoViewItems) {
256 SystemTray* tray = GetSystemTray();
257 ASSERT_TRUE(tray->GetWidget());
259 // Verify that no crashes occur on items lacking some views.
260 TestNoViewItem* no_view_item = new TestNoViewItem;
261 tray->AddTrayItem(no_view_item);
262 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
263 tray->ShowDetailedView(no_view_item, 0, false, BUBBLE_USE_EXISTING);
264 RunAllPendingInMessageLoop();
267 TEST_F(SystemTrayTest, TrayWidgetAutoResizes) {
268 SystemTray* tray = GetSystemTray();
269 ASSERT_TRUE(tray->GetWidget());
271 // Add an initial tray item so that the tray gets laid out correctly.
272 TestItem* initial_item = new TestItem;
273 tray->AddTrayItem(initial_item);
275 gfx::Size initial_size = tray->GetWidget()->GetWindowBoundsInScreen().size();
277 TestItem* new_item = new TestItem;
278 tray->AddTrayItem(new_item);
280 gfx::Size new_size = tray->GetWidget()->GetWindowBoundsInScreen().size();
282 // Adding the new item should change the size of the tray.
283 EXPECT_NE(initial_size.ToString(), new_size.ToString());
285 // Hiding the tray view of the new item should also change the size of the
287 new_item->tray_view()->SetVisible(false);
288 EXPECT_EQ(initial_size.ToString(),
289 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString());
291 new_item->tray_view()->SetVisible(true);
292 EXPECT_EQ(new_size.ToString(),
293 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString());
296 TEST_F(SystemTrayTest, SystemTrayNotifications) {
297 SystemTray* tray = GetSystemTray();
298 ASSERT_TRUE(tray->GetWidget());
300 TestItem* test_item = new TestItem;
301 TestItem* detailed_item = new TestItem;
302 tray->AddTrayItem(test_item);
303 tray->AddTrayItem(detailed_item);
305 // Ensure the tray views are created.
306 ASSERT_TRUE(test_item->tray_view() != NULL);
307 ASSERT_TRUE(detailed_item->tray_view() != NULL);
309 // Ensure a notification view is created.
310 tray->ShowNotificationView(test_item);
311 ASSERT_TRUE(test_item->notification_view() != NULL);
313 // Show the default view, notification view should remain.
314 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
315 RunAllPendingInMessageLoop();
316 ASSERT_TRUE(test_item->notification_view() != NULL);
318 // Show the detailed view, ensure the notification view remains.
319 tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW);
320 RunAllPendingInMessageLoop();
321 ASSERT_TRUE(detailed_item->detailed_view() != NULL);
322 ASSERT_TRUE(test_item->notification_view() != NULL);
324 // Hide the detailed view, ensure the notification view still exists.
325 ASSERT_TRUE(tray->CloseSystemBubble());
326 RunAllPendingInMessageLoop();
327 ASSERT_TRUE(detailed_item->detailed_view() == NULL);
328 ASSERT_TRUE(test_item->notification_view() != NULL);
331 TEST_F(SystemTrayTest, BubbleCreationTypesTest) {
332 SystemTray* tray = GetSystemTray();
333 ASSERT_TRUE(tray->GetWidget());
335 TestItem* test_item = new TestItem;
336 tray->AddTrayItem(test_item);
338 // Ensure the tray views are created.
339 ASSERT_TRUE(test_item->tray_view() != NULL);
341 // Show the default view, ensure the notification view is destroyed.
342 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
343 RunAllPendingInMessageLoop();
345 views::Widget* widget = test_item->default_view()->GetWidget();
346 gfx::Rect bubble_bounds = widget->GetWindowBoundsInScreen();
348 tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING);
349 RunAllPendingInMessageLoop();
351 EXPECT_FALSE(test_item->default_view());
353 EXPECT_EQ(bubble_bounds.ToString(), test_item->detailed_view()->GetWidget()->
354 GetWindowBoundsInScreen().ToString());
355 EXPECT_EQ(widget, test_item->detailed_view()->GetWidget());
357 tray->ShowDefaultView(BUBBLE_USE_EXISTING);
358 RunAllPendingInMessageLoop();
360 EXPECT_EQ(bubble_bounds.ToString(), test_item->default_view()->GetWidget()->
361 GetWindowBoundsInScreen().ToString());
362 EXPECT_EQ(widget, test_item->default_view()->GetWidget());
365 // Tests that the tray is laid out properly and is fully contained within
367 TEST_F(SystemTrayTest, TrayBoundsInWidget) {
368 ShelfLayoutManager* manager =
369 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
370 StatusAreaWidget* widget =
371 Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
372 SystemTray* tray = widget->system_tray();
374 // Test in bottom alignment.
375 manager->SetAlignment(SHELF_ALIGNMENT_BOTTOM);
376 gfx::Rect window_bounds = widget->GetWindowBoundsInScreen();
377 gfx::Rect tray_bounds = tray->GetBoundsInScreen();
378 EXPECT_TRUE(window_bounds.bottom() >= tray_bounds.bottom());
379 EXPECT_TRUE(window_bounds.right() >= tray_bounds.right());
380 EXPECT_TRUE(window_bounds.x() >= tray_bounds.x());
381 EXPECT_TRUE(window_bounds.y() >= tray_bounds.y());
383 // Test in the left alignment.
384 manager->SetAlignment(SHELF_ALIGNMENT_LEFT);
385 window_bounds = widget->GetWindowBoundsInScreen();
386 tray_bounds = tray->GetBoundsInScreen();
387 EXPECT_TRUE(window_bounds.bottom() >= tray_bounds.bottom());
388 EXPECT_TRUE(window_bounds.right() >= tray_bounds.right());
389 EXPECT_TRUE(window_bounds.x() >= tray_bounds.x());
390 EXPECT_TRUE(window_bounds.y() >= tray_bounds.y());
392 // Test in the right alignment.
393 manager->SetAlignment(SHELF_ALIGNMENT_LEFT);
394 window_bounds = widget->GetWindowBoundsInScreen();
395 tray_bounds = tray->GetBoundsInScreen();
396 EXPECT_TRUE(window_bounds.bottom() >= tray_bounds.bottom());
397 EXPECT_TRUE(window_bounds.right() >= tray_bounds.right());
398 EXPECT_TRUE(window_bounds.x() >= tray_bounds.x());
399 EXPECT_TRUE(window_bounds.y() >= tray_bounds.y());
402 TEST_F(SystemTrayTest, PersistentBubble) {
403 SystemTray* tray = GetSystemTray();
404 ASSERT_TRUE(tray->GetWidget());
406 TestItem* test_item = new TestItem;
407 tray->AddTrayItem(test_item);
409 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
411 // Tests for usual default view.
412 // Activating window.
413 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
414 ASSERT_TRUE(tray->HasSystemBubble());
415 wm::ActivateWindow(window.get());
416 base::RunLoop().RunUntilIdle();
417 ASSERT_FALSE(tray->HasSystemBubble());
419 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
420 ASSERT_TRUE(tray->HasSystemBubble());
422 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
424 generator.ClickLeftButton();
425 ASSERT_FALSE(tray->HasSystemBubble());
428 // Same tests for persistent default view.
429 tray->ShowPersistentDefaultView();
430 ASSERT_TRUE(tray->HasSystemBubble());
431 wm::ActivateWindow(window.get());
432 base::RunLoop().RunUntilIdle();
433 ASSERT_TRUE(tray->HasSystemBubble());
436 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
438 generator.ClickLeftButton();
439 ASSERT_TRUE(tray->HasSystemBubble());
443 #if defined(OS_CHROMEOS)
444 // Accessibility/Settings tray items are available only on cros.
445 #define MAYBE_WithSystemModal WithSystemModal
447 #define MAYBE_WithSystemModal DISABLED_WithSystemModal
449 TEST_F(SystemTrayTest, MAYBE_WithSystemModal) {
450 // Check if the accessibility item is created even with system modal
452 Shell::GetInstance()->accessibility_delegate()->SetVirtualKeyboardEnabled(
454 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
455 new ModalWidgetDelegate(),
456 Shell::GetPrimaryRootWindow(),
457 gfx::Rect(0, 0, 100, 100));
460 SystemTray* tray = GetSystemTray();
461 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
463 ASSERT_TRUE(tray->HasSystemBubble());
464 const views::View* accessibility =
465 tray->GetSystemBubble()->bubble_view()->GetViewByID(
466 test::kAccessibilityTrayItemViewId);
467 ASSERT_TRUE(accessibility);
468 EXPECT_TRUE(accessibility->visible());
469 EXPECT_FALSE(tray->GetSystemBubble()->bubble_view()->GetViewByID(
470 test::kSettingsTrayItemViewId));
474 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
475 // System modal is gone. The bubble should now contains settings
477 accessibility = tray->GetSystemBubble()->bubble_view()->GetViewByID(
478 test::kAccessibilityTrayItemViewId);
479 ASSERT_TRUE(accessibility);
480 EXPECT_TRUE(accessibility->visible());
482 const views::View* settings =
483 tray->GetSystemBubble()->bubble_view()->GetViewByID(
484 test::kSettingsTrayItemViewId);
485 ASSERT_TRUE(settings);
486 EXPECT_TRUE(settings->visible());
489 // Tests that if SetVisible(true) is called while animating to hidden that the
490 // tray becomes visible, and stops animating to hidden.
491 TEST_F(SystemTrayTest, SetVisibleDuringHideAnimation) {
492 SystemTray* tray = GetSystemTray();
493 ASSERT_TRUE(tray->visible());
495 scoped_ptr<ui::ScopedAnimationDurationScaleMode> animation_duration;
496 animation_duration.reset(
497 new ui::ScopedAnimationDurationScaleMode(
498 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
499 tray->SetVisible(false);
500 EXPECT_TRUE(tray->visible());
501 EXPECT_EQ(0.0f, tray->layer()->GetTargetOpacity());
503 tray->SetVisible(true);
504 animation_duration.reset();
505 tray->layer()->GetAnimator()->StopAnimating();
506 EXPECT_TRUE(tray->visible());
507 EXPECT_EQ(1.0f, tray->layer()->GetTargetOpacity());
510 #if defined(OS_CHROMEOS)
511 // Tests that touch on an item in the system bubble triggers it to become
513 TEST_F(SystemTrayTest, TrayPopupItemContainerTouchFeedback) {
514 SystemTray* tray = GetSystemTray();
515 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
517 TrayPopupItemContainer* view =
518 static_cast<TrayPopupItemContainer*>(tray->GetSystemBubble()->
519 bubble_view()->child_at(0));
520 EXPECT_FALSE(view->active());
522 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
523 generator.set_current_location(view->GetBoundsInScreen().CenterPoint());
524 generator.PressTouch();
525 RunAllPendingInMessageLoop();
526 EXPECT_TRUE(view->active());
528 generator.ReleaseTouch();
529 RunAllPendingInMessageLoop();
530 EXPECT_FALSE(view->active());
533 // Tests that touch events on an item in the system bubble cause it to stop
535 TEST_F(SystemTrayTest, TrayPopupItemContainerTouchFeedbackCancellation) {
536 SystemTray* tray = GetSystemTray();
537 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
539 TrayPopupItemContainer* view =
540 static_cast<TrayPopupItemContainer*>(tray->GetSystemBubble()->
541 bubble_view()->child_at(0));
542 EXPECT_FALSE(view->active());
544 gfx::Rect view_bounds = view->GetBoundsInScreen();
545 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
546 generator.set_current_location(view_bounds.CenterPoint());
547 generator.PressTouch();
548 RunAllPendingInMessageLoop();
549 EXPECT_TRUE(view->active());
551 gfx::Point move_point(view_bounds.x(), view_bounds.CenterPoint().y());
552 generator.MoveTouch(move_point);
553 RunAllPendingInMessageLoop();
554 EXPECT_FALSE(view->active());
556 generator.set_current_location(move_point);
557 generator.ReleaseTouch();
558 RunAllPendingInMessageLoop();
559 EXPECT_FALSE(view->active());
561 #endif // OS_CHROMEOS