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.
4
5 #include "ash/system/tray/system_tray.h"
6
7 #include <vector>
8
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"
31
32 #if defined(OS_WIN)
33 #include "base/win/windows_version.h"
34 #endif
35
36 namespace ash {
37 namespace test {
38
39 namespace {
40
GetSystemTray()41 SystemTray* GetSystemTray() {
42 return Shell::GetPrimaryRootWindowController()->shelf()->
43 status_area_widget()->system_tray();
44 }
45
46 // Trivial item implementation that tracks its views for testing.
47 class TestItem : public SystemTrayItem {
48 public:
TestItem()49 TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL) {}
50
CreateTrayView(user::LoginStatus status)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")));
56 return tray_view_;
57 }
58
CreateDefaultView(user::LoginStatus status)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")));
63 return default_view_;
64 }
65
CreateDetailedView(user::LoginStatus status)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_;
72 }
73
CreateNotificationView(user::LoginStatus status)74 virtual views::View* CreateNotificationView(
75 user::LoginStatus status) OVERRIDE {
76 notification_view_ = new views::View;
77 return notification_view_;
78 }
79
DestroyTrayView()80 virtual void DestroyTrayView() OVERRIDE {
81 tray_view_ = NULL;
82 }
83
DestroyDefaultView()84 virtual void DestroyDefaultView() OVERRIDE {
85 default_view_ = NULL;
86 }
87
DestroyDetailedView()88 virtual void DestroyDetailedView() OVERRIDE {
89 detailed_view_ = NULL;
90 }
91
DestroyNotificationView()92 virtual void DestroyNotificationView() OVERRIDE {
93 notification_view_ = NULL;
94 }
95
UpdateAfterLoginStatusChange(user::LoginStatus status)96 virtual void UpdateAfterLoginStatusChange(
97 user::LoginStatus status) OVERRIDE {
98 }
99
tray_view() const100 views::View* tray_view() const { return tray_view_; }
default_view() const101 views::View* default_view() const { return default_view_; }
detailed_view() const102 views::View* detailed_view() const { return detailed_view_; }
notification_view() const103 views::View* notification_view() const { return notification_view_; }
104
105 private:
106 views::View* tray_view_;
107 views::View* default_view_;
108 views::View* detailed_view_;
109 views::View* notification_view_;
110 };
111
112 // Trivial item implementation that returns NULL from tray/default/detailed
113 // view creation methods.
114 class TestNoViewItem : public SystemTrayItem {
115 public:
TestNoViewItem()116 TestNoViewItem() : SystemTrayItem(GetSystemTray()) {}
117
CreateTrayView(user::LoginStatus status)118 virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE {
119 return NULL;
120 }
121
CreateDefaultView(user::LoginStatus status)122 virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
123 return NULL;
124 }
125
CreateDetailedView(user::LoginStatus status)126 virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE {
127 return NULL;
128 }
129
CreateNotificationView(user::LoginStatus status)130 virtual views::View* CreateNotificationView(
131 user::LoginStatus status) OVERRIDE {
132 return NULL;
133 }
134
DestroyTrayView()135 virtual void DestroyTrayView() OVERRIDE {}
DestroyDefaultView()136 virtual void DestroyDefaultView() OVERRIDE {}
DestroyDetailedView()137 virtual void DestroyDetailedView() OVERRIDE {}
DestroyNotificationView()138 virtual void DestroyNotificationView() OVERRIDE {}
UpdateAfterLoginStatusChange(user::LoginStatus status)139 virtual void UpdateAfterLoginStatusChange(
140 user::LoginStatus status) OVERRIDE {
141 }
142 };
143
144 class ModalWidgetDelegate : public views::WidgetDelegateView {
145 public:
ModalWidgetDelegate()146 ModalWidgetDelegate() {}
~ModalWidgetDelegate()147 virtual ~ModalWidgetDelegate() {}
148
GetContentsView()149 virtual views::View* GetContentsView() OVERRIDE { return this; }
GetModalType() const150 virtual ui::ModalType GetModalType() const OVERRIDE {
151 return ui::MODAL_TYPE_SYSTEM;
152 }
153
154 private:
155 DISALLOW_COPY_AND_ASSIGN(ModalWidgetDelegate);
156 };
157
158 } // namespace
159
160 typedef AshTestBase SystemTrayTest;
161
TEST_F(SystemTrayTest,SystemTrayDefaultView)162 TEST_F(SystemTrayTest, SystemTrayDefaultView) {
163 SystemTray* tray = GetSystemTray();
164 ASSERT_TRUE(tray->GetWidget());
165
166 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
167
168 // Ensure that closing the bubble destroys it.
169 ASSERT_TRUE(tray->CloseSystemBubble());
170 RunAllPendingInMessageLoop();
171 ASSERT_FALSE(tray->CloseSystemBubble());
172 }
173
174 // Opening and closing the bubble should change the coloring of the tray.
TEST_F(SystemTrayTest,SystemTrayColoring)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());
180
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());
184
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());
189 }
190
191 // Closing the system bubble through an alignment change should change the
192 // system tray coloring back to normal.
TEST_F(SystemTrayTest,SystemTrayColoringAfterAlignmentChange)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());
201
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());
205
206 // Changing the alignment should close the system bubble and change the
207 // background color.
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());
213 }
214
TEST_F(SystemTrayTest,SystemTrayTestItems)215 TEST_F(SystemTrayTest, SystemTrayTestItems) {
216 SystemTray* tray = GetSystemTray();
217 ASSERT_TRUE(tray->GetWidget());
218
219 TestItem* test_item = new TestItem;
220 TestItem* detailed_item = new TestItem;
221 tray->AddTrayItem(test_item);
222 tray->AddTrayItem(detailed_item);
223
224 // Check items have been added
225 const std::vector<SystemTrayItem*>& items = tray->GetTrayItems();
226 ASSERT_TRUE(
227 std::find(items.begin(), items.end(), test_item) != items.end());
228 ASSERT_TRUE(
229 std::find(items.begin(), items.end(), detailed_item) != items.end());
230
231 // Ensure the tray views are created.
232 ASSERT_TRUE(test_item->tray_view() != NULL);
233 ASSERT_TRUE(detailed_item->tray_view() != NULL);
234
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);
239
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);
245
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);
251 }
252
TEST_F(SystemTrayTest,SystemTrayNoViewItems)253 TEST_F(SystemTrayTest, SystemTrayNoViewItems) {
254 SystemTray* tray = GetSystemTray();
255 ASSERT_TRUE(tray->GetWidget());
256
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();
263 }
264
TEST_F(SystemTrayTest,TrayWidgetAutoResizes)265 TEST_F(SystemTrayTest, TrayWidgetAutoResizes) {
266 SystemTray* tray = GetSystemTray();
267 ASSERT_TRUE(tray->GetWidget());
268
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);
272
273 gfx::Size initial_size = tray->GetWidget()->GetWindowBoundsInScreen().size();
274
275 TestItem* new_item = new TestItem;
276 tray->AddTrayItem(new_item);
277
278 gfx::Size new_size = tray->GetWidget()->GetWindowBoundsInScreen().size();
279
280 // Adding the new item should change the size of the tray.
281 EXPECT_NE(initial_size.ToString(), new_size.ToString());
282
283 // Hiding the tray view of the new item should also change the size of the
284 // tray.
285 new_item->tray_view()->SetVisible(false);
286 EXPECT_EQ(initial_size.ToString(),
287 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString());
288
289 new_item->tray_view()->SetVisible(true);
290 EXPECT_EQ(new_size.ToString(),
291 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString());
292 }
293
TEST_F(SystemTrayTest,SystemTrayNotifications)294 TEST_F(SystemTrayTest, SystemTrayNotifications) {
295 SystemTray* tray = GetSystemTray();
296 ASSERT_TRUE(tray->GetWidget());
297
298 TestItem* test_item = new TestItem;
299 TestItem* detailed_item = new TestItem;
300 tray->AddTrayItem(test_item);
301 tray->AddTrayItem(detailed_item);
302
303 // Ensure the tray views are created.
304 ASSERT_TRUE(test_item->tray_view() != NULL);
305 ASSERT_TRUE(detailed_item->tray_view() != NULL);
306
307 // Ensure a notification view is created.
308 tray->ShowNotificationView(test_item);
309 ASSERT_TRUE(test_item->notification_view() != NULL);
310
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);
315
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);
321
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);
327 }
328
TEST_F(SystemTrayTest,BubbleCreationTypesTest)329 TEST_F(SystemTrayTest, BubbleCreationTypesTest) {
330 SystemTray* tray = GetSystemTray();
331 ASSERT_TRUE(tray->GetWidget());
332
333 TestItem* test_item = new TestItem;
334 tray->AddTrayItem(test_item);
335
336 // Ensure the tray views are created.
337 ASSERT_TRUE(test_item->tray_view() != NULL);
338
339 // Show the default view, ensure the notification view is destroyed.
340 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
341 RunAllPendingInMessageLoop();
342
343 views::Widget* widget = test_item->default_view()->GetWidget();
344 gfx::Rect bubble_bounds = widget->GetWindowBoundsInScreen();
345
346 tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING);
347 RunAllPendingInMessageLoop();
348
349 EXPECT_FALSE(test_item->default_view());
350
351 EXPECT_EQ(bubble_bounds.ToString(), test_item->detailed_view()->GetWidget()->
352 GetWindowBoundsInScreen().ToString());
353 EXPECT_EQ(widget, test_item->detailed_view()->GetWidget());
354
355 tray->ShowDefaultView(BUBBLE_USE_EXISTING);
356 RunAllPendingInMessageLoop();
357
358 EXPECT_EQ(bubble_bounds.ToString(), test_item->default_view()->GetWidget()->
359 GetWindowBoundsInScreen().ToString());
360 EXPECT_EQ(widget, test_item->default_view()->GetWidget());
361 }
362
363 // Tests that the tray is laid out properly and is fully contained within
364 // the shelf.
TEST_F(SystemTrayTest,TrayBoundsInWidget)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();
371
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());
380
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());
389
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());
398 }
399
TEST_F(SystemTrayTest,PersistentBubble)400 TEST_F(SystemTrayTest, PersistentBubble) {
401 SystemTray* tray = GetSystemTray();
402 ASSERT_TRUE(tray->GetWidget());
403
404 TestItem* test_item = new TestItem;
405 tray->AddTrayItem(test_item);
406
407 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
408
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());
416
417 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
418 ASSERT_TRUE(tray->HasSystemBubble());
419 {
420 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
421 gfx::Point(5, 5));
422 generator.ClickLeftButton();
423 ASSERT_FALSE(tray->HasSystemBubble());
424 }
425
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());
432
433 {
434 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
435 gfx::Point(5, 5));
436 generator.ClickLeftButton();
437 ASSERT_TRUE(tray->HasSystemBubble());
438 }
439 }
440
441 #if defined(OS_CHROMEOS)
442 // Accessibility/Settings tray items are available only on cros.
443 #define MAYBE_WithSystemModal WithSystemModal
444 #else
445 #define MAYBE_WithSystemModal DISABLED_WithSystemModal
446 #endif
TEST_F(SystemTrayTest,MAYBE_WithSystemModal)447 TEST_F(SystemTrayTest, MAYBE_WithSystemModal) {
448 // Check if the accessibility item is created even with system modal
449 // dialog.
450 Shell::GetInstance()->accessibility_delegate()->SetVirtualKeyboardEnabled(
451 true);
452 views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
453 new ModalWidgetDelegate(),
454 Shell::GetPrimaryRootWindow(),
455 gfx::Rect(0, 0, 100, 100));
456 widget->Show();
457
458 SystemTray* tray = GetSystemTray();
459 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
460
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));
469
470 widget->Close();
471
472 tray->ShowDefaultView(BUBBLE_CREATE_NEW);
473 // System modal is gone. The bubble should now contains settings
474 // as well.
475 accessibility = tray->GetSystemBubble()->bubble_view()->GetViewByID(
476 test::kAccessibilityTrayItemViewId);
477 ASSERT_TRUE(accessibility);
478 EXPECT_TRUE(accessibility->visible());
479
480 const views::View* settings =
481 tray->GetSystemBubble()->bubble_view()->GetViewByID(
482 test::kSettingsTrayItemViewId);
483 ASSERT_TRUE(settings);
484 EXPECT_TRUE(settings->visible());
485 }
486
487 // Tests that if SetVisible(true) is called while animating to hidden that the
488 // tray becomes visible, and stops animating to hidden.
TEST_F(SystemTrayTest,SetVisibleDuringHideAnimation)489 TEST_F(SystemTrayTest, SetVisibleDuringHideAnimation) {
490 SystemTray* tray = GetSystemTray();
491 ASSERT_TRUE(tray->visible());
492
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());
500
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());
506 }
507
508 } // namespace test
509 } // namespace ash
510