// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/display/resolution_notification_controller.h" #include "ash/display/display_manager.h" #include "ash/screen_util.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" #include "base/bind.h" #include "base/strings/utf_string_conversions.h" #include "grit/ash_strings.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/size.h" #include "ui/message_center/message_center.h" #include "ui/message_center/notification.h" #include "ui/message_center/notification_list.h" namespace ash { namespace { base::string16 ExpectedNotificationMessage(int64 display_id, const gfx::Size& new_resolution) { return l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, base::UTF8ToUTF16( Shell::GetInstance()->display_manager()->GetDisplayNameForId( display_id)), base::UTF8ToUTF16(new_resolution.ToString())); } base::string16 ExpectedFallbackNotificationMessage( int64 display_id, const gfx::Size& specified_resolution, const gfx::Size& fallback_resolution) { return l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED, base::UTF8ToUTF16( Shell::GetInstance()->display_manager()->GetDisplayNameForId( display_id)), base::UTF8ToUTF16(specified_resolution.ToString()), base::UTF8ToUTF16(fallback_resolution.ToString())); } } // namespace class ResolutionNotificationControllerTest : public ash::test::AshTestBase { public: ResolutionNotificationControllerTest() : accept_count_(0) { } virtual ~ResolutionNotificationControllerTest() {} protected: virtual void SetUp() OVERRIDE { ash::test::AshTestBase::SetUp(); ResolutionNotificationController::SuppressTimerForTest(); } void SetDisplayResolutionAndNotifyWithResolution( const gfx::Display& display, const gfx::Size& new_resolution, const gfx::Size& actual_new_resolution) { DisplayManager* display_manager = Shell::GetInstance()->display_manager(); const DisplayInfo& info = display_manager->GetDisplayInfo(display.id()); controller()->SetDisplayResolutionAndNotify( display.id(), info.size_in_pixel(), new_resolution, base::Bind(&ResolutionNotificationControllerTest::OnAccepted, base::Unretained(this))); // OnConfigurationChanged event won't be emitted in the test environment, // so invoke UpdateDisplay() to emit that event explicitly. std::vector<DisplayInfo> info_list; for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { int64 id = display_manager->GetDisplayAt(i).id(); DisplayInfo info = display_manager->GetDisplayInfo(id); if (display.id() == id) { gfx::Rect bounds = info.bounds_in_native(); bounds.set_size(actual_new_resolution); info.SetBounds(bounds); } info_list.push_back(info); } display_manager->OnNativeDisplaysChanged(info_list); RunAllPendingInMessageLoop(); } void SetDisplayResolutionAndNotify(const gfx::Display& display, const gfx::Size& new_resolution) { SetDisplayResolutionAndNotifyWithResolution( display, new_resolution, new_resolution); } static base::string16 GetNotificationMessage() { const message_center::NotificationList::Notifications& notifications = message_center::MessageCenter::Get()->GetVisibleNotifications(); for (message_center::NotificationList::Notifications::const_iterator iter = notifications.begin(); iter != notifications.end(); ++iter) { if ((*iter)->id() == ResolutionNotificationController::kNotificationId) return (*iter)->title(); } return base::string16(); } static void ClickOnNotification() { message_center::MessageCenter::Get()->ClickOnNotification( ResolutionNotificationController::kNotificationId); } static void ClickOnNotificationButton(int index) { message_center::MessageCenter::Get()->ClickOnNotificationButton( ResolutionNotificationController::kNotificationId, index); } static void CloseNotification() { message_center::MessageCenter::Get()->RemoveNotification( ResolutionNotificationController::kNotificationId, true /* by_user */); } static bool IsNotificationVisible() { return message_center::MessageCenter::Get()->FindVisibleNotificationById( ResolutionNotificationController::kNotificationId); } static void TickTimer() { controller()->OnTimerTick(); } static ResolutionNotificationController* controller() { return Shell::GetInstance()->resolution_notification_controller(); } int accept_count() const { return accept_count_; } private: void OnAccepted() { EXPECT_FALSE(controller()->DoesNotificationTimeout()); accept_count_++; } int accept_count_; DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationControllerTest); }; // Basic behaviors and verifies it doesn't cause crashes. TEST_F(ResolutionNotificationControllerTest, Basic) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("300x300#300x300%57|200x200%58,250x250#250x250%59|200x200%60"); int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); ASSERT_EQ(0, accept_count()); EXPECT_FALSE(IsNotificationVisible()); // Changes the resolution and apply the result. SetDisplayResolutionAndNotify( ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); EXPECT_FALSE(controller()->DoesNotificationTimeout()); EXPECT_EQ(ExpectedNotificationMessage(id2, gfx::Size(200, 200)), GetNotificationMessage()); DisplayMode mode; EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(60.0, mode.refresh_rate); // Click the revert button, which reverts to the best resolution. ClickOnNotificationButton(0); RunAllPendingInMessageLoop(); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(0, accept_count()); EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("250x250", mode.size.ToString()); EXPECT_EQ(59.0, mode.refresh_rate); } TEST_F(ResolutionNotificationControllerTest, ClickMeansAccept) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("300x300#300x300%57|200x200%58,250x250#250x250%59|200x200%60"); int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); ASSERT_EQ(0, accept_count()); EXPECT_FALSE(IsNotificationVisible()); // Changes the resolution and apply the result. SetDisplayResolutionAndNotify( ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); EXPECT_FALSE(controller()->DoesNotificationTimeout()); DisplayMode mode; EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(60.0, mode.refresh_rate); // Click the revert button, which reverts the resolution. ClickOnNotification(); RunAllPendingInMessageLoop(); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(1, accept_count()); EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(60.0, mode.refresh_rate); } TEST_F(ResolutionNotificationControllerTest, AcceptButton) { if (!SupportsMultipleDisplays()) return; ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); UpdateDisplay("300x300#300x300%59|200x200%60"); const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay(); SetDisplayResolutionAndNotify(display, gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); // If there's a single display only, it will have timeout and the first button // becomes accept. EXPECT_TRUE(controller()->DoesNotificationTimeout()); ClickOnNotificationButton(0); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(1, accept_count()); DisplayMode mode; EXPECT_TRUE( display_manager->GetSelectedModeForDisplayId(display.id(), &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(60.0f, mode.refresh_rate); // In that case the second button is revert. UpdateDisplay("300x300#300x300%59|200x200%60"); SetDisplayResolutionAndNotify(display, gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); EXPECT_TRUE(controller()->DoesNotificationTimeout()); ClickOnNotificationButton(1); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(1, accept_count()); EXPECT_TRUE( display_manager->GetSelectedModeForDisplayId(display.id(), &mode)); EXPECT_EQ("300x300", mode.size.ToString()); EXPECT_EQ(59.0f, mode.refresh_rate); } TEST_F(ResolutionNotificationControllerTest, Close) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("100x100,150x150#150x150%59|200x200%60"); int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); ASSERT_EQ(0, accept_count()); EXPECT_FALSE(IsNotificationVisible()); // Changes the resolution and apply the result. SetDisplayResolutionAndNotify( ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); EXPECT_FALSE(controller()->DoesNotificationTimeout()); DisplayMode mode; EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(60.0f, mode.refresh_rate); // Close the notification (imitates clicking [x] button). Also verifies if // this does not cause a crash. See crbug.com/271784 CloseNotification(); RunAllPendingInMessageLoop(); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(1, accept_count()); } TEST_F(ResolutionNotificationControllerTest, Timeout) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("300x300#300x300%59|200x200%60"); const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay(); SetDisplayResolutionAndNotify(display, gfx::Size(200, 200)); for (int i = 0; i < ResolutionNotificationController::kTimeoutInSec; ++i) { EXPECT_TRUE(IsNotificationVisible()) << "notification is closed after " << i << "-th timer tick"; TickTimer(); RunAllPendingInMessageLoop(); } EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(0, accept_count()); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); DisplayMode mode; EXPECT_TRUE( display_manager->GetSelectedModeForDisplayId(display.id(), &mode)); EXPECT_EQ("300x300", mode.size.ToString()); EXPECT_EQ(59.0f, mode.refresh_rate); } TEST_F(ResolutionNotificationControllerTest, DisplayDisconnected) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("300x300#300x300%56|200x200%57," "200x200#250x250%58|200x200%59|100x100%60"); int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); SetDisplayResolutionAndNotify( ScreenUtil::GetSecondaryDisplay(), gfx::Size(100, 100)); ASSERT_TRUE(IsNotificationVisible()); // Disconnects the secondary display and verifies it doesn't cause crashes. UpdateDisplay("300x300#300x300%56|200x200%57"); RunAllPendingInMessageLoop(); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(0, accept_count()); DisplayMode mode; EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); gfx::Size resolution; EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(59.0f, mode.refresh_rate); } TEST_F(ResolutionNotificationControllerTest, MultipleResolutionChange) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("300x300#300x300%56|200x200%57," "250x250#250x250%58|200x200%59"); int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); SetDisplayResolutionAndNotify( ScreenUtil::GetSecondaryDisplay(), gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); EXPECT_FALSE(controller()->DoesNotificationTimeout()); DisplayMode mode; EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(59.0f, mode.refresh_rate); // Invokes SetDisplayResolutionAndNotify during the previous notification is // visible. SetDisplayResolutionAndNotify( ScreenUtil::GetSecondaryDisplay(), gfx::Size(250, 250)); EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("250x250", mode.size.ToString()); EXPECT_EQ(58.0f, mode.refresh_rate); // Then, click the revert button. Although |old_resolution| for the second // SetDisplayResolutionAndNotify is 200x200, it should revert to the original // size 250x250. ClickOnNotificationButton(0); RunAllPendingInMessageLoop(); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(0, accept_count()); EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("250x250", mode.size.ToString()); EXPECT_EQ(58.0f, mode.refresh_rate); } TEST_F(ResolutionNotificationControllerTest, Fallback) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("300x300#300x300%56|200x200%57," "250x250#250x250%58|220x220%59|200x200%60"); int64 id2 = ash::ScreenUtil::GetSecondaryDisplay().id(); ash::DisplayManager* display_manager = ash::Shell::GetInstance()->display_manager(); ASSERT_EQ(0, accept_count()); EXPECT_FALSE(IsNotificationVisible()); // Changes the resolution and apply the result. SetDisplayResolutionAndNotifyWithResolution( ScreenUtil::GetSecondaryDisplay(), gfx::Size(220, 220), gfx::Size(200, 200)); EXPECT_TRUE(IsNotificationVisible()); EXPECT_FALSE(controller()->DoesNotificationTimeout()); EXPECT_EQ( ExpectedFallbackNotificationMessage( id2, gfx::Size(220, 220), gfx::Size(200, 200)), GetNotificationMessage()); DisplayMode mode; EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("200x200", mode.size.ToString()); EXPECT_EQ(60.0f, mode.refresh_rate); // Click the revert button, which reverts to the best resolution. ClickOnNotificationButton(0); RunAllPendingInMessageLoop(); EXPECT_FALSE(IsNotificationVisible()); EXPECT_EQ(0, accept_count()); EXPECT_TRUE(display_manager->GetSelectedModeForDisplayId(id2, &mode)); EXPECT_EQ("250x250", mode.size.ToString()); EXPECT_EQ(58.0f, mode.refresh_rate); } } // namespace ash