1 // Copyright 2013 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/display/resolution_notification_controller.h" 6 7 #include "ash/display/display_manager.h" 8 #include "ash/screen_util.h" 9 #include "ash/shell.h" 10 #include "ash/test/ash_test_base.h" 11 #include "base/bind.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "grit/ash_strings.h" 14 #include "ui/base/l10n/l10n_util.h" 15 #include "ui/gfx/size.h" 16 #include "ui/message_center/message_center.h" 17 #include "ui/message_center/notification.h" 18 #include "ui/message_center/notification_list.h" 19 20 namespace ash { 21 namespace { 22 23 base::string16 ExpectedNotificationMessage(int64 display_id, 24 const gfx::Size& new_resolution) { 25 return l10n_util::GetStringFUTF16( 26 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, 27 base::UTF8ToUTF16( 28 Shell::GetInstance()->display_manager()->GetDisplayNameForId( 29 display_id)), 30 base::UTF8ToUTF16(new_resolution.ToString())); 31 } 32 33 base::string16 ExpectedFallbackNotificationMessage( 34 int64 display_id, 35 const gfx::Size& specified_resolution, 36 const gfx::Size& fallback_resolution) { 37 return l10n_util::GetStringFUTF16( 38 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED, 39 base::UTF8ToUTF16( 40 Shell::GetInstance()->display_manager()->GetDisplayNameForId( 41 display_id)), 42 base::UTF8ToUTF16(specified_resolution.ToString()), 43 base::UTF8ToUTF16(fallback_resolution.ToString())); 44 } 45 46 } // namespace 47 48 class ResolutionNotificationControllerTest : public ash::test::AshTestBase { 49 public: 50 ResolutionNotificationControllerTest() 51 : accept_count_(0) { 52 } 53 54 virtual ~ResolutionNotificationControllerTest() {} 55 56 protected: 57 virtual void SetUp() OVERRIDE { 58 ash::test::AshTestBase::SetUp(); 59 ResolutionNotificationController::SuppressTimerForTest(); 60 } 61 62 void SetDisplayResolutionAndNotifyWithResolution( 63 const gfx::Display& display, 64 const gfx::Size& new_resolution, 65 const gfx::Size& actual_new_resolution) { 66 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 67 68 const DisplayInfo& info = display_manager->GetDisplayInfo(display.id()); 69 DisplayMode old_mode(info.size_in_pixel(), 70 60 /* refresh_rate */, 71 false /* interlaced */, 72 false /* native */); 73 DisplayMode new_mode = old_mode; 74 new_mode.size = new_resolution; 75 76 if (display_manager->SetDisplayMode(display.id(), new_mode)) { 77 controller()->PrepareNotification( 78 display.id(), 79 old_mode, 80 new_mode, 81 base::Bind(&ResolutionNotificationControllerTest::OnAccepted, 82 base::Unretained(this))); 83 } 84 85 // OnConfigurationChanged event won't be emitted in the test environment, 86 // so invoke UpdateDisplay() to emit that event explicitly. 87 std::vector<DisplayInfo> info_list; 88 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { 89 int64 id = display_manager->GetDisplayAt(i).id(); 90 DisplayInfo info = display_manager->GetDisplayInfo(id); 91 if (display.id() == id) { 92 gfx::Rect bounds = info.bounds_in_native(); 93 bounds.set_size(actual_new_resolution); 94 info.SetBounds(bounds); 95 } 96 info_list.push_back(info); 97 } 98 display_manager->OnNativeDisplaysChanged(info_list); 99 RunAllPendingInMessageLoop(); 100 } 101 102 void SetDisplayResolutionAndNotify(const gfx::Display& display, 103 const gfx::Size& new_resolution) { 104 SetDisplayResolutionAndNotifyWithResolution( 105 display, new_resolution, new_resolution); 106 } 107 108 static base::string16 GetNotificationMessage() { 109 const message_center::NotificationList::Notifications& notifications = 110 message_center::MessageCenter::Get()->GetVisibleNotifications(); 111 for (message_center::NotificationList::Notifications::const_iterator iter = 112 notifications.begin(); iter != notifications.end(); ++iter) { 113 if ((*iter)->id() == ResolutionNotificationController::kNotificationId) 114 return (*iter)->title(); 115 } 116 117 return base::string16(); 118 } 119 120 static void ClickOnNotification() { 121 message_center::MessageCenter::Get()->ClickOnNotification( 122 ResolutionNotificationController::kNotificationId); 123 } 124 125 static void ClickOnNotificationButton(int index) { 126 message_center::MessageCenter::Get()->ClickOnNotificationButton( 127 ResolutionNotificationController::kNotificationId, index); 128 } 129 130 static void CloseNotification() { 131 message_center::MessageCenter::Get()->RemoveNotification( 132 ResolutionNotificationController::kNotificationId, true /* by_user */); 133 } 134 135 static bool IsNotificationVisible() { 136 return message_center::MessageCenter::Get()->FindVisibleNotificationById( 137 ResolutionNotificationController::kNotificationId); 138 } 139 140 static void TickTimer() { 141 controller()->OnTimerTick(); 142 } 143 144 static ResolutionNotificationController* controller() { 145 return Shell::GetInstance()->resolution_notification_controller(); 146 } 147 148 int accept_count() const { 149 return accept_count_; 150 } 151 152 private: 153 void OnAccepted() { 154 EXPECT_FALSE(controller()->DoesNotificationTimeout()); 155 accept_count_++; 156 } 157 158 int accept_count_; 159 160 DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationControllerTest); 161 }; 162 163 // Basic behaviors and verifies it doesn't cause crashes. 164 TEST_F(ResolutionNotificationControllerTest, Basic) { 165 if (!SupportsMultipleDisplays()) 166 return; 167 168 UpdateDisplay("300x300#300x300%57|200x200%58,250x250#250x250%59|200x200%60"); 169 int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); 170 ash::DisplayManager* display_manager = 171 ash::Shell::GetInstance()->display_manager(); 172 ASSERT_EQ(0, accept_count()); 173 EXPECT_FALSE(IsNotificationVisible()); 174 175 // Changes the resolution and apply the result. 176 SetDisplayResolutionAndNotify( 177 ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); 178 EXPECT_TRUE(IsNotificationVisible()); 179 EXPECT_FALSE(controller()->DoesNotificationTimeout()); 180 EXPECT_EQ(ExpectedNotificationMessage(id2, gfx::Size(200, 200)), 181 GetNotificationMessage()); 182 DisplayMode mode; 183 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 184 EXPECT_EQ("200x200", mode.size.ToString()); 185 EXPECT_EQ(60.0, mode.refresh_rate); 186 187 // Click the revert button, which reverts to the best resolution. 188 ClickOnNotificationButton(0); 189 RunAllPendingInMessageLoop(); 190 EXPECT_FALSE(IsNotificationVisible()); 191 EXPECT_EQ(0, accept_count()); 192 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 193 EXPECT_EQ("250x250", mode.size.ToString()); 194 EXPECT_EQ(59.0, mode.refresh_rate); 195 } 196 197 TEST_F(ResolutionNotificationControllerTest, ClickMeansAccept) { 198 if (!SupportsMultipleDisplays()) 199 return; 200 201 UpdateDisplay("300x300#300x300%57|200x200%58,250x250#250x250%59|200x200%60"); 202 int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); 203 ash::DisplayManager* display_manager = 204 ash::Shell::GetInstance()->display_manager(); 205 ASSERT_EQ(0, accept_count()); 206 EXPECT_FALSE(IsNotificationVisible()); 207 208 // Changes the resolution and apply the result. 209 SetDisplayResolutionAndNotify( 210 ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); 211 EXPECT_TRUE(IsNotificationVisible()); 212 EXPECT_FALSE(controller()->DoesNotificationTimeout()); 213 DisplayMode mode; 214 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 215 EXPECT_EQ("200x200", mode.size.ToString()); 216 EXPECT_EQ(60.0, mode.refresh_rate); 217 218 // Click the revert button, which reverts the resolution. 219 ClickOnNotification(); 220 RunAllPendingInMessageLoop(); 221 EXPECT_FALSE(IsNotificationVisible()); 222 EXPECT_EQ(1, accept_count()); 223 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 224 EXPECT_EQ("200x200", mode.size.ToString()); 225 EXPECT_EQ(60.0, mode.refresh_rate); 226 } 227 228 TEST_F(ResolutionNotificationControllerTest, AcceptButton) { 229 if (!SupportsMultipleDisplays()) 230 return; 231 232 ash::DisplayManager* display_manager = 233 ash::Shell::GetInstance()->display_manager(); 234 235 UpdateDisplay("300x300#300x300%59|200x200%60"); 236 const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay(); 237 SetDisplayResolutionAndNotify(display, gfx::Size(200, 200)); 238 EXPECT_TRUE(IsNotificationVisible()); 239 240 // If there's a single display only, it will have timeout and the first button 241 // becomes accept. 242 EXPECT_TRUE(controller()->DoesNotificationTimeout()); 243 ClickOnNotificationButton(0); 244 EXPECT_FALSE(IsNotificationVisible()); 245 EXPECT_EQ(1, accept_count()); 246 DisplayMode mode; 247 EXPECT_TRUE( 248 display_manager->GetSelectedModeForDisplayId(display.id(), &mode)); 249 EXPECT_EQ("200x200", mode.size.ToString()); 250 EXPECT_EQ(60.0f, mode.refresh_rate); 251 252 // In that case the second button is revert. 253 UpdateDisplay("300x300#300x300%59|200x200%60"); 254 SetDisplayResolutionAndNotify(display, gfx::Size(200, 200)); 255 EXPECT_TRUE(IsNotificationVisible()); 256 257 EXPECT_TRUE(controller()->DoesNotificationTimeout()); 258 ClickOnNotificationButton(1); 259 EXPECT_FALSE(IsNotificationVisible()); 260 EXPECT_EQ(1, accept_count()); 261 EXPECT_TRUE( 262 display_manager->GetSelectedModeForDisplayId(display.id(), &mode)); 263 EXPECT_EQ("300x300", mode.size.ToString()); 264 EXPECT_EQ(59.0f, mode.refresh_rate); 265 } 266 267 TEST_F(ResolutionNotificationControllerTest, Close) { 268 if (!SupportsMultipleDisplays()) 269 return; 270 271 UpdateDisplay("100x100,150x150#150x150%59|200x200%60"); 272 int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); 273 ash::DisplayManager* display_manager = 274 ash::Shell::GetInstance()->display_manager(); 275 ASSERT_EQ(0, accept_count()); 276 EXPECT_FALSE(IsNotificationVisible()); 277 278 // Changes the resolution and apply the result. 279 SetDisplayResolutionAndNotify( 280 ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); 281 EXPECT_TRUE(IsNotificationVisible()); 282 EXPECT_FALSE(controller()->DoesNotificationTimeout()); 283 DisplayMode mode; 284 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 285 EXPECT_EQ("200x200", mode.size.ToString()); 286 EXPECT_EQ(60.0f, mode.refresh_rate); 287 288 // Close the notification (imitates clicking [x] button). Also verifies if 289 // this does not cause a crash. See crbug.com/271784 290 CloseNotification(); 291 RunAllPendingInMessageLoop(); 292 EXPECT_FALSE(IsNotificationVisible()); 293 EXPECT_EQ(1, accept_count()); 294 } 295 296 TEST_F(ResolutionNotificationControllerTest, Timeout) { 297 if (!SupportsMultipleDisplays()) 298 return; 299 300 UpdateDisplay("300x300#300x300%59|200x200%60"); 301 const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay(); 302 SetDisplayResolutionAndNotify(display, gfx::Size(200, 200)); 303 304 for (int i = 0; i < ResolutionNotificationController::kTimeoutInSec; ++i) { 305 EXPECT_TRUE(IsNotificationVisible()) << "notification is closed after " 306 << i << "-th timer tick"; 307 TickTimer(); 308 RunAllPendingInMessageLoop(); 309 } 310 EXPECT_FALSE(IsNotificationVisible()); 311 EXPECT_EQ(0, accept_count()); 312 ash::DisplayManager* display_manager = 313 ash::Shell::GetInstance()->display_manager(); 314 DisplayMode mode; 315 EXPECT_TRUE( 316 display_manager->GetSelectedModeForDisplayId(display.id(), &mode)); 317 EXPECT_EQ("300x300", mode.size.ToString()); 318 EXPECT_EQ(59.0f, mode.refresh_rate); 319 } 320 321 TEST_F(ResolutionNotificationControllerTest, DisplayDisconnected) { 322 if (!SupportsMultipleDisplays()) 323 return; 324 325 UpdateDisplay("300x300#300x300%56|200x200%57," 326 "200x200#250x250%58|200x200%59|100x100%60"); 327 int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); 328 ash::DisplayManager* display_manager = 329 ash::Shell::GetInstance()->display_manager(); 330 SetDisplayResolutionAndNotify( 331 ScreenUtil::GetSecondaryDisplay(), gfx::Size(100, 100)); 332 ASSERT_TRUE(IsNotificationVisible()); 333 334 // Disconnects the secondary display and verifies it doesn't cause crashes. 335 UpdateDisplay("300x300#300x300%56|200x200%57"); 336 RunAllPendingInMessageLoop(); 337 EXPECT_FALSE(IsNotificationVisible()); 338 EXPECT_EQ(0, accept_count()); 339 DisplayMode mode; 340 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 341 gfx::Size resolution; 342 EXPECT_EQ("200x200", mode.size.ToString()); 343 EXPECT_EQ(59.0f, mode.refresh_rate); 344 } 345 346 TEST_F(ResolutionNotificationControllerTest, MultipleResolutionChange) { 347 if (!SupportsMultipleDisplays()) 348 return; 349 350 UpdateDisplay("300x300#300x300%56|200x200%57," 351 "250x250#250x250%58|200x200%59"); 352 int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); 353 ash::DisplayManager* display_manager = 354 ash::Shell::GetInstance()->display_manager(); 355 356 SetDisplayResolutionAndNotify( 357 ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); 358 EXPECT_TRUE(IsNotificationVisible()); 359 EXPECT_FALSE(controller()->DoesNotificationTimeout()); 360 DisplayMode mode; 361 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 362 EXPECT_EQ("200x200", mode.size.ToString()); 363 EXPECT_EQ(59.0f, mode.refresh_rate); 364 365 // Invokes SetDisplayResolutionAndNotify during the previous notification is 366 // visible. 367 SetDisplayResolutionAndNotify( 368 ScreenUtil::GetSecondaryDisplay(), gfx::Size(250, 250)); 369 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 370 EXPECT_EQ("250x250", mode.size.ToString()); 371 EXPECT_EQ(58.0f, mode.refresh_rate); 372 373 // Then, click the revert button. Although |old_resolution| for the second 374 // SetDisplayResolutionAndNotify is 200x200, it should revert to the original 375 // size 250x250. 376 ClickOnNotificationButton(0); 377 RunAllPendingInMessageLoop(); 378 EXPECT_FALSE(IsNotificationVisible()); 379 EXPECT_EQ(0, accept_count()); 380 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 381 EXPECT_EQ("250x250", mode.size.ToString()); 382 EXPECT_EQ(58.0f, mode.refresh_rate); 383 } 384 385 TEST_F(ResolutionNotificationControllerTest, Fallback) { 386 if (!SupportsMultipleDisplays()) 387 return; 388 389 UpdateDisplay("300x300#300x300%56|200x200%57," 390 "250x250#250x250%58|220x220%59|200x200%60"); 391 int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); 392 ash::DisplayManager* display_manager = 393 ash::Shell::GetInstance()->display_manager(); 394 ASSERT_EQ(0, accept_count()); 395 EXPECT_FALSE(IsNotificationVisible()); 396 397 // Changes the resolution and apply the result. 398 SetDisplayResolutionAndNotifyWithResolution( 399 ScreenUtil::GetSecondaryDisplay(), 400 gfx::Size(220, 220), 401 gfx::Size(200, 200)); 402 EXPECT_TRUE(IsNotificationVisible()); 403 EXPECT_FALSE(controller()->DoesNotificationTimeout()); 404 EXPECT_EQ( 405 ExpectedFallbackNotificationMessage( 406 id2, gfx::Size(220, 220), gfx::Size(200, 200)), 407 GetNotificationMessage()); 408 DisplayMode mode; 409 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 410 EXPECT_EQ("200x200", mode.size.ToString()); 411 EXPECT_EQ(60.0f, mode.refresh_rate); 412 413 // Click the revert button, which reverts to the best resolution. 414 ClickOnNotificationButton(0); 415 RunAllPendingInMessageLoop(); 416 EXPECT_FALSE(IsNotificationVisible()); 417 EXPECT_EQ(0, accept_count()); 418 EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); 419 EXPECT_EQ("250x250", mode.size.ToString()); 420 EXPECT_EQ(58.0f, mode.refresh_rate); 421 } 422 423 } // namespace ash 424