// 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/system/screen_layout_observer.h" #include #include "ash/constants/ash_features.h" #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 "base/test/scoped_feature_list.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/display_manager_utilities.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; base::test::ScopedFeatureList scoped_feature_list_; 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()); } const message_center::Notification* ScreenLayoutObserverTest::GetDisplayNotification() const { const message_center::NotificationList::Notifications notifications = message_center::MessageCenter::Get()->GetVisibleNotifications(); for (const auto* 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::GetNextSynthesizedDisplayId(first_display_id); display::ManagedDisplayInfo first_display_info = display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 500)); display::ManagedDisplayInfo second_display_info = display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 500)); std::vector 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, absl::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, absl::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, absl::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) { scoped_feature_list_.Reset(); 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::GetNextSynthesizedDisplayId(first_display_id); display::ManagedDisplayInfo first_display_info = display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 400)); display::ManagedDisplayInfo second_display_info = display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 400)); std::vector 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, absl::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