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_widget.h" 11 #include "ash/shell.h" 12 #include "ash/system/status_area_widget.h" 13 #include "ash/system/tray/system_tray_item.h" 14 #include "ash/test/ash_test_base.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "ui/views/controls/label.h" 17 #include "ui/views/layout/fill_layout.h" 18 #include "ui/views/view.h" 19 #include "ui/views/widget/widget.h" 20 21 #if defined(OS_WIN) 22 #include "base/win/windows_version.h" 23 #endif 24 25 namespace ash { 26 namespace test { 27 28 namespace { 29 30 const int kStatusTrayOffsetFromScreenEdgeForTest = 4; 31 32 SystemTray* GetSystemTray() { 33 return Shell::GetPrimaryRootWindowController()->shelf()-> 34 status_area_widget()->system_tray(); 35 } 36 37 // Trivial item implementation that tracks its views for testing. 38 class TestItem : public SystemTrayItem { 39 public: 40 TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL) {} 41 42 virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE { 43 tray_view_ = new views::View; 44 // Add a label so it has non-zero width. 45 tray_view_->SetLayoutManager(new views::FillLayout); 46 tray_view_->AddChildView(new views::Label(UTF8ToUTF16("Tray"))); 47 return tray_view_; 48 } 49 50 virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE { 51 default_view_ = new views::View; 52 default_view_->SetLayoutManager(new views::FillLayout); 53 default_view_->AddChildView(new views::Label(UTF8ToUTF16("Default"))); 54 return default_view_; 55 } 56 57 virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE { 58 detailed_view_ = new views::View; 59 detailed_view_->SetLayoutManager(new views::FillLayout); 60 detailed_view_->AddChildView(new views::Label(UTF8ToUTF16("Detailed"))); 61 return detailed_view_; 62 } 63 64 virtual views::View* CreateNotificationView( 65 user::LoginStatus status) OVERRIDE { 66 notification_view_ = new views::View; 67 return notification_view_; 68 } 69 70 virtual void DestroyTrayView() OVERRIDE { 71 tray_view_ = NULL; 72 } 73 74 virtual void DestroyDefaultView() OVERRIDE { 75 default_view_ = NULL; 76 } 77 78 virtual void DestroyDetailedView() OVERRIDE { 79 detailed_view_ = NULL; 80 } 81 82 virtual void DestroyNotificationView() OVERRIDE { 83 notification_view_ = NULL; 84 } 85 86 virtual void UpdateAfterLoginStatusChange( 87 user::LoginStatus status) OVERRIDE { 88 } 89 90 views::View* tray_view() const { return tray_view_; } 91 views::View* default_view() const { return default_view_; } 92 views::View* detailed_view() const { return detailed_view_; } 93 views::View* notification_view() const { return notification_view_; } 94 95 private: 96 views::View* tray_view_; 97 views::View* default_view_; 98 views::View* detailed_view_; 99 views::View* notification_view_; 100 }; 101 102 // Trivial item implementation that returns NULL from tray/default/detailed 103 // view creation methods. 104 class TestNoViewItem : public SystemTrayItem { 105 public: 106 TestNoViewItem() : SystemTrayItem(GetSystemTray()) {} 107 108 virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE { 109 return NULL; 110 } 111 112 virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE { 113 return NULL; 114 } 115 116 virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE { 117 return NULL; 118 } 119 120 virtual views::View* CreateNotificationView( 121 user::LoginStatus status) OVERRIDE { 122 return NULL; 123 } 124 125 virtual void DestroyTrayView() OVERRIDE {} 126 virtual void DestroyDefaultView() OVERRIDE {} 127 virtual void DestroyDetailedView() OVERRIDE {} 128 virtual void DestroyNotificationView() OVERRIDE {} 129 virtual void UpdateAfterLoginStatusChange( 130 user::LoginStatus status) OVERRIDE { 131 } 132 }; 133 134 } // namespace 135 136 typedef AshTestBase SystemTrayTest; 137 138 TEST_F(SystemTrayTest, SystemTrayDefaultView) { 139 SystemTray* tray = GetSystemTray(); 140 ASSERT_TRUE(tray->GetWidget()); 141 142 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 143 144 // Ensure that closing the bubble destroys it. 145 ASSERT_TRUE(tray->CloseSystemBubble()); 146 RunAllPendingInMessageLoop(); 147 ASSERT_FALSE(tray->CloseSystemBubble()); 148 } 149 150 TEST_F(SystemTrayTest, SystemTrayTestItems) { 151 SystemTray* tray = GetSystemTray(); 152 ASSERT_TRUE(tray->GetWidget()); 153 154 TestItem* test_item = new TestItem; 155 TestItem* detailed_item = new TestItem; 156 tray->AddTrayItem(test_item); 157 tray->AddTrayItem(detailed_item); 158 159 // Check items have been added 160 const std::vector<SystemTrayItem*>& items = tray->GetTrayItems(); 161 ASSERT_TRUE( 162 std::find(items.begin(), items.end(), test_item) != items.end()); 163 ASSERT_TRUE( 164 std::find(items.begin(), items.end(), detailed_item) != items.end()); 165 166 // Ensure the tray views are created. 167 ASSERT_TRUE(test_item->tray_view() != NULL); 168 ASSERT_TRUE(detailed_item->tray_view() != NULL); 169 170 // Ensure a default views are created. 171 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 172 ASSERT_TRUE(test_item->default_view() != NULL); 173 ASSERT_TRUE(detailed_item->default_view() != NULL); 174 175 // Show the detailed view, ensure it's created and the default view destroyed. 176 tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW); 177 RunAllPendingInMessageLoop(); 178 ASSERT_TRUE(test_item->default_view() == NULL); 179 ASSERT_TRUE(detailed_item->detailed_view() != NULL); 180 181 // Show the default view, ensure it's created and the detailed view destroyed. 182 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 183 RunAllPendingInMessageLoop(); 184 ASSERT_TRUE(test_item->default_view() != NULL); 185 ASSERT_TRUE(detailed_item->detailed_view() == NULL); 186 } 187 188 TEST_F(SystemTrayTest, SystemTrayNoViewItems) { 189 SystemTray* tray = GetSystemTray(); 190 ASSERT_TRUE(tray->GetWidget()); 191 192 // Verify that no crashes occur on items lacking some views. 193 TestNoViewItem* no_view_item = new TestNoViewItem; 194 tray->AddTrayItem(no_view_item); 195 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 196 tray->ShowDetailedView(no_view_item, 0, false, BUBBLE_USE_EXISTING); 197 RunAllPendingInMessageLoop(); 198 } 199 200 TEST_F(SystemTrayTest, TrayWidgetAutoResizes) { 201 SystemTray* tray = GetSystemTray(); 202 ASSERT_TRUE(tray->GetWidget()); 203 204 // Add an initial tray item so that the tray gets laid out correctly. 205 TestItem* initial_item = new TestItem; 206 tray->AddTrayItem(initial_item); 207 208 gfx::Size initial_size = tray->GetWidget()->GetWindowBoundsInScreen().size(); 209 210 TestItem* new_item = new TestItem; 211 tray->AddTrayItem(new_item); 212 213 gfx::Size new_size = tray->GetWidget()->GetWindowBoundsInScreen().size(); 214 215 // Adding the new item should change the size of the tray. 216 EXPECT_NE(initial_size.ToString(), new_size.ToString()); 217 218 // Hiding the tray view of the new item should also change the size of the 219 // tray. 220 new_item->tray_view()->SetVisible(false); 221 EXPECT_EQ(initial_size.ToString(), 222 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString()); 223 224 new_item->tray_view()->SetVisible(true); 225 EXPECT_EQ(new_size.ToString(), 226 tray->GetWidget()->GetWindowBoundsInScreen().size().ToString()); 227 } 228 229 TEST_F(SystemTrayTest, SystemTrayNotifications) { 230 SystemTray* tray = GetSystemTray(); 231 ASSERT_TRUE(tray->GetWidget()); 232 233 TestItem* test_item = new TestItem; 234 TestItem* detailed_item = new TestItem; 235 tray->AddTrayItem(test_item); 236 tray->AddTrayItem(detailed_item); 237 238 // Ensure the tray views are created. 239 ASSERT_TRUE(test_item->tray_view() != NULL); 240 ASSERT_TRUE(detailed_item->tray_view() != NULL); 241 242 // Ensure a notification view is created. 243 tray->ShowNotificationView(test_item); 244 ASSERT_TRUE(test_item->notification_view() != NULL); 245 246 // Show the default view, notification view should remain. 247 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 248 RunAllPendingInMessageLoop(); 249 ASSERT_TRUE(test_item->notification_view() != NULL); 250 251 // Show the detailed view, ensure the notificaiton view remains. 252 tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW); 253 RunAllPendingInMessageLoop(); 254 ASSERT_TRUE(detailed_item->detailed_view() != NULL); 255 ASSERT_TRUE(test_item->notification_view() != NULL); 256 257 // Hide the detailed view, ensure the notification view still exists. 258 ASSERT_TRUE(tray->CloseSystemBubble()); 259 RunAllPendingInMessageLoop(); 260 ASSERT_TRUE(detailed_item->detailed_view() == NULL); 261 ASSERT_TRUE(test_item->notification_view() != NULL); 262 } 263 264 TEST_F(SystemTrayTest, BubbleCreationTypesTest) { 265 SystemTray* tray = GetSystemTray(); 266 ASSERT_TRUE(tray->GetWidget()); 267 268 TestItem* test_item = new TestItem; 269 tray->AddTrayItem(test_item); 270 271 // Ensure the tray views are created. 272 ASSERT_TRUE(test_item->tray_view() != NULL); 273 274 // Show the default view, ensure the notification view is destroyed. 275 tray->ShowDefaultView(BUBBLE_CREATE_NEW); 276 RunAllPendingInMessageLoop(); 277 278 views::Widget* widget = test_item->default_view()->GetWidget(); 279 gfx::Rect bubble_bounds = widget->GetWindowBoundsInScreen(); 280 281 tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING); 282 RunAllPendingInMessageLoop(); 283 284 EXPECT_FALSE(test_item->default_view()); 285 286 EXPECT_EQ(bubble_bounds.ToString(), test_item->detailed_view()->GetWidget()-> 287 GetWindowBoundsInScreen().ToString()); 288 EXPECT_EQ(widget, test_item->detailed_view()->GetWidget()); 289 290 tray->ShowDefaultView(BUBBLE_USE_EXISTING); 291 RunAllPendingInMessageLoop(); 292 293 EXPECT_EQ(bubble_bounds.ToString(), test_item->default_view()->GetWidget()-> 294 GetWindowBoundsInScreen().ToString()); 295 EXPECT_EQ(widget, test_item->default_view()->GetWidget()); 296 } 297 298 // Tests that the tray is laid out properly in the widget to make sure that the 299 // tray extends to the correct edge of the screen. 300 TEST_F(SystemTrayTest, TrayBoundsInWidget) { 301 internal::StatusAreaWidget* widget = 302 Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget(); 303 SystemTray* tray = widget->system_tray(); 304 305 // Test in bottom alignment. Bottom and right edges of the view should be 306 // aligned with the widget. 307 widget->SetShelfAlignment(SHELF_ALIGNMENT_BOTTOM); 308 gfx::Rect window_bounds = widget->GetWindowBoundsInScreen(); 309 gfx::Rect tray_bounds = tray->GetBoundsInScreen(); 310 EXPECT_EQ(window_bounds.bottom(), 311 tray_bounds.bottom() + kStatusTrayOffsetFromScreenEdgeForTest); 312 EXPECT_EQ(window_bounds.right(), tray_bounds.right()); 313 314 // Test in the top alignment. Top and right edges should match. 315 widget->SetShelfAlignment(SHELF_ALIGNMENT_TOP); 316 window_bounds = widget->GetWindowBoundsInScreen(); 317 tray_bounds = tray->GetBoundsInScreen(); 318 EXPECT_EQ(window_bounds.y(), 319 tray_bounds.y() - kStatusTrayOffsetFromScreenEdgeForTest); 320 EXPECT_EQ(window_bounds.right(), tray_bounds.right()); 321 322 // Test in the left alignment. Left and bottom edges should match. 323 widget->SetShelfAlignment(SHELF_ALIGNMENT_LEFT); 324 window_bounds = widget->GetWindowBoundsInScreen(); 325 tray_bounds = tray->GetBoundsInScreen(); 326 EXPECT_EQ(window_bounds.bottom(), tray_bounds.bottom()); 327 EXPECT_EQ(window_bounds.x(), 328 tray_bounds.x() - kStatusTrayOffsetFromScreenEdgeForTest); 329 330 // Test in the right alignment. Right and bottom edges should match. 331 widget->SetShelfAlignment(SHELF_ALIGNMENT_LEFT); 332 window_bounds = widget->GetWindowBoundsInScreen(); 333 tray_bounds = tray->GetBoundsInScreen(); 334 EXPECT_EQ(window_bounds.bottom(), tray_bounds.bottom()); 335 EXPECT_EQ(window_bounds.right(), tray_bounds.right()); 336 } 337 338 } // namespace test 339 } // namespace ash 340