|  | // 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/geometry/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_t 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_t 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) {} | 
|  |  | 
|  | ~ResolutionNotificationControllerTest() override {} | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | ash::test::AshTestBase::SetUp(); | 
|  | ResolutionNotificationController::SuppressTimerForTest(); | 
|  | } | 
|  |  | 
|  | void SetDisplayResolutionAndNotifyWithResolution( | 
|  | const display::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()); | 
|  | DisplayMode old_mode(info.size_in_pixel(), 60 /* refresh_rate */, | 
|  | false /* interlaced */, false /* native */); | 
|  | DisplayMode new_mode = old_mode; | 
|  | new_mode.size = new_resolution; | 
|  |  | 
|  | if (display_manager->SetDisplayMode(display.id(), new_mode)) { | 
|  | controller()->PrepareNotification( | 
|  | display.id(), old_mode, new_mode, | 
|  | 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_t 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 display::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_t 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_t 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 display::Display& display = | 
|  | display::Screen::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_t 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 display::Display& display = | 
|  | display::Screen::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_t 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_t 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_t 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 |