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