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/root_window_controller.h" 10 #include "ash/shelf/shelf_layout_manager.h" 11 #include "ash/shelf/shelf_widget.h" 12 #include "ash/shell.h" 13 #include "ash/system/status_area_widget.h" 14 #include "ash/system/tray/system_tray_item.h" 15 #include "ash/test/ash_test_base.h" 16 #include "ash/wm/window_util.h" 17 #include "base/run_loop.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "ui/aura/test/event_generator.h" 20 #include "ui/aura/window.h" 21 #include "ui/views/controls/label.h" 22 #include "ui/views/layout/fill_layout.h" 23 #include "ui/views/view.h" 24 #include "ui/views/widget/widget.h" 25 26 #if defined(OS_WIN) 27 #include "base/win/windows_version.h" 28 #endif 29 30 namespace ash { 31 namespace test { 32 33 namespace { 34 35 SystemTray* GetSystemTray() { 36 return Shell::GetPrimaryRootWindowController()->shelf()-> 37 status_area_widget()->system_tray(); 38 } 39 40 // Trivial item implementation that tracks its views for testing. 41 class TestItem : public SystemTrayItem { 42 public: 43 TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL) {} 44 45 virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE { 46 tray_view_ = new views::View; 47 // Add a label so it has non-zero width. 48 tray_view_->SetLayoutManager(new views::FillLayout); 49 tray_view_->AddChildView(new views::Label(UTF8ToUTF16("Tray"))); 50 return tray_view_; 51 } 52 53 virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE { 54 default_view_ = new views::View; 55 default_view_->SetLayoutManager(new views::FillLayout); 56 default_view_->AddChildView(new views::Label(UTF8ToUTF16("Default"))); 57 return default_view_; 58 } 59 60 virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE { 61 detailed_view_ = new views::View; 62 detailed_view_->SetLayoutManager(new views::FillLayout); 63 detailed_view_->AddChildView(new views::Label(UTF8ToUTF16("Detailed"))); 64 return detailed_view_; 65 } 66 67 virtual views::View* CreateNotificationView( 68 user::LoginStatus status) OVERRIDE { 69 notification_view_ = new views::View; 70 return notification_view_; 71 } 72 73 virtual void DestroyTrayView() OVERRIDE { 74 tray_view_ = NULL; 75 } 76 77 virtual void DestroyDefaultView() OVERRIDE { 78 default_view_ = NULL; 79 } 80 81 virtual void DestroyDetailedView() OVERRIDE { 82 detailed_view_ = NULL; 83 } 84 85 virtual void DestroyNotificationView() OVERRIDE { 86 notification_view_ = NULL; 87 } 88 89 virtual void UpdateAfterLoginStatusChange( 90 user::LoginStatus status) OVERRIDE { 91 } 92 93 views::View* tray_view() const { return tray_view_; } 94 views::View* default_view() const { return default_view_; } 95 views::View* detailed_view() const { return detailed_view_; } 96 views::View* notification_view() const { return notification_view_; } 97 98 private: 99 views::View* tray_view_; 100 views::View* default_view_; 101 views::View* detailed_view_; 102 views::View* notification_view_; 103 }; 104 105 // Trivial item implementation that returns NULL from tray/default/detailed 106 // view creation methods. 107 class TestNoViewItem : public SystemTrayItem { 108 public: 109 TestNoViewItem() : SystemTrayItem(GetSystemTray()) {} 110 111 virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE { 112 return NULL; 113 } 114 115 virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE { 116 return NULL; 117 } 118 119 virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE { 120 return NULL; 121 } 122 123 virtual views::View* CreateNotificationView( 124 user::LoginStatus status) OVERRIDE { 125 return NULL; 126 } 127 128 virtual void DestroyTrayView() OVERRIDE {} 129 virtual void DestroyDefaultView() OVERRIDE {} 130 virtual void DestroyDetailedView() OVERRIDE {} 131 virtual void DestroyNotificationView() OVERRIDE {} 132 virtual void UpdateAfterLoginStatusChange( 133 user::LoginStatus status) OVERRIDE { 134 } 135 }; 136 137 } // namespace 138 139 typedef AshTestBase SystemTrayTest; 140 141 TEST_F(SystemTrayTest, SystemTrayDefaultView) { 142 SystemTray* tray = GetSystemTray(); 143 ASSERT_TRUE(tray->GetWidget()); 144 145 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 146 147 // Ensure that closing the bubble destroys it. 148 ASSERT_TRUE(tray->CloseSystemBubble()); 149 RunAllPendingInMessageLoop(); 150 ASSERT_FALSE(tray->CloseSystemBubble()); 151 } 152 153 // Opening and closing the bubble should change the coloring of the tray. 154 TEST_F(SystemTrayTest, SystemTrayColoring) { 155 SystemTray* tray = GetSystemTray(); 156 ASSERT_TRUE(tray->GetWidget()); 157 // At the beginning the tray coloring is not active. 158 ASSERT_FALSE(tray->draw_background_as_active()); 159 160 // Showing the system bubble should show the background as active. 161 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 162 ASSERT_TRUE(tray->draw_background_as_active()); 163 164 // Closing the system menu should change the coloring back to normal. 165 ASSERT_TRUE(tray->CloseSystemBubble()); 166 RunAllPendingInMessageLoop(); 167 ASSERT_FALSE(tray->draw_background_as_active()); 168 } 169 170 // Closing the system bubble through an alignment change should change the 171 // system tray coloring back to normal. 172 TEST_F(SystemTrayTest, SystemTrayColoringAfterAlignmentChange) { 173 SystemTray* tray = GetSystemTray(); 174 ASSERT_TRUE(tray->GetWidget()); 175 internal::ShelfLayoutManager* manager = 176 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager(); 177 manager->SetAlignment(SHELF_ALIGNMENT_BOTTOM); 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 // Changing the alignment should close the system bubble and change the 186 // background color. 187 manager->SetAlignment(SHELF_ALIGNMENT_LEFT); 188 ASSERT_FALSE(tray->draw_background_as_active()); 189 RunAllPendingInMessageLoop(); 190 // The bubble should already be closed by now. 191 ASSERT_FALSE(tray->CloseSystemBubble()); 192 } 193 194 TEST_F(SystemTrayTest, SystemTrayTestItems) { 195 SystemTray* tray = GetSystemTray(); 196 ASSERT_TRUE(tray->GetWidget()); 197 198 TestItem* test_item = new TestItem; 199 TestItem* detailed_item = new TestItem; 200 tray->AddTrayItem(test_item); 201 tray->AddTrayItem(detailed_item); 202 203 // Check items have been added 204 const std::vector<SystemTrayItem*>& items = tray->GetTrayItems(); 205 ASSERT_TRUE( 206 std::find(items.begin(), items.end(), test_item) != items.end()); 207 ASSERT_TRUE( 208 std::find(items.begin(), items.end(), detailed_item) != items.end()); 209 210 // Ensure the tray views are created. 211 ASSERT_TRUE(test_item->tray_view() != NULL); 212 ASSERT_TRUE(detailed_item->tray_view() != NULL); 213 214 // Ensure a default views are created. 215 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 216 ASSERT_TRUE(test_item->default_view() != NULL); 217 ASSERT_TRUE(detailed_item->default_view() != NULL); 218 219 // Show the detailed view, ensure it's created and the default view destroyed. 220 tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW); 221 RunAllPendingInMessageLoop(); 222 ASSERT_TRUE(test_item->default_view() == NULL); 223 ASSERT_TRUE(detailed_item->detailed_view() != NULL); 224 225 // Show the default view, ensure it's created and the detailed view destroyed. 226 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 227 RunAllPendingInMessageLoop(); 228 ASSERT_TRUE(test_item->default_view() != NULL); 229 ASSERT_TRUE(detailed_item->detailed_view() == NULL); 230 } 231 232 TEST_F(SystemTrayTest, SystemTrayNoViewItems) { 233 SystemTray* tray = GetSystemTray(); 234 ASSERT_TRUE(tray->GetWidget()); 235 236 // Verify that no crashes occur on items lacking some views. 237 TestNoViewItem* no_view_item = new TestNoViewItem; 238 tray->AddTrayItem(no_view_item); 239 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 240 tray->ShowDetailedView(no_view_item, 0, false, BUBBLE_USE_EXISTING); 241 RunAllPendingInMessageLoop(); 242 } 243 244 TEST_F(SystemTrayTest, TrayWidgetAutoResizes) { 245 SystemTray* tray = GetSystemTray(); 246 ASSERT_TRUE(tray->GetWidget()); 247 248 // Add an initial tray item so that the tray gets laid out correctly. 249 TestItem* initial_item = new TestItem; 250 tray->AddTrayItem(initial_item); 251 252 gfx::Size initial_size = tray->GetWidget()->GetWindowBoundsInScreen().size(); 253 254 TestItem* new_item = new TestItem; 255 tray->AddTrayItem(new_item); 256 257 gfx::Size new_size = tray->GetWidget()->GetWindowBoundsInScreen().size(); 258 259 // Adding the new item should change the size of the tray. 260 EXPECT_NE(initial_size.ToString(), new_size.ToString()); 261 262 // Hiding the tray view of the new item should also change the size of the 263 // tray. 264 new_item->tray_view()->SetVisible(false); 265 EXPECT_EQ(initial_size.ToString(), 266 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString()); 267 268 new_item->tray_view()->SetVisible(true); 269 EXPECT_EQ(new_size.ToString(), 270 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString()); 271 } 272 273 TEST_F(SystemTrayTest, SystemTrayNotifications) { 274 SystemTray* tray = GetSystemTray(); 275 ASSERT_TRUE(tray->GetWidget()); 276 277 TestItem* test_item = new TestItem; 278 TestItem* detailed_item = new TestItem; 279 tray->AddTrayItem(test_item); 280 tray->AddTrayItem(detailed_item); 281 282 // Ensure the tray views are created. 283 ASSERT_TRUE(test_item->tray_view() != NULL); 284 ASSERT_TRUE(detailed_item->tray_view() != NULL); 285 286 // Ensure a notification view is created. 287 tray->ShowNotificationView(test_item); 288 ASSERT_TRUE(test_item->notification_view() != NULL); 289 290 // Show the default view, notification view should remain. 291 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 292 RunAllPendingInMessageLoop(); 293 ASSERT_TRUE(test_item->notification_view() != NULL); 294 295 // Show the detailed view, ensure the notificaiton view remains. 296 tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW); 297 RunAllPendingInMessageLoop(); 298 ASSERT_TRUE(detailed_item->detailed_view() != NULL); 299 ASSERT_TRUE(test_item->notification_view() != NULL); 300 301 // Hide the detailed view, ensure the notification view still exists. 302 ASSERT_TRUE(tray->CloseSystemBubble()); 303 RunAllPendingInMessageLoop(); 304 ASSERT_TRUE(detailed_item->detailed_view() == NULL); 305 ASSERT_TRUE(test_item->notification_view() != NULL); 306 } 307 308 TEST_F(SystemTrayTest, BubbleCreationTypesTest) { 309 SystemTray* tray = GetSystemTray(); 310 ASSERT_TRUE(tray->GetWidget()); 311 312 TestItem* test_item = new TestItem; 313 tray->AddTrayItem(test_item); 314 315 // Ensure the tray views are created. 316 ASSERT_TRUE(test_item->tray_view() != NULL); 317 318 // Show the default view, ensure the notification view is destroyed. 319 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 320 RunAllPendingInMessageLoop(); 321 322 views::Widget* widget = test_item->default_view()->GetWidget(); 323 gfx::Rect bubble_bounds = widget->GetWindowBoundsInScreen(); 324 325 tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING); 326 RunAllPendingInMessageLoop(); 327 328 EXPECT_FALSE(test_item->default_view()); 329 330 EXPECT_EQ(bubble_bounds.ToString(), test_item->detailed_view()->GetWidget()-> 331 GetWindowBoundsInScreen().ToString()); 332 EXPECT_EQ(widget, test_item->detailed_view()->GetWidget()); 333 334 tray->ShowDefaultView(BUBBLE_USE_EXISTING); 335 RunAllPendingInMessageLoop(); 336 337 EXPECT_EQ(bubble_bounds.ToString(), test_item->default_view()->GetWidget()-> 338 GetWindowBoundsInScreen().ToString()); 339 EXPECT_EQ(widget, test_item->default_view()->GetWidget()); 340 } 341 342 // Tests that the tray is laid out properly and is fully contained within 343 // the shelf. 344 TEST_F(SystemTrayTest, TrayBoundsInWidget) { 345 internal::ShelfLayoutManager* manager = 346 Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager(); 347 internal::StatusAreaWidget* widget = 348 Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget(); 349 SystemTray* tray = widget->system_tray(); 350 351 // Test in bottom alignment. 352 manager->SetAlignment(SHELF_ALIGNMENT_BOTTOM); 353 gfx::Rect window_bounds = widget->GetWindowBoundsInScreen(); 354 gfx::Rect tray_bounds = tray->GetBoundsInScreen(); 355 EXPECT_TRUE(window_bounds.bottom() >= tray_bounds.bottom()); 356 EXPECT_TRUE(window_bounds.right() >= tray_bounds.right()); 357 EXPECT_TRUE(window_bounds.x() >= tray_bounds.x()); 358 EXPECT_TRUE(window_bounds.y() >= tray_bounds.y()); 359 360 // Test in the left alignment. 361 manager->SetAlignment(SHELF_ALIGNMENT_LEFT); 362 window_bounds = widget->GetWindowBoundsInScreen(); 363 tray_bounds = tray->GetBoundsInScreen(); 364 EXPECT_TRUE(window_bounds.bottom() >= tray_bounds.bottom()); 365 EXPECT_TRUE(window_bounds.right() >= tray_bounds.right()); 366 EXPECT_TRUE(window_bounds.x() >= tray_bounds.x()); 367 EXPECT_TRUE(window_bounds.y() >= tray_bounds.y()); 368 369 // Test in the right alignment. 370 manager->SetAlignment(SHELF_ALIGNMENT_LEFT); 371 window_bounds = widget->GetWindowBoundsInScreen(); 372 tray_bounds = tray->GetBoundsInScreen(); 373 EXPECT_TRUE(window_bounds.bottom() >= tray_bounds.bottom()); 374 EXPECT_TRUE(window_bounds.right() >= tray_bounds.right()); 375 EXPECT_TRUE(window_bounds.x() >= tray_bounds.x()); 376 EXPECT_TRUE(window_bounds.y() >= tray_bounds.y()); 377 } 378 379 TEST_F(SystemTrayTest, PersistentBubble) { 380 SystemTray* tray = GetSystemTray(); 381 ASSERT_TRUE(tray->GetWidget()); 382 383 TestItem* test_item = new TestItem; 384 tray->AddTrayItem(test_item); 385 386 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0)); 387 388 // Tests for usual default view. 389 // Activating window. 390 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 391 ASSERT_TRUE(tray->HasSystemBubble()); 392 wm::ActivateWindow(window.get()); 393 base::RunLoop().RunUntilIdle(); 394 ASSERT_FALSE(tray->HasSystemBubble()); 395 396 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 397 ASSERT_TRUE(tray->HasSystemBubble()); 398 { 399 aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), 400 gfx::Point(5, 5)); 401 generator.ClickLeftButton(); 402 ASSERT_FALSE(tray->HasSystemBubble()); 403 } 404 405 // Same tests for persistent default view. 406 tray->ShowPersistentDefaultView(); 407 ASSERT_TRUE(tray->HasSystemBubble()); 408 wm::ActivateWindow(window.get()); 409 base::RunLoop().RunUntilIdle(); 410 ASSERT_TRUE(tray->HasSystemBubble()); 411 412 { 413 aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), 414 gfx::Point(5, 5)); 415 generator.ClickLeftButton(); 416 ASSERT_TRUE(tray->HasSystemBubble()); 417 } 418 } 419 420 } // namespace test 421 } // namespace ash 422