Home | History | Annotate | Download | only in tray
      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 
     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:
     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.
    114 class 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 
    144 class 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 
    160 typedef AshTestBase SystemTrayTest;
    161 
    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.
    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.
    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 
    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 
    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 
    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 
    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 
    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.
    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 
    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
    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.
    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