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
36namespace ash {
37namespace test {
38
39namespace {
40
41SystemTray* GetSystemTray() {
42  return Shell::GetPrimaryRootWindowController()->shelf()->
43      status_area_widget()->system_tray();
44}
45
46// Trivial item implementation that tracks its views for testing.
47class TestItem : public SystemTrayItem {
48 public:
49  TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL) {}
50
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
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
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
74  virtual views::View* CreateNotificationView(
75      user::LoginStatus status) OVERRIDE {
76    notification_view_ = new views::View;
77    return notification_view_;
78  }
79
80  virtual void DestroyTrayView() OVERRIDE {
81    tray_view_ = NULL;
82  }
83
84  virtual void DestroyDefaultView() OVERRIDE {
85    default_view_ = NULL;
86  }
87
88  virtual void DestroyDetailedView() OVERRIDE {
89    detailed_view_ = NULL;
90  }
91
92  virtual void DestroyNotificationView() OVERRIDE {
93    notification_view_ = NULL;
94  }
95
96  virtual void UpdateAfterLoginStatusChange(
97      user::LoginStatus status) OVERRIDE {
98  }
99
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_; }
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.
114class TestNoViewItem : public SystemTrayItem {
115 public:
116  TestNoViewItem() : SystemTrayItem(GetSystemTray()) {}
117
118  virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE {
119    return NULL;
120  }
121
122  virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
123    return NULL;
124  }
125
126  virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE {
127    return NULL;
128  }
129
130  virtual views::View* CreateNotificationView(
131      user::LoginStatus status) OVERRIDE {
132    return NULL;
133  }
134
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 {
141  }
142};
143
144class ModalWidgetDelegate : public views::WidgetDelegateView {
145 public:
146  ModalWidgetDelegate() {}
147  virtual ~ModalWidgetDelegate() {}
148
149  virtual views::View* GetContentsView() OVERRIDE { return this; }
150  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
160typedef AshTestBase SystemTrayTest;
161
162TEST_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.
175TEST_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.
193TEST_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
215TEST_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
253TEST_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
265TEST_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
294TEST_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
329TEST_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.
365TEST_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
400TEST_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
447TEST_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.
489TEST_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