| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/screen_layout_observer.h" |
| |
| #include <string> |
| |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/command_line.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/chromeos/devicetype_utils.h" |
| #include "ui/display/display_layout_builder.h" |
| #include "ui/display/display_switches.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/util/display_manager_test_util.h" |
| #include "ui/display/manager/util/display_manager_util.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/display/util/display_util.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/notification_list.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "ui/views/controls/label.h" |
| |
| namespace ash { |
| |
| class ScreenLayoutObserverTest : public AshTestBase { |
| public: |
| ScreenLayoutObserverTest(); |
| |
| ScreenLayoutObserverTest(const ScreenLayoutObserverTest&) = delete; |
| ScreenLayoutObserverTest& operator=(const ScreenLayoutObserverTest&) = delete; |
| |
| ~ScreenLayoutObserverTest() override; |
| |
| // AshTestBase: |
| void SetUp() override; |
| |
| protected: |
| ScreenLayoutObserver* GetScreenLayoutObserver(); |
| void CheckUpdate(); |
| |
| void CloseNotification(); |
| std::u16string GetDisplayNotificationText() const; |
| std::u16string GetDisplayNotificationAdditionalText() const; |
| |
| std::u16string GetFirstDisplayName(); |
| |
| std::u16string GetSecondDisplayName(); |
| |
| std::u16string GetMirroringDisplayNames(); |
| |
| std::u16string GetUnifiedDisplayName(); |
| |
| bool IsNotificationShown() const; |
| |
| display::ManagedDisplayInfo CreateDisplayInfo(int64_t id, |
| const gfx::Rect& bounds); |
| |
| private: |
| const message_center::Notification* GetDisplayNotification() const; |
| }; |
| ScreenLayoutObserverTest::ScreenLayoutObserverTest() = default; |
| |
| ScreenLayoutObserverTest::~ScreenLayoutObserverTest() = default; |
| |
| void ScreenLayoutObserverTest::SetUp() { |
| AshTestBase::SetUp(); |
| GetScreenLayoutObserver()->set_show_notifications_for_testing(true); |
| } |
| |
| ScreenLayoutObserver* ScreenLayoutObserverTest::GetScreenLayoutObserver() { |
| return Shell::Get()->screen_layout_observer(); |
| } |
| |
| void ScreenLayoutObserverTest::CloseNotification() { |
| message_center::MessageCenter::Get()->RemoveNotification( |
| ScreenLayoutObserver::kNotificationId, false); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| std::u16string ScreenLayoutObserverTest::GetDisplayNotificationText() const { |
| const message_center::Notification* notification = GetDisplayNotification(); |
| return notification ? notification->title() : std::u16string(); |
| } |
| |
| std::u16string ScreenLayoutObserverTest::GetDisplayNotificationAdditionalText() |
| const { |
| const message_center::Notification* notification = GetDisplayNotification(); |
| return notification ? notification->message() : std::u16string(); |
| } |
| |
| std::u16string ScreenLayoutObserverTest::GetFirstDisplayName() { |
| return base::UTF8ToUTF16(display_manager()->GetDisplayNameForId( |
| display_manager()->first_display_id())); |
| } |
| |
| std::u16string ScreenLayoutObserverTest::GetSecondDisplayName() { |
| return base::UTF8ToUTF16(display_manager()->GetDisplayNameForId( |
| display::test::DisplayManagerTestApi(display_manager()) |
| .GetSecondaryDisplay() |
| .id())); |
| } |
| |
| std::u16string ScreenLayoutObserverTest::GetMirroringDisplayNames() { |
| DCHECK(display_manager()->IsInMirrorMode()); |
| std::u16string display_names; |
| for (auto& id : display_manager()->GetMirroringDestinationDisplayIdList()) { |
| if (!display_names.empty()) |
| display_names.append(u","); |
| display_names.append( |
| base::UTF8ToUTF16(display_manager()->GetDisplayNameForId(id))); |
| } |
| return display_names; |
| } |
| |
| std::u16string ScreenLayoutObserverTest::GetUnifiedDisplayName() { |
| return base::UTF8ToUTF16( |
| display_manager()->GetDisplayNameForId(display::kUnifiedDisplayId)); |
| } |
| |
| bool ScreenLayoutObserverTest::IsNotificationShown() const { |
| return !(GetDisplayNotificationText().empty() && |
| GetDisplayNotificationAdditionalText().empty()); |
| } |
| |
| display::ManagedDisplayInfo ScreenLayoutObserverTest::CreateDisplayInfo( |
| int64_t id, |
| const gfx::Rect& bounds) { |
| display::ManagedDisplayInfo info = display::CreateDisplayInfo(id, bounds); |
| // Each display should have at least one native mode. |
| display::ManagedDisplayMode mode(bounds.size(), /*refresh_rate=*/60.f, |
| /*is_interlaced=*/true, |
| /*native=*/true); |
| info.SetManagedDisplayModes({mode}); |
| return info; |
| } |
| |
| const message_center::Notification* |
| ScreenLayoutObserverTest::GetDisplayNotification() const { |
| const message_center::NotificationList::Notifications notifications = |
| message_center::MessageCenter::Get()->GetVisibleNotifications(); |
| for (const message_center::Notification* notification : notifications) { |
| if (notification->id() == ScreenLayoutObserver::kNotificationId) |
| return notification; |
| } |
| |
| return nullptr; |
| } |
| |
| // This test is flaky. crbug.com/1222612 |
| TEST_F(ScreenLayoutObserverTest, DISABLED_DisplayNotifications) { |
| UpdateDisplay("500x400"); |
| display::SetInternalDisplayIds({display_manager()->first_display_id()}); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| |
| // No-update |
| CloseNotification(); |
| UpdateDisplay("500x400"); |
| EXPECT_FALSE(IsNotificationShown()); |
| |
| // Extended. |
| CloseNotification(); |
| UpdateDisplay("500x400,300x200"); |
| EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, |
| GetSecondDisplayName()), |
| GetDisplayNotificationText()); |
| EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); |
| |
| const int64_t first_display_id = |
| display::Screen::GetScreen()->GetPrimaryDisplay().id(); |
| const int64_t second_display_id = |
| display::SynthesizeDisplayIdFromSeed(first_display_id); |
| display::ManagedDisplayInfo first_display_info = |
| CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 500)); |
| display::ManagedDisplayInfo second_display_info = |
| CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 500)); |
| std::vector<display::ManagedDisplayInfo> display_info_list; |
| display_info_list.push_back(first_display_info); |
| display_info_list.push_back(second_display_info); |
| display_manager()->OnNativeDisplaysChanged(display_info_list); |
| |
| display::test::DisplayManagerTestApi(Shell::Get()->display_manager()) |
| .set_maximum_display(2u); |
| UpdateDisplay("500x400,300x200,200x100"); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| EXPECT_EQ(l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM), |
| GetDisplayNotificationAdditionalText()); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| UpdateDisplay("500x400,300x200"); |
| CloseNotification(); |
| |
| // Start tablet mode and wait until display mode is updated. |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Exit mirror mode manually. Now display mode should be extending mode. |
| display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt); |
| EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT), |
| GetDisplayNotificationText()); |
| CloseNotification(); |
| |
| // Simulate that device can support at most two displays and user connects |
| // it with three displays. Because device is in tablet mode, display mode |
| // becomes mirror mode from extending mode. Under this circumstance, user is |
| // still notified of connecting more displays than maximum. See issue 827406 |
| // (https://crbug.com/827406). |
| UpdateDisplay("500x400,300x200,200x100"); |
| EXPECT_EQ(l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM), |
| GetDisplayNotificationAdditionalText()); |
| EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, |
| GetMirroringDisplayNames()), |
| GetDisplayNotificationText()); |
| |
| // Reset the parameter. Close tablet mode and wait until display mode is |
| // updated. |
| display::test::DisplayManagerTestApi(Shell::Get()->display_manager()) |
| .ResetMaximumDisplay(); |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Turn on mirror mode. |
| CloseNotification(); |
| display_manager()->SetMirrorMode(display::MirrorMode::kNormal, std::nullopt); |
| EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, |
| GetMirroringDisplayNames()), |
| GetDisplayNotificationText()); |
| EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); |
| |
| // Disconnect a display to end mirror mode. |
| CloseNotification(); |
| display_info_list.erase(display_info_list.end() - 1); |
| display_manager()->OnNativeDisplaysChanged(display_info_list); |
| EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT), |
| GetDisplayNotificationText()); |
| EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); |
| |
| // Restore mirror mode. |
| CloseNotification(); |
| display_info_list.push_back(second_display_info); |
| display_manager()->OnNativeDisplaysChanged(display_info_list); |
| EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, |
| GetMirroringDisplayNames()), |
| GetDisplayNotificationText()); |
| EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); |
| |
| // Turn off mirror mode. |
| CloseNotification(); |
| display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt); |
| EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT), |
| GetDisplayNotificationText()); |
| EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty()); |
| |
| // Enters closed lid mode. |
| UpdateDisplay("500x400@1.5,300x200"); |
| display::SetInternalDisplayIds( |
| {display::test::DisplayManagerTestApi(display_manager()) |
| .GetSecondaryDisplay() |
| .id()}); |
| UpdateDisplay("500x400@1.5"); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| } |
| |
| TEST_F(ScreenLayoutObserverTest, DisplayNotificationsDisabled) { |
| UpdateDisplay("500x400"); |
| display::SetInternalDisplayIds({display_manager()->first_display_id()}); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| |
| // Adding a display. |
| UpdateDisplay("500x400,300x200"); |
| EXPECT_FALSE(IsNotificationShown()); |
| |
| const int64_t first_display_id = |
| display::Screen::GetScreen()->GetPrimaryDisplay().id(); |
| const int64_t second_display_id = |
| display::SynthesizeDisplayIdFromSeed(first_display_id); |
| display::ManagedDisplayInfo first_display_info = |
| CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 400)); |
| display::ManagedDisplayInfo second_display_info = |
| CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 400)); |
| std::vector<display::ManagedDisplayInfo> display_info_list; |
| display_info_list.push_back(first_display_info); |
| display_info_list.push_back(second_display_info); |
| display_manager()->OnNativeDisplaysChanged(display_info_list); |
| |
| // Simulate that device can support at most two displays and user |
| // connects it with three displays. Notification should still be created to |
| // warn user of it. See issue 827406 (https://crbug.com/827406). |
| display::test::DisplayManagerTestApi(Shell::Get()->display_manager()) |
| .set_maximum_display(2u); |
| UpdateDisplay("500x400,300x200,200x100"); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| EXPECT_EQ(l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM), |
| GetDisplayNotificationAdditionalText()); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| UpdateDisplay("500x400,300x200"); |
| CloseNotification(); |
| |
| // Start tablet mode and wait until display mode is updated. |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Exit mirror mode manually. Now display mode should be extending mode. |
| display_manager()->SetMirrorMode(display::MirrorMode::kOff, std::nullopt); |
| EXPECT_FALSE(IsNotificationShown()); |
| |
| // Simulate that device can support at most two displays and user connects |
| // it with three displays. Because device is in tablet mode, display mode |
| // becomes mirror mode from extending mode. Under this circumstance, user is |
| // still notified of connecting more displays than maximum. See issue 827406 |
| // (https://crbug.com/827406). Notification should still be shown. |
| UpdateDisplay("500x400,300x200,200x100"); |
| EXPECT_EQ(l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_REMOVED_EXCEEDED_MAXIMUM), |
| GetDisplayNotificationAdditionalText()); |
| // The tablet should no longer be in mirror mode. |
| EXPECT_FALSE(display_manager()->IsInMirrorMode()); |
| EXPECT_TRUE(GetDisplayNotificationText().empty()); |
| CloseNotification(); |
| } |
| |
| // Verify that no notification is shown when overscan of a screen is changed. |
| TEST_F(ScreenLayoutObserverTest, OverscanDisplay) { |
| UpdateDisplay("500x400, 400x300"); |
| // Close the notification that is shown from initially adding a monitor. |
| CloseNotification(); |
| display::SetInternalDisplayIds({display_manager()->first_display_id()}); |
| |
| // /o creates the default overscan. |
| UpdateDisplay("500x400, 400x300/o"); |
| EXPECT_FALSE(IsNotificationShown()); |
| |
| // Reset the overscan. |
| Shell::Get()->display_manager()->SetOverscanInsets( |
| display::test::DisplayManagerTestApi(display_manager()) |
| .GetSecondaryDisplay() |
| .id(), |
| gfx::Insets()); |
| EXPECT_FALSE(IsNotificationShown()); |
| } |
| } // namespace ash |