// Copyright (c) 2012 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/display_prefs.h" #include #include #include #include #include #include "ash/constants/ash_pref_names.h" #include "ash/constants/ash_switches.h" #include "ash/display/display_configuration_observer.h" #include "ash/display/display_util.h" #include "ash/display/resolution_notification_controller.h" #include "ash/display/screen_orientation_controller.h" #include "ash/display/window_tree_host_manager.h" #include "ash/session/test_session_controller_client.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" #include "ash/test/ash_test_helper.h" #include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "base/callback.h" #include "base/command_line.h" #include "base/memory/ptr_util.h" #include "base/numerics/math_constants.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/test/scoped_feature_list.h" #include "base/values.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/prefs/testing_pref_service.h" #include "components/user_manager/user_type.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/display/display_layout_builder.h" #include "ui/display/display_switches.h" #include "ui/display/manager/display_configurator.h" #include "ui/display/manager/display_layout_store.h" #include "ui/display/manager/display_manager.h" #include "ui/display/manager/display_manager_utilities.h" #include "ui/display/manager/json_converter.h" #include "ui/display/manager/managed_display_info.h" #include "ui/display/manager/test/touch_device_manager_test_api.h" #include "ui/display/screen.h" #include "ui/display/test/display_manager_test_api.h" #include "ui/display/util/display_util.h" #include "ui/events/devices/touchscreen_device.h" #include "ui/gfx/geometry/vector3d_f.h" namespace ash { using testing::DoubleEq; using testing::ElementsAre; using testing::Optional; namespace { const char kPrimaryIdKey[] = "primary-id"; bool IsRotationLocked() { return ash::Shell::Get()->screen_orientation_controller()->rotation_locked(); } bool CompareTouchAssociations( const display::TouchDeviceManager::TouchAssociationMap& map_1, const display::TouchDeviceManager::TouchAssociationMap& map_2) { if (map_1.size() != map_2.size()) return false; // Each iterator instance |entry| is a pair of type // std::pair for (const auto& entry : map_1) { if (!map_2.count(entry.first)) return false; const auto& association_info_map_1 = entry.second; const auto& association_info_map_2 = map_2.at(entry.first); if (association_info_map_1.size() != association_info_map_2.size()) return false; // Each iterator instance is a pair of type: // std::pair for (const auto& info_1 : association_info_map_1) { if (!association_info_map_2.count(info_1.first)) return false; const auto& info_2 = association_info_map_2.at(info_1.first); if (!(info_1.second.timestamp == info_2.timestamp && info_1.second.calibration_data == info_2.calibration_data)) { return false; } } } return true; } bool ComparePortAssociations( const display::TouchDeviceManager::PortAssociationMap& map_1, const display::TouchDeviceManager::PortAssociationMap& map_2) { if (map_1.size() != map_2.size()) return false; auto it_1 = map_1.begin(); auto it_2 = map_2.begin(); while (it_1 != map_1.end()) { if (it_1->first != it_2->first) return false; if (it_1->second != it_2->second) return false; it_1++; it_2++; } return true; } } // namespace class DisplayPrefsTest : public AshTestBase { public: DisplayPrefsTest(const DisplayPrefsTest&) = delete; DisplayPrefsTest& operator=(const DisplayPrefsTest&) = delete; protected: DisplayPrefsTest() = default; ~DisplayPrefsTest() override = default; void SetUp() override { AshTestBase::SetUp(); observer_ = std::make_unique(); observer_->OnDisplaysInitialized(); } void TearDown() override { observer_.reset(); AshTestBase::TearDown(); } void LoggedInAsUser() { SimulateUserLogin("user1@test.com"); } void LoggedInAsGuest() { SimulateGuestLogin(); } void LoggedInAsPublicAccount() { SimulateUserLogin("pa@test.com", user_manager::USER_TYPE_PUBLIC_ACCOUNT); } void LoadDisplayPreferences() { display_prefs()->LoadDisplayPreferences(); } // Do not use the implementation of display_prefs.cc directly to avoid // notifying the update to the system. void StoreDisplayLayoutPrefForList( const display::DisplayIdList& list, display::DisplayPlacement::Position position, int offset, int64_t primary_id) { std::string name = display::DisplayIdListToString(list); DictionaryPrefUpdate update(local_state(), prefs::kSecondaryDisplays); display::DisplayLayout display_layout; display_layout.placement_list.emplace_back(position, offset); display_layout.primary_id = primary_id; DCHECK(!name.empty()); base::Value::Dict& pref_data = update->GetDict(); base::Value::Dict* layout_dict = pref_data.EnsureDict(name); display::DisplayLayoutToJson(display_layout, *layout_dict); } void StoreDisplayPropertyForList(const display::DisplayIdList& list, const std::string& key, base::Value value) { std::string name = display::DisplayIdListToString(list); DictionaryPrefUpdate update(local_state(), prefs::kSecondaryDisplays); base::Value::Dict& pref_data = update->GetDict(); if (base::Value::Dict* existing_layout_value = pref_data.FindDict(name)) { existing_layout_value->Set(key, std::move(value)); } else { base::Value::Dict layout_dict; layout_dict.Set(key, true); pref_data.SetByDottedPath(name, std::move(layout_dict)); } } void StoreDisplayBoolPropertyForList(const display::DisplayIdList& list, const std::string& key, bool value) { StoreDisplayPropertyForList(list, key, base::Value(value)); } void StoreDisplayLayoutPrefForList(const display::DisplayIdList& list, display::DisplayPlacement::Position layout, int offset) { StoreDisplayLayoutPrefForList(list, layout, offset, list[0]); } void StoreDisplayOverscan(int64_t id, const gfx::Insets& insets) { DictionaryPrefUpdate update(local_state(), prefs::kDisplayProperties); const std::string name = base::NumberToString(id); base::Value* pref_data = update.Get(); base::Value insets_value(base::Value::Type::DICTIONARY); insets_value.SetIntKey("insets_top", insets.top()); insets_value.SetIntKey("insets_left", insets.left()); insets_value.SetIntKey("insets_bottom", insets.bottom()); insets_value.SetIntKey("insets_right", insets.right()); pref_data->SetKey(name, std::move(insets_value)); } display::Display::Rotation GetRotation() { return Shell::Get() ->display_manager() ->GetDisplayInfo(display::Display::InternalDisplayId()) .GetRotation(display::Display::RotationSource::ACCELEROMETER); } void StoreExternalDisplayMirrorInfo( const std::set& external_display_mirror_info) { ListPrefUpdate update(local_state(), prefs::kExternalDisplayMirrorInfo); base::Value* pref_data = update.Get(); pref_data->ClearList(); for (const auto& id : external_display_mirror_info) pref_data->Append(base::NumberToString(id)); } std::string GetRegisteredDisplayPlacementStr( const display::DisplayIdList& list) { return Shell::Get() ->display_manager() ->layout_store() ->GetRegisteredDisplayLayout(list) .placement_list[0] .ToString(); } const base::Value::Dict* ReadPropertiesForDisplay(int64_t display_id) { const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayProperties); const base::Value::Dict* property = properties.FindDict(base::NumberToString(display_id)); EXPECT_TRUE(property); return property; } void ExpectMixedMirrorModeParamsPrefs( int64_t source_id, const display::DisplayIdList& dest_ids) { std::vector expected_dest_id_strs; for (const int64_t id : dest_ids) { expected_dest_id_strs.push_back(base::NumberToString(id)); } SCOPED_TRACE(testing::Message() << "Expected to read kDisplayMixedMirrorModeParams with " "mirroring_source_id=" << source_id << " and mirroring_destination_ids=" << base::JoinString(expected_dest_id_strs, ",")); const base::Value::Dict& prefs = local_state()->GetValueDict(prefs::kDisplayMixedMirrorModeParams); EXPECT_THAT(prefs.FindString("mirroring_source_id"), testing::Pointee(base::NumberToString(source_id))); const auto* mirror_ids = prefs.FindList("mirroring_destination_ids"); ASSERT_TRUE(mirror_ids); display::DisplayIdList pref_dest_ids; for (const auto& value : *mirror_ids) { int64_t id; EXPECT_TRUE(base::StringToInt64(value.GetString(), &id)); pref_dest_ids.push_back(id); } EXPECT_EQ(pref_dest_ids, dest_ids); } void ExpectExternalDisplayMirrorPrefs(const std::set& display_ids) { std::vector expected_display_id_strs; for (const int64_t id : display_ids) { expected_display_id_strs.push_back(base::NumberToString(id)); } SCOPED_TRACE( testing::Message() << "Expected to read kExternalDisplayMirrorInfo with list values=" << base::JoinString(expected_display_id_strs, ",")); const base::Value::List& prefs = local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); std::set read_ids; for (const auto& value : prefs) { int64_t id; EXPECT_TRUE(base::StringToInt64(value.GetString(), &id)); read_ids.insert(id); } EXPECT_EQ(read_ids, display_ids); } display::DisplayConfigurator* display_configurator() { return Shell::Get()->display_configurator(); } DisplayPrefs* display_prefs() { return Shell::Get()->display_prefs(); } private: std::unique_ptr observer_; base::test::ScopedFeatureList scoped_feature_list_; }; class DisplayPrefsTestGuest : public DisplayPrefsTest { public: DisplayPrefsTestGuest() { set_start_session(false); } DisplayPrefsTestGuest(const DisplayPrefsTestGuest&) = delete; DisplayPrefsTestGuest& operator=(const DisplayPrefsTestGuest&) = delete; }; TEST_F(DisplayPrefsTest, ListedLayoutOverrides) { UpdateDisplay("200x100,300x200"); display::DisplayIdList list = display_manager()->GetConnectedDisplayIdList(); display::DisplayIdList dummy_list = display::test::CreateDisplayIdList2( list[0], display::GetNextSynthesizedDisplayId(list[1])); ASSERT_NE(list[0], dummy_list[1]); StoreDisplayLayoutPrefForList(list, display::DisplayPlacement::TOP, 20); StoreDisplayLayoutPrefForList(dummy_list, display::DisplayPlacement::LEFT, 30); display_prefs()->StoreDisplayPowerStateForTest( chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON); base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kFirstExecAfterBoot); LoadDisplayPreferences(); // requested_power_state_ should be chromeos::DISPLAY_POWER_ALL_ON at boot const absl::optional requested_power_state = display_configurator()->GetRequestedPowerStateForTest(); ASSERT_NE(absl::nullopt, requested_power_state); EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, *requested_power_state); // DisplayPowerState should be ignored at boot. EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, display_configurator()->GetRequestedPowerState()); Shell::Get()->display_manager()->UpdateDisplays(); // Check if the layout settings are notified to the system properly. // The new layout overrides old layout. // Inverted one of for specified pair (id1, id2). Not used for the list // (id1, dummy_id) since dummy_id is not connected right now. EXPECT_EQ("id=2200000257, parent=2200000000, top, 20", Shell::Get() ->display_manager() ->GetCurrentDisplayLayout() .placement_list[0] .ToString()); EXPECT_EQ("id=2200000257, parent=2200000000, top, 20", GetRegisteredDisplayPlacementStr(list)); EXPECT_EQ("id=2200000258, parent=2200000000, left, 30", GetRegisteredDisplayPlacementStr(dummy_list)); } TEST_F(DisplayPrefsTest, BasicStores) { WindowTreeHostManager* window_tree_host_manager = Shell::Get()->window_tree_host_manager(); int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id(); // For each configuration change, we store mirror info only for external // displays. So set internal display first before adding display. display::test::ScopedSetInternalDisplayId set_internal(display_manager(), id1); UpdateDisplay("300x200*2, 400x300#500x400|300x200*1.25"); display::test::DisplayManagerTestApi display_manager_test(display_manager()); int64_t id2 = display_manager_test.GetSecondaryDisplay().id(); int64_t dummy_id = display::GetNextSynthesizedDisplayId(id2); ASSERT_NE(id1, dummy_id); LoggedInAsUser(); display_manager()->SetLayoutForCurrentDisplays( display::test::CreateDisplayLayout(display_manager(), display::DisplayPlacement::TOP, 10)); const display::DisplayLayout& layout = display_manager()->GetCurrentDisplayLayout(); EXPECT_EQ(display::DisplayPlacement::TOP, layout.placement_list[0].position); EXPECT_EQ(10, layout.placement_list[0].offset); display::DisplayLayoutBuilder dummy_layout_builder(id1); dummy_layout_builder.SetSecondaryPlacement( dummy_id, display::DisplayPlacement::LEFT, 20); std::unique_ptr dummy_layout( dummy_layout_builder.Build()); display::DisplayIdList list = display::test::CreateDisplayIdList2(id1, dummy_id); display_prefs()->StoreDisplayLayoutPrefForTest(list, *dummy_layout); // Can't switch to a display that does not exist. window_tree_host_manager->SetPrimaryDisplayId(dummy_id); EXPECT_NE(dummy_id, display::Screen::GetScreen()->GetPrimaryDisplay().id()); window_tree_host_manager->SetOverscanInsets( id1, gfx::Insets::TLBR(10, 11, 12, 13)); display_manager()->SetDisplayRotation(id1, display::Display::ROTATE_90, display::Display::RotationSource::USER); constexpr float zoom_factor_1 = 1.f / display::kDsf_2_252; constexpr float zoom_factor_2 = 1.60f; display_manager()->UpdateZoomFactor(id1, zoom_factor_1); display_manager()->UpdateZoomFactor(id2, zoom_factor_2); // Set touch calibration data for display |id2|. ui::TouchscreenDevice touchdevice(11, ui::InputDeviceType::INPUT_DEVICE_USB, std::string("test touch device"), gfx::Size(123, 456), 1); touchdevice.phys = "5678"; display::TouchCalibrationData::CalibrationPointPairQuad point_pair_quad_1 = { {std::make_pair(gfx::Point(10, 10), gfx::Point(11, 12)), std::make_pair(gfx::Point(190, 10), gfx::Point(195, 8)), std::make_pair(gfx::Point(10, 90), gfx::Point(12, 94)), std::make_pair(gfx::Point(190, 90), gfx::Point(189, 88))}}; gfx::Size touch_size_1(200, 150); ui::TouchscreenDevice touchdevice_2(12, ui::InputDeviceType::INPUT_DEVICE_USB, std::string("test touch device 2"), gfx::Size(132, 465), 1); touchdevice_2.phys = "3456"; display::TouchCalibrationData::CalibrationPointPairQuad point_pair_quad_2 = { {std::make_pair(gfx::Point(10, 10), gfx::Point(11, 12)), std::make_pair(gfx::Point(190, 10), gfx::Point(195, 8)), std::make_pair(gfx::Point(10, 90), gfx::Point(12, 94)), std::make_pair(gfx::Point(190, 90), gfx::Point(189, 88))}}; gfx::Size touch_size_2(150, 150); // Create a 3rd touch device which has the same primary ID as the 2nd touch // device but is connected to a different port. ui::TouchscreenDevice touchdevice_3(15, ui::InputDeviceType::INPUT_DEVICE_USB, std::string("test touch device 3"), gfx::Size(231, 416), 1); touchdevice_3.phys = "1357"; display_manager()->SetTouchCalibrationData(id2, point_pair_quad_1, touch_size_1, touchdevice); display_manager()->SetTouchCalibrationData(id2, point_pair_quad_2, touch_size_2, touchdevice_2); display_manager()->SetTouchCalibrationData(id2, point_pair_quad_2, touch_size_1, touchdevice_3); const base::Value::Dict& displays = local_state()->GetValueDict(prefs::kSecondaryDisplays); std::string key = base::NumberToString(id1) + "," + base::NumberToString(id2); std::string dummy_key = base::NumberToString(id1) + "," + base::NumberToString(dummy_id); const base::Value::Dict* layout_value = displays.FindDict(dummy_key); EXPECT_TRUE(layout_value); display::DisplayLayout stored_layout; EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout)); ASSERT_EQ(1u, stored_layout.placement_list.size()); EXPECT_EQ(dummy_layout->placement_list[0].position, stored_layout.placement_list[0].position); EXPECT_EQ(dummy_layout->placement_list[0].offset, stored_layout.placement_list[0].offset); const base::Value::List* external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(0U, external_display_mirror_info->size()); const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayProperties); const base::Value::Dict* property = properties.FindDict(base::NumberToString(id1)); EXPECT_TRUE(property); EXPECT_EQ(1, property->FindInt("rotation")); absl::optional display_zoom_1 = property->FindDouble("display_zoom_factor"); ASSERT_TRUE(display_zoom_1); EXPECT_NEAR(*display_zoom_1, zoom_factor_1, 0.0001); // Internal display never registered the resolution. EXPECT_FALSE(property->FindInt("width")); EXPECT_FALSE(property->FindInt("height")); EXPECT_EQ(10, property->FindInt("insets_top")); EXPECT_EQ(11, property->FindInt("insets_left")); EXPECT_EQ(12, property->FindInt("insets_bottom")); EXPECT_EQ(13, property->FindInt("insets_right")); display::TouchDeviceManager* tdm = display_manager()->touch_device_manager(); display::test::TouchDeviceManagerTestApi tdm_test_api(tdm); display::TouchDeviceManager::TouchAssociationMap expected_touch_associations_map = tdm->touch_associations(); display::TouchDeviceManager::PortAssociationMap expected_port_associations_map = tdm->port_associations(); tdm_test_api.ResetTouchDeviceManager(); EXPECT_FALSE(CompareTouchAssociations(expected_touch_associations_map, tdm->touch_associations())); EXPECT_FALSE(ComparePortAssociations(expected_port_associations_map, tdm->port_associations())); display_prefs()->LoadTouchAssociationPreferenceForTest(); display::TouchDeviceManager::TouchAssociationMap actual_touch_associations_map = tdm->touch_associations(); EXPECT_TRUE(CompareTouchAssociations(actual_touch_associations_map, expected_touch_associations_map)); EXPECT_TRUE(ComparePortAssociations(expected_port_associations_map, tdm->port_associations())); std::string touch_str; property = properties.FindDict(base::NumberToString(id2)); ASSERT_TRUE(property); EXPECT_EQ(0, property->FindInt("rotation")); absl::optional display_zoom_2 = property->FindDouble("display_zoom_factor"); ASSERT_TRUE(display_zoom_2); EXPECT_NEAR(*display_zoom_2, zoom_factor_2, 0.0001); EXPECT_FALSE(property->FindInt("insets_top")); EXPECT_FALSE(property->FindInt("insets_left")); EXPECT_FALSE(property->FindInt("insets_bottom")); EXPECT_FALSE(property->FindInt("insets_right")); // Resolution is saved only when the resolution is set // by DisplayManager::SetDisplayMode EXPECT_FALSE(property->FindInt("width")); EXPECT_FALSE(property->FindInt("height")); display::ManagedDisplayMode mode(gfx::Size(300, 200), 60.0f, false, false, 1.25f /* device_scale_factor */); display_manager()->SetDisplayMode(id2, mode); window_tree_host_manager->SetPrimaryDisplayId(id2); EXPECT_EQ(id2, display::Screen::GetScreen()->GetPrimaryDisplay().id()); property = properties.FindDict(base::NumberToString(id1)); ASSERT_TRUE(property); // Internal display shouldn't store its resolution. EXPECT_FALSE(property->FindInt("width")); EXPECT_FALSE(property->FindInt("height")); // External display's resolution must be stored this time because // it's not best. property = properties.FindDict(base::NumberToString(id2)); ASSERT_TRUE(property); EXPECT_EQ(property->FindInt("width"), 300); EXPECT_EQ(property->FindInt("height"), 200); EXPECT_EQ(property->FindInt("device-scale-factor"), 1250); // The layout is swapped. layout_value = displays.FindDict(key); ASSERT_TRUE(layout_value); EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout)); ASSERT_EQ(1u, stored_layout.placement_list.size()); const display::DisplayPlacement& stored_placement = stored_layout.placement_list[0]; EXPECT_EQ(display::DisplayPlacement::BOTTOM, stored_placement.position); EXPECT_EQ(-10, stored_placement.offset); EXPECT_EQ(id1, stored_placement.display_id); EXPECT_EQ(id2, stored_placement.parent_display_id); EXPECT_EQ(id2, stored_layout.primary_id); const std::string* primary_id_str = layout_value->FindString(kPrimaryIdKey); ASSERT_TRUE(primary_id_str); EXPECT_EQ(base::NumberToString(id2), *primary_id_str); display_manager()->SetLayoutForCurrentDisplays( display::test::CreateDisplayLayout(Shell::Get()->display_manager(), display::DisplayPlacement::BOTTOM, 20)); // Test Hardware Mirroring scenario. UpdateDisplay("1+0-300x200*2,1+0-300x200"); EXPECT_FALSE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_TRUE(display_manager()->IsInHardwareMirrorMode()); layout_value = displays.FindDict(key); ASSERT_TRUE(layout_value); EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout)); EXPECT_EQ(display::DisplayPlacement::BOTTOM, stored_placement.position); EXPECT_EQ(20, stored_placement.offset); EXPECT_EQ(id1, stored_placement.display_id); EXPECT_EQ(id2, stored_placement.parent_display_id); property = properties.FindDict(base::NumberToString(id1)); ASSERT_TRUE(property); EXPECT_FALSE(property->FindInt("width")); EXPECT_FALSE(property->FindInt("height")); external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(1U, external_display_mirror_info->size()); // ExternalDisplayInfo stores ID without output index. EXPECT_EQ(base::NumberToString(display::GetDisplayIdWithoutOutputIndex(id2)), (*external_display_mirror_info)[0].GetString()); // External display's selected resolution must not change // by mirroring. property = properties.FindDict(base::NumberToString(id2)); ASSERT_TRUE(property); EXPECT_EQ(300, property->FindInt("width")); EXPECT_EQ(200, property->FindInt("height")); // Set new display's selected resolution. display_manager()->RegisterDisplayProperty( display::GetNextSynthesizedDisplayId(id2), display::Display::ROTATE_0, nullptr, gfx::Size(500, 400), 1.0f, 1.0f, 60.f, false); UpdateDisplay("300x200*2, 600x500#600x500|500x400"); EXPECT_FALSE(display_manager()->IsInMirrorMode()); // Update key as the 2nd display gets new id. id2 = display_manager_test.GetSecondaryDisplay().id(); key = base::NumberToString(id1) + "," + base::NumberToString(id2); layout_value = displays.FindDict(key); ASSERT_TRUE(layout_value); EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout)); EXPECT_EQ(display::DisplayPlacement::RIGHT, stored_placement.position); EXPECT_EQ(0, stored_placement.offset); EXPECT_EQ(id1, stored_placement.parent_display_id); EXPECT_EQ(id2, stored_placement.display_id); primary_id_str = layout_value->FindString(kPrimaryIdKey); ASSERT_TRUE(primary_id_str); EXPECT_EQ(base::NumberToString(id1), *primary_id_str); // Best resolution should not be saved. property = properties.FindDict(base::NumberToString(id2)); ASSERT_TRUE(property); EXPECT_FALSE(property->FindInt("width")); EXPECT_FALSE(property->FindInt("height")); // Set yet another new display's selected resolution. display_manager()->RegisterDisplayProperty( display::GetNextSynthesizedDisplayId(id2), display::Display::ROTATE_0, nullptr, gfx::Size(500, 400), 1.0f, 1.0f, 60.f, false); // Disconnect 2nd display first to generate new id for external display. UpdateDisplay("300x200*2"); UpdateDisplay("300x200*2, 500x400#600x500|500x400%60.0f"); // Update key as the 2nd display gets new id. id2 = display_manager_test.GetSecondaryDisplay().id(); key = base::NumberToString(id1) + "," + base::NumberToString(id2); layout_value = displays.FindDict(key); ASSERT_TRUE(layout_value); EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout)); EXPECT_EQ(display::DisplayPlacement::RIGHT, stored_placement.position); EXPECT_EQ(0, stored_placement.offset); primary_id_str = layout_value->FindString(kPrimaryIdKey); ASSERT_TRUE(primary_id_str); EXPECT_EQ(base::NumberToString(id1), *primary_id_str); // External display's selected resolution must be updated. property = properties.FindDict(base::NumberToString(id2)); ASSERT_TRUE(property); EXPECT_EQ(500, property->FindInt("width")); EXPECT_EQ(400, property->FindInt("height")); } TEST_F(DisplayPrefsTest, PreventStore) { LoggedInAsUser(); UpdateDisplay("400x300#500x400|400x300|300x200"); int64_t id = display::Screen::GetScreen()->GetPrimaryDisplay().id(); // Set display's resolution in single display. It creates the notification and // display preferences should not stored meanwhile. Shell* shell = Shell::Get(); display::ManagedDisplayMode old_mode(gfx::Size(400, 300), 60.0f, false, false); display::ManagedDisplayMode new_mode(gfx::Size(500, 400), 60.0f, false, true); EXPECT_TRUE(shell->resolution_notification_controller() ->PrepareNotificationAndSetDisplayMode( id, old_mode, new_mode, crosapi::mojom::DisplayConfigSource::kUser, base::OnceClosure())); UpdateDisplay("500x400#500x400|400x300|300x200"); const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayProperties); const base::Value::Dict* property = properties.FindDict(base::NumberToString(id)); EXPECT_TRUE(property); EXPECT_FALSE(property->FindInt("width")); EXPECT_FALSE(property->FindInt("height")); // Revert the change. shell->resolution_notification_controller()->RevertResolutionChange(false); base::RunLoop().RunUntilIdle(); // The specified resolution will be stored by SetDisplayMode. Shell::Get()->display_manager()->SetDisplayMode( id, display::ManagedDisplayMode(gfx::Size(300, 200), 60.0f, false, true)); UpdateDisplay("300x200#500x400|400x300|300x200"); property = properties.FindDict(base::NumberToString(id)); ASSERT_TRUE(property); EXPECT_EQ(300, property->FindInt("width")); EXPECT_EQ(200, property->FindInt("height")); } TEST_F(DisplayPrefsTest, StoreForSwappedDisplay) { UpdateDisplay("200x100,300x200"); display::test::DisplayManagerTestApi display_manager_test(display_manager()); int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id(); int64_t id2 = display_manager_test.GetSecondaryDisplay().id(); LoggedInAsUser(); SwapPrimaryDisplay(); ASSERT_EQ(id1, display_manager_test.GetSecondaryDisplay().id()); std::string key = base::NumberToString(id1) + "," + base::NumberToString(id2); const base::Value::Dict& displays = local_state()->GetValueDict(prefs::kSecondaryDisplays); // Initial saved value is swapped. { const base::Value::Dict* new_value = displays.FindDict(key); EXPECT_TRUE(new_value); display::DisplayLayout stored_layout; EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); ASSERT_EQ(1u, stored_layout.placement_list.size()); const display::DisplayPlacement& stored_placement = stored_layout.placement_list[0]; EXPECT_EQ(display::DisplayPlacement::LEFT, stored_placement.position); EXPECT_EQ(0, stored_placement.offset); EXPECT_EQ(id1, stored_placement.display_id); EXPECT_EQ(id2, stored_placement.parent_display_id); EXPECT_EQ(id2, stored_layout.primary_id); } // Updating layout with primary swapped should save the correct value. { display_manager()->SetLayoutForCurrentDisplays( display::test::CreateDisplayLayout(display_manager(), display::DisplayPlacement::TOP, 10)); const base::Value::Dict* new_value = displays.FindDict(key); ASSERT_TRUE(new_value); display::DisplayLayout stored_layout; EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); ASSERT_EQ(1u, stored_layout.placement_list.size()); const display::DisplayPlacement& stored_placement = stored_layout.placement_list[0]; EXPECT_EQ(display::DisplayPlacement::TOP, stored_placement.position); EXPECT_EQ(10, stored_placement.offset); EXPECT_EQ(id1, stored_placement.display_id); EXPECT_EQ(id2, stored_placement.parent_display_id); EXPECT_EQ(id2, stored_layout.primary_id); } // Swapping primary will save the swapped value. { SwapPrimaryDisplay(); const base::Value::Dict* new_value = displays.FindDict(key); ASSERT_TRUE(new_value); display::DisplayLayout stored_layout; EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); ASSERT_EQ(1u, stored_layout.placement_list.size()); const display::DisplayPlacement& stored_placement = stored_layout.placement_list[0]; EXPECT_EQ(display::DisplayPlacement::BOTTOM, stored_placement.position); EXPECT_EQ(-10, stored_placement.offset); EXPECT_EQ(id2, stored_placement.display_id); EXPECT_EQ(id1, stored_placement.parent_display_id); EXPECT_EQ(id1, stored_layout.primary_id); } } TEST_F(DisplayPrefsTestGuest, DisplayPrefsTestGuest) { WindowTreeHostManager* window_tree_host_manager = Shell::Get()->window_tree_host_manager(); UpdateDisplay("300x200*2,300x200"); LoggedInAsGuest(); int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id(); display::test::ScopedSetInternalDisplayId set_internal( Shell::Get()->display_manager(), id1); int64_t id2 = display::test::DisplayManagerTestApi(display_manager()) .GetSecondaryDisplay() .id(); display_manager()->SetLayoutForCurrentDisplays( display::test::CreateDisplayLayout(display_manager(), display::DisplayPlacement::TOP, 10)); const float scale = 1.25f; display_manager()->UpdateZoomFactor(id1, 1.f / scale); window_tree_host_manager->SetPrimaryDisplayId(id2); int64_t new_primary = display::Screen::GetScreen()->GetPrimaryDisplay().id(); window_tree_host_manager->SetOverscanInsets( new_primary, gfx::Insets::TLBR(10, 11, 12, 13)); display_manager()->SetDisplayRotation(new_primary, display::Display::ROTATE_90, display::Display::RotationSource::USER); // Does not store the preferences locally. EXPECT_FALSE(local_state() ->FindPreference(prefs::kSecondaryDisplays) ->HasUserSetting()); EXPECT_FALSE(local_state() ->FindPreference(prefs::kDisplayProperties) ->HasUserSetting()); // Settings are still notified to the system. display::Screen* screen = display::Screen::GetScreen(); EXPECT_EQ(id2, screen->GetPrimaryDisplay().id()); const display::DisplayPlacement& placement = display_manager()->GetCurrentDisplayLayout().placement_list[0]; EXPECT_EQ(display::DisplayPlacement::BOTTOM, placement.position); EXPECT_EQ(-10, placement.offset); const display::Display& primary_display = screen->GetPrimaryDisplay(); EXPECT_EQ(gfx::Size(178, 276), primary_display.bounds().size()); EXPECT_EQ(display::Display::ROTATE_90, primary_display.rotation()); const display::ManagedDisplayInfo& info1 = display_manager()->GetDisplayInfo(id1); EXPECT_FLOAT_EQ(1.f / scale, info1.zoom_factor()); const display::ManagedDisplayInfo& info_primary = display_manager()->GetDisplayInfo(new_primary); EXPECT_EQ(display::Display::ROTATE_90, info_primary.GetActiveRotation()); EXPECT_EQ(1.0f, info_primary.zoom_factor()); } // Test case which accepts the boolean value of the // AllowMGSToStoreDisplayProperties policy. When set to True, this policy allows // managed guest session to store display configuration permanently in the local // state. When set to False or unset, the display configuration is not stored in // the local state. class DisplayPrefsPublicAccountTest : public DisplayPrefsTestGuest, public testing::WithParamInterface { public: bool IsMGSAllowedToStoreDisplayProperties() const { return GetParam(); } void SetUp() override { DisplayPrefsTestGuest::SetUp(); UpdateDisplay("300x200*2,300x200"); local_state()->SetBoolean(prefs::kAllowMGSToStoreDisplayProperties, IsMGSAllowedToStoreDisplayProperties()); } }; TEST_P(DisplayPrefsPublicAccountTest, StoreDisplayPrefsForPublicAccount) { WindowTreeHostManager* window_tree_host_manager = Shell::Get()->window_tree_host_manager(); LoggedInAsPublicAccount(); int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id(); display::test::ScopedSetInternalDisplayId set_internal( Shell::Get()->display_manager(), id1); int64_t id2 = display::test::DisplayManagerTestApi(display_manager()) .GetSecondaryDisplay() .id(); display_manager()->SetLayoutForCurrentDisplays( display::test::CreateDisplayLayout(display_manager(), display::DisplayPlacement::TOP, 10)); const float scale = 1.25f; display_manager()->UpdateZoomFactor(id1, 1.f / scale); window_tree_host_manager->SetPrimaryDisplayId(id2); const int64_t new_primary = display::Screen::GetScreen()->GetPrimaryDisplay().id(); window_tree_host_manager->SetOverscanInsets( new_primary, gfx::Insets::TLBR(10, 11, 12, 13)); display_manager()->SetDisplayRotation(new_primary, display::Display::ROTATE_90, display::Display::RotationSource::USER); // Preferences should only be stored if the AllowMGSToStoreDisplayProperties // policy was set to true. EXPECT_EQ(IsMGSAllowedToStoreDisplayProperties(), local_state() ->FindPreference(prefs::kSecondaryDisplays) ->HasUserSetting()); EXPECT_EQ(IsMGSAllowedToStoreDisplayProperties(), local_state() ->FindPreference(prefs::kDisplayProperties) ->HasUserSetting()); // Settings are still notified to the system. display::Screen* screen = display::Screen::GetScreen(); EXPECT_EQ(id2, screen->GetPrimaryDisplay().id()); const display::DisplayPlacement& placement = display_manager()->GetCurrentDisplayLayout().placement_list[0]; EXPECT_EQ(display::DisplayPlacement::BOTTOM, placement.position); EXPECT_EQ(-10, placement.offset); const display::Display& primary_display = screen->GetPrimaryDisplay(); EXPECT_EQ(gfx::Size(178, 276), primary_display.bounds().size()); EXPECT_EQ(display::Display::ROTATE_90, primary_display.rotation()); const display::ManagedDisplayInfo& info1 = display_manager()->GetDisplayInfo(id1); EXPECT_FLOAT_EQ(1.f / scale, info1.zoom_factor()); const display::ManagedDisplayInfo& info_primary = display_manager()->GetDisplayInfo(new_primary); EXPECT_EQ(display::Display::ROTATE_90, info_primary.GetActiveRotation()); EXPECT_EQ(1.0f, info_primary.zoom_factor()); } INSTANTIATE_TEST_SUITE_P(All, DisplayPrefsPublicAccountTest, testing::Bool()); TEST_F(DisplayPrefsTest, StorePowerStateNoLogin) { EXPECT_FALSE(local_state()->HasPrefPath(prefs::kDisplayPowerState)); // Stores display prefs without login, which still stores the power state. display_prefs()->MaybeStoreDisplayPrefs(); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayPowerState)); } TEST_F(DisplayPrefsTest, StorePowerStateGuest) { EXPECT_FALSE(local_state()->HasPrefPath(prefs::kDisplayPowerState)); LoggedInAsGuest(); display_prefs()->MaybeStoreDisplayPrefs(); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayPowerState)); } TEST_F(DisplayPrefsTest, StorePowerStateNormalUser) { EXPECT_FALSE(local_state()->HasPrefPath(prefs::kDisplayPowerState)); LoggedInAsUser(); display_prefs()->MaybeStoreDisplayPrefs(); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayPowerState)); } TEST_F(DisplayPrefsTest, DisplayPowerStateAfterRestart) { display_prefs()->StoreDisplayPowerStateForTest( chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON); EXPECT_EQ("internal_off_external_on", local_state()->GetString(prefs::kDisplayPowerState)); display_configurator()->reset_requested_power_state_for_test(); LoadDisplayPreferences(); EXPECT_EQ(chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON, display_configurator()->GetRequestedPowerState()); } TEST_F(DisplayPrefsTest, DontSaveAndRestoreAllOff) { display_prefs()->StoreDisplayPowerStateForTest( chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON); EXPECT_EQ("internal_off_external_on", local_state()->GetString(prefs::kDisplayPowerState)); // Don't save ALL_OFF. display_prefs()->StoreDisplayPowerStateForTest( chromeos::DISPLAY_POWER_ALL_OFF); EXPECT_EQ("internal_off_external_on", local_state()->GetString(prefs::kDisplayPowerState)); // Don't restore ALL_OFF. local_state()->SetString(prefs::kDisplayPowerState, "all_off"); display_configurator()->reset_requested_power_state_for_test(); LoadDisplayPreferences(); EXPECT_EQ(absl::nullopt, display_configurator()->GetRequestedPowerStateForTest()); } // Tests that display configuration changes caused by TabletModeController // are not saved. TEST_F(DisplayPrefsTest, DontSaveTabletModeControllerRotations) { Shell* shell = Shell::Get(); display::SetInternalDisplayIds( {display::Screen::GetScreen()->GetPrimaryDisplay().id()}); LoggedInAsUser(); // Populate the properties. display_manager()->SetDisplayRotation(display::Display::InternalDisplayId(), display::Display::ROTATE_180, display::Display::RotationSource::USER); // Reset property to avoid rotation lock display_manager()->SetDisplayRotation(display::Display::InternalDisplayId(), display::Display::ROTATE_0, display::Display::RotationSource::USER); // Open up 270 degrees to trigger tablet mode AccelerometerUpdate update; update.Set(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD, 0.0f, 0.0f, -base::kMeanGravityFloat); update.Set(ACCELEROMETER_SOURCE_SCREEN, 0.0f, base::kMeanGravityFloat, 0.0f); TabletModeController* controller = Shell::Get()->tablet_mode_controller(); controller->OnAccelerometerUpdated(update); EXPECT_TRUE(controller->InTabletMode()); // Trigger 90 degree rotation update.Set(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD, base::kMeanGravityFloat, 0.0f, 0.0f); update.Set(ACCELEROMETER_SOURCE_SCREEN, base::kMeanGravityFloat, 0.0f, 0.0f); controller->OnAccelerometerUpdated(update); shell->screen_orientation_controller()->OnAccelerometerUpdated(update); EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation()); const base::Value::Dict* properties = &local_state()->GetValueDict(prefs::kDisplayProperties); const base::Value::Dict* property = properties->FindDict( base::NumberToString(display::Display::InternalDisplayId())); EXPECT_TRUE(property); EXPECT_EQ(display::Display::ROTATE_0, property->FindInt("rotation")); // Trigger a save, the acceleration rotation should not be saved as the user // rotation. display_prefs()->MaybeStoreDisplayPrefs(); properties = &local_state()->GetValueDict(prefs::kDisplayProperties); property = properties->FindDict( base::NumberToString(display::Display::InternalDisplayId())); EXPECT_TRUE(property); EXPECT_EQ(display::Display::ROTATE_0, property->FindInt("rotation")); } // Tests that the rotation state is saved without a user being logged in. TEST_F(DisplayPrefsTest, StoreRotationStateNoLogin) { display::SetInternalDisplayIds( {display::Screen::GetScreen()->GetPrimaryDisplay().id()}); EXPECT_FALSE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); bool current_rotation_lock = IsRotationLocked(); display_prefs()->StoreDisplayRotationPrefsForTest(GetRotation(), current_rotation_lock); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayRotationLock); absl::optional rotation_lock = properties.FindBool("lock"); ASSERT_TRUE(rotation_lock.has_value()); EXPECT_EQ(current_rotation_lock, rotation_lock.value()); display::Display::Rotation current_rotation = GetCurrentInternalDisplayRotation(); EXPECT_EQ(current_rotation, properties.FindInt("orientation")); } // Tests that the rotation state is saved when a guest is logged in. TEST_F(DisplayPrefsTest, StoreRotationStateGuest) { display::SetInternalDisplayIds( {display::Screen::GetScreen()->GetPrimaryDisplay().id()}); EXPECT_FALSE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); LoggedInAsGuest(); bool current_rotation_lock = IsRotationLocked(); display_prefs()->StoreDisplayRotationPrefsForTest(GetRotation(), current_rotation_lock); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayRotationLock); absl::optional rotation_lock = properties.FindBool("lock"); ASSERT_TRUE(rotation_lock.has_value()); EXPECT_EQ(current_rotation_lock, rotation_lock.value()); display::Display::Rotation current_rotation = GetCurrentInternalDisplayRotation(); EXPECT_EQ(current_rotation, properties.FindInt("orientation")); } // Tests that the rotation state is saved when a normal user is logged in. TEST_F(DisplayPrefsTest, StoreRotationStateNormalUser) { display::SetInternalDisplayIds( {display::Screen::GetScreen()->GetPrimaryDisplay().id()}); EXPECT_FALSE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); LoggedInAsGuest(); bool current_rotation_lock = IsRotationLocked(); display_prefs()->StoreDisplayRotationPrefsForTest(GetRotation(), current_rotation_lock); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayRotationLock); absl::optional rotation_lock = properties.FindBool("lock"); ASSERT_TRUE(rotation_lock.has_value()); EXPECT_EQ(current_rotation_lock, rotation_lock.value()); display::Display::Rotation current_rotation = GetCurrentInternalDisplayRotation(); EXPECT_EQ(current_rotation, properties.FindInt("orientation")); } // Tests that rotation state is loaded without a user being logged in, and that // entering tablet mode applies the state. TEST_F(DisplayPrefsTest, LoadRotationNoLogin) { display::SetInternalDisplayIds( {display::Screen::GetScreen()->GetPrimaryDisplay().id()}); ASSERT_FALSE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); bool initial_rotation_lock = IsRotationLocked(); ASSERT_FALSE(initial_rotation_lock); display::Display::Rotation initial_rotation = GetCurrentInternalDisplayRotation(); ASSERT_EQ(display::Display::ROTATE_0, initial_rotation); display_prefs()->StoreDisplayRotationPrefsForTest(GetRotation(), initial_rotation_lock); ASSERT_TRUE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); display_prefs()->StoreDisplayRotationPrefsForTest(display::Display::ROTATE_90, true); LoadDisplayPreferences(); bool display_rotation_lock = display_manager()->registered_internal_display_rotation_lock(); bool display_rotation = display_manager()->registered_internal_display_rotation(); EXPECT_TRUE(display_rotation_lock); EXPECT_EQ(display::Display::ROTATE_90, display_rotation); bool rotation_lock = IsRotationLocked(); display::Display::Rotation before_tablet_mode_rotation = GetCurrentInternalDisplayRotation(); // Settings should not be applied until tablet mode activates EXPECT_FALSE(rotation_lock); EXPECT_EQ(display::Display::ROTATE_0, before_tablet_mode_rotation); // Open up 270 degrees to trigger tablet mode AccelerometerUpdate update; update.Set(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD, 0.0f, 0.0f, -base::kMeanGravityFloat); update.Set(ACCELEROMETER_SOURCE_SCREEN, 0.0f, base::kMeanGravityFloat, 0.0f); TabletModeController* tablet_mode_controller = Shell::Get()->tablet_mode_controller(); tablet_mode_controller->OnAccelerometerUpdated(update); EXPECT_TRUE(tablet_mode_controller->InTabletMode()); bool screen_orientation_rotation_lock = IsRotationLocked(); display::Display::Rotation tablet_mode_rotation = GetCurrentInternalDisplayRotation(); EXPECT_TRUE(screen_orientation_rotation_lock); EXPECT_EQ(display::Display::ROTATE_90, tablet_mode_rotation); } // Tests that rotation lock being set causes the rotation state to be saved. TEST_F(DisplayPrefsTest, RotationLockTriggersStore) { display::SetInternalDisplayIds( {display::Screen::GetScreen()->GetPrimaryDisplay().id()}); ASSERT_FALSE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); Shell::Get()->screen_orientation_controller()->ToggleUserRotationLock(); EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayRotationLock)); const base::Value::Dict& properties = local_state()->GetValueDict(prefs::kDisplayRotationLock); absl::optional rotation_lock = properties.FindBool("lock"); EXPECT_TRUE(rotation_lock.has_value()); } TEST_F(DisplayPrefsTest, SaveUnifiedMode) { LoggedInAsUser(); display_manager()->SetUnifiedDesktopEnabled(true); UpdateDisplay("300x200,200x100"); display::DisplayIdList list = display_manager()->GetConnectedDisplayIdList(); EXPECT_EQ(gfx::Size(700, 200), display::Screen::GetScreen()->GetPrimaryDisplay().size()); const base::Value::Dict& secondary_displays = local_state()->GetValueDict(prefs::kSecondaryDisplays); const base::Value::Dict* new_value = secondary_displays.FindDict(display::DisplayIdListToString(list)); EXPECT_TRUE(new_value); display::DisplayLayout stored_layout; EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); EXPECT_TRUE(stored_layout.default_unified); const base::Value::Dict& displays = local_state()->GetValueDict(prefs::kDisplayProperties); int64_t unified_id = display::Screen::GetScreen()->GetPrimaryDisplay().id(); new_value = displays.FindDict(base::NumberToString(unified_id)); EXPECT_FALSE(new_value); display::test::SetDisplayResolution(display_manager(), unified_id, gfx::Size(350, 100)); EXPECT_EQ(gfx::Size(350, 100), display::Screen::GetScreen()->GetPrimaryDisplay().size()); EXPECT_FALSE(displays.FindDict(base::NumberToString(unified_id))); // Mirror mode should remember if the default mode was unified. display_manager()->SetMirrorMode(display::MirrorMode::kNormal, absl::nullopt); new_value = secondary_displays.FindDict(display::DisplayIdListToString(list)); ASSERT_TRUE(new_value); EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); EXPECT_TRUE(stored_layout.default_unified); display_manager()->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); new_value = secondary_displays.FindDict(display::DisplayIdListToString(list)); ASSERT_TRUE(new_value); EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); EXPECT_TRUE(stored_layout.default_unified); // Exit unified mode. display_manager()->SetDefaultMultiDisplayModeForCurrentDisplays( display::DisplayManager::EXTENDED); new_value = secondary_displays.FindDict(display::DisplayIdListToString(list)); ASSERT_TRUE(new_value); EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout)); EXPECT_FALSE(stored_layout.default_unified); } TEST_F(DisplayPrefsTest, RestoreUnifiedMode) { const int64_t first_display_id = 210000001; const int64_t second_display_id = 220000002; display::ManagedDisplayInfo first_display_info = display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 600, 500)); display::ManagedDisplayInfo second_display_info = display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 600, 500)); std::vector display_info_list; display_info_list.emplace_back(first_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); Shell::Get()->window_tree_host_manager()->SetPrimaryDisplayId( first_display_id); EXPECT_FALSE(display_manager()->IsInUnifiedMode()); EXPECT_FALSE(display_manager()->IsInMirrorMode()); display::DisplayIdList list = display::test::CreateDisplayIdList2(first_display_id, second_display_id); StoreDisplayBoolPropertyForList(list, "default_unified", true); StoreDisplayPropertyForList( list, "primary-id", base::Value(base::NumberToString(first_display_id))); LoadDisplayPreferences(); // Should not restore to unified unless unified desktop is enabled. display_info_list.emplace_back(second_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInUnifiedMode()); // Restored to unified. display_manager()->SetUnifiedDesktopEnabled(true); StoreDisplayBoolPropertyForList(list, "default_unified", true); LoadDisplayPreferences(); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_TRUE(display_manager()->IsInUnifiedMode()); // Remove the second display. display_info_list.erase(display_info_list.end() - 1); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInUnifiedMode()); // Restored to mirror, then unified. std::set external_display_mirror_info; external_display_mirror_info.emplace( display::GetDisplayIdWithoutOutputIndex(second_display_id)); StoreExternalDisplayMirrorInfo(external_display_mirror_info); StoreDisplayBoolPropertyForList(list, "default_unified", true); LoadDisplayPreferences(); display_info_list.emplace_back(second_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_TRUE(display_manager()->IsInMirrorMode()); display_manager()->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); EXPECT_TRUE(display_manager()->IsInUnifiedMode()); // Remove the second display. display_info_list.erase(display_info_list.end() - 1); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInUnifiedMode()); // Sanity check. Restore to extended. external_display_mirror_info.clear(); StoreExternalDisplayMirrorInfo(external_display_mirror_info); StoreDisplayBoolPropertyForList(list, "default_unified", false); LoadDisplayPreferences(); display_info_list.emplace_back(second_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInMirrorMode()); EXPECT_FALSE(display_manager()->IsInUnifiedMode()); } TEST_F(DisplayPrefsTest, SaveThreeDisplays) { LoggedInAsUser(); UpdateDisplay("300x200,300x200,400x300"); display::DisplayIdList list = display_manager()->GetConnectedDisplayIdList(); ASSERT_EQ(3u, list.size()); display::DisplayLayoutBuilder builder(list[0]); builder.AddDisplayPlacement(list[1], list[0], display::DisplayPlacement::RIGHT, 0); builder.AddDisplayPlacement(list[2], list[0], display::DisplayPlacement::BOTTOM, 100); display_manager()->SetLayoutForCurrentDisplays(builder.Build()); const base::Value::Dict& secondary_displays = local_state()->GetValueDict(prefs::kSecondaryDisplays); const base::Value::Dict* new_value = secondary_displays.FindDict(display::DisplayIdListToString(list)); EXPECT_TRUE(new_value); } TEST_F(DisplayPrefsTest, RestoreThreeDisplays) { LoggedInAsUser(); int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id(); display::DisplayIdList list = display::test::CreateDisplayIdListN(id1, 3); display::DisplayLayoutBuilder builder(list[0]); builder.AddDisplayPlacement(list[1], list[0], display::DisplayPlacement::LEFT, 0); builder.AddDisplayPlacement(list[2], list[1], display::DisplayPlacement::BOTTOM, 100); display_prefs()->StoreDisplayLayoutPrefForTest(list, *builder.Build()); LoadDisplayPreferences(); UpdateDisplay("300x200,300x200,400x300"); display::DisplayIdList new_list = display_manager()->GetConnectedDisplayIdList(); ASSERT_EQ(3u, list.size()); ASSERT_EQ(list[0], new_list[0]); ASSERT_EQ(list[1], new_list[1]); ASSERT_EQ(list[2], new_list[2]); EXPECT_EQ(gfx::Rect(0, 0, 300, 200), display_manager()->GetDisplayForId(list[0]).bounds()); EXPECT_EQ(gfx::Rect(-300, 0, 300, 200), display_manager()->GetDisplayForId(list[1]).bounds()); EXPECT_EQ(gfx::Rect(-200, 200, 400, 300), display_manager()->GetDisplayForId(list[2]).bounds()); } TEST_F(DisplayPrefsTest, LegacyTouchCalibrationDataSupport) { UpdateDisplay("800x600,1200x800"); LoggedInAsUser(); int64_t id = display::Screen::GetScreen()->GetPrimaryDisplay().id(); display::TouchCalibrationData::CalibrationPointPairQuad point_pair_quad = { {std::make_pair(gfx::Point(10, 10), gfx::Point(11, 12)), std::make_pair(gfx::Point(190, 10), gfx::Point(195, 8)), std::make_pair(gfx::Point(10, 90), gfx::Point(12, 94)), std::make_pair(gfx::Point(190, 90), gfx::Point(189, 88))}}; gfx::Size touch_size(200, 150); display::TouchCalibrationData data(point_pair_quad, touch_size); display_prefs()->StoreLegacyTouchDataForTest(id, data); display::TouchDeviceManager* tdm = display_manager()->touch_device_manager(); display::test::TouchDeviceManagerTestApi tdm_test_api(tdm); tdm_test_api.ResetTouchDeviceManager(); display_prefs()->LoadTouchAssociationPreferenceForTest(); const display::TouchDeviceManager::TouchAssociationMap& association_map = tdm->touch_associations(); const display::TouchDeviceIdentifier& fallback_identifier = display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier(); EXPECT_TRUE(association_map.count(fallback_identifier)); EXPECT_TRUE(association_map.at(fallback_identifier).count(id)); EXPECT_EQ(association_map.at(fallback_identifier).at(id).calibration_data, data); int64_t id_2 = display::test::DisplayManagerTestApi(display_manager()) .GetSecondaryDisplay() .id(); gfx::Size touch_size_2(300, 300); display::TouchCalibrationData data_2(point_pair_quad, touch_size_2); const ui::TouchscreenDevice touchdevice_4( 19, ui::InputDeviceType::INPUT_DEVICE_USB, std::string("test touch device 4"), gfx::Size(231, 416), 1); display::TouchDeviceIdentifier identifier = display::TouchDeviceIdentifier::FromDevice(touchdevice_4); display_manager()->SetTouchCalibrationData(id_2, point_pair_quad, touch_size_2, touchdevice_4); EXPECT_TRUE(tdm->touch_associations().count(identifier)); EXPECT_TRUE(tdm->touch_associations().at(identifier).count(id_2)); EXPECT_EQ(tdm->touch_associations().at(identifier).at(id_2).calibration_data, data_2); tdm_test_api.ResetTouchDeviceManager(); EXPECT_TRUE(tdm->touch_associations().empty()); display_prefs()->LoadTouchAssociationPreferenceForTest(); EXPECT_TRUE(tdm->touch_associations().count(fallback_identifier)); EXPECT_TRUE(tdm->touch_associations().at(fallback_identifier).count(id)); EXPECT_EQ( tdm->touch_associations().at(fallback_identifier).at(id).calibration_data, data); EXPECT_TRUE(tdm->touch_associations().count(identifier)); EXPECT_TRUE(tdm->touch_associations().at(identifier).count(id_2)); EXPECT_EQ(tdm->touch_associations().at(identifier).at(id_2).calibration_data, data_2); } TEST_F(DisplayPrefsTest, ExternalDisplayMirrorInfo) { LoggedInAsUser(); base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kFirstExecAfterBoot); const int64_t internal_display_id = display::test::DisplayManagerTestApi(display_manager()) .SetFirstDisplayAsInternalDisplay(); constexpr int64_t first_display_id = 210000001; constexpr int64_t second_display_id = 220000002; const int64_t first_display_masked_id = display::GetDisplayIdWithoutOutputIndex(first_display_id); const int64_t second_display_masked_id = display::GetDisplayIdWithoutOutputIndex(second_display_id); display::ManagedDisplayInfo first_display_info = display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 600, 500)); display::ManagedDisplayInfo second_display_info = display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 600, 500)); std::vector display_info_list; // There's no external display now. display_info_list.push_back(display::CreateDisplayInfo( internal_display_id, gfx::Rect(0, 0, 200, 100))); display_manager()->OnNativeDisplaysChanged(display_info_list); // Add first display id to the external display mirror info. std::set external_display_mirror_info; external_display_mirror_info.emplace(first_display_masked_id); StoreExternalDisplayMirrorInfo(external_display_mirror_info); LoadDisplayPreferences(); const base::Value::List* pref_external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(1U, pref_external_display_mirror_info->size()); EXPECT_EQ(base::NumberToString(first_display_masked_id), (*pref_external_display_mirror_info)[0].GetString()); // Add first display, mirror mode restores and the external display mirror // info does not change. display_info_list.emplace_back(first_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_TRUE(display_manager()->IsInMirrorMode()); pref_external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(1U, pref_external_display_mirror_info->size()); EXPECT_EQ(base::NumberToString(first_display_masked_id), (*pref_external_display_mirror_info)[0].GetString()); // Add second display, mirror mode persists and the second display id is added // to the external display mirror info. display_info_list.emplace_back(second_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_TRUE(display_manager()->IsInMirrorMode()); pref_external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(2U, pref_external_display_mirror_info->size()); EXPECT_EQ(base::NumberToString(first_display_masked_id), (*pref_external_display_mirror_info)[0].GetString()); EXPECT_EQ(base::NumberToString(second_display_masked_id), (*pref_external_display_mirror_info)[1].GetString()); // Disconnect all external displays. display_info_list.erase(display_info_list.begin() + 1, display_info_list.end()); display_manager()->OnNativeDisplaysChanged(display_info_list); // Clear external display mirror info and only add second display id to it. external_display_mirror_info.clear(); external_display_mirror_info.emplace(second_display_masked_id); StoreExternalDisplayMirrorInfo(external_display_mirror_info); LoadDisplayPreferences(); pref_external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(1U, pref_external_display_mirror_info->size()); EXPECT_EQ(base::NumberToString(second_display_masked_id), (*pref_external_display_mirror_info)[0].GetString()); // Add first display, mirror mode is off and the external display mirror info // does not change. display_info_list.emplace_back(first_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInMirrorMode()); pref_external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(1U, pref_external_display_mirror_info->size()); EXPECT_EQ(base::NumberToString(second_display_masked_id), (*pref_external_display_mirror_info)[0].GetString()); // Add second display, mirror mode remains off and the second display id is // removed from the external display mirror info. display_info_list.emplace_back(second_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInMirrorMode()); pref_external_display_mirror_info = &local_state()->GetValueList(prefs::kExternalDisplayMirrorInfo); EXPECT_EQ(0U, pref_external_display_mirror_info->size()); } TEST_F(DisplayPrefsTest, ExternalDisplayConnectedBeforeLoadingPrefs) { LoggedInAsUser(); base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kFirstExecAfterBoot); const int64_t internal_display_id = display::test::DisplayManagerTestApi(display_manager()) .SetFirstDisplayAsInternalDisplay(); constexpr int64_t external_display_id = 210000001; display::ManagedDisplayInfo external_display_info = display::CreateDisplayInfo(external_display_id, gfx::Rect(1, 1, 600, 500)); // Both internal and external displays connect before the prefs are loaded. std::vector display_info_list; display_info_list.push_back(display::CreateDisplayInfo( internal_display_id, gfx::Rect(0, 0, 200, 100))); display_info_list.push_back(external_display_info); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_FALSE(display_manager()->IsInMirrorMode()); EXPECT_EQ(2u, display_manager()->num_connected_displays()); // Add external display id to the external display mirror info. std::set external_display_mirror_info; const int64_t external_display_masked_id = display::GetDisplayIdWithoutOutputIndex(external_display_id); external_display_mirror_info.emplace(external_display_masked_id); StoreExternalDisplayMirrorInfo(external_display_mirror_info); // Load the preferences and simulate a native display reconfiguration. Expect // that we are mirroring now. LoadDisplayPreferences(); // Simulate a change in display configuration between loading the prefs, and // reconfiguring after the prefs have been loaded. Make sure that the external // display mirror configs are not overwritten, and the loaded prefs will be // applied. display_manager()->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_TRUE(display_manager()->IsInMirrorMode()); } TEST_F(DisplayPrefsTest, DisplayMixedMirrorMode) { LoggedInAsUser(); const int64_t internal_display_id = display::test::DisplayManagerTestApi(display_manager()) .SetFirstDisplayAsInternalDisplay(); constexpr int64_t first_display_id = 210000001; constexpr int64_t second_display_id = 220000002; std::vector display_info_list; display_info_list.push_back(display::CreateDisplayInfo( internal_display_id, gfx::Rect(0, 0, 200, 100))); display_info_list.push_back( display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 600, 500))); display_info_list.push_back( display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 600, 500))); // Store mixed mirror mode parameters which specify mirroring from the // internal display to the first external display. display::DisplayIdList dst_ids; dst_ids.emplace_back(first_display_id); absl::optional mixed_params( absl::in_place, internal_display_id, dst_ids); display_prefs()->StoreDisplayMixedMirrorModeParamsForTest(mixed_params); LoadDisplayPreferences(); // Connect both first and second external display. Mixed mirror mode is // restored. display_manager()->OnNativeDisplaysChanged(display_info_list); EXPECT_TRUE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_EQ(internal_display_id, display_manager()->mirroring_source_id()); display::DisplayIdList destination_ids = display_manager()->GetMirroringDestinationDisplayIdList(); EXPECT_EQ(1U, destination_ids.size()); EXPECT_EQ(first_display_id, destination_ids[0]); // Check the preferences. const base::Value::Dict* pref_data = &local_state()->GetValueDict(prefs::kDisplayMixedMirrorModeParams); EXPECT_EQ(base::NumberToString(internal_display_id), *pref_data->FindString("mirroring_source_id")); const base::Value::List* destination_ids_list = pref_data->FindList("mirroring_destination_ids"); EXPECT_EQ(1U, destination_ids_list->size()); EXPECT_EQ(base::NumberToString(first_display_id), (*destination_ids_list)[0].GetString()); // Overwrite current mixed mirror mode with a new configuration. (Mirror from // the first external display to the second external display) dst_ids.clear(); dst_ids.emplace_back(second_display_id); absl::optional new_mixed_params( absl::in_place, first_display_id, dst_ids); display_manager()->SetMirrorMode(display::MirrorMode::kMixed, new_mixed_params); EXPECT_TRUE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_EQ(first_display_id, display_manager()->mirroring_source_id()); destination_ids = display_manager()->GetMirroringDestinationDisplayIdList(); EXPECT_EQ(1U, destination_ids.size()); EXPECT_EQ(second_display_id, destination_ids[0]); // Check the preferences. pref_data = &local_state()->GetValueDict(prefs::kDisplayMixedMirrorModeParams); EXPECT_EQ(base::NumberToString(first_display_id), *pref_data->FindString("mirroring_source_id")); destination_ids_list = pref_data->FindList("mirroring_destination_ids"); EXPECT_EQ(1U, destination_ids_list->size()); EXPECT_EQ(base::NumberToString(second_display_id), (*destination_ids_list)[0].GetString()); // Turn off mirror mode. display_manager()->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); EXPECT_FALSE(display_manager()->IsInMirrorMode()); // Check the preferences. pref_data = &local_state()->GetValueDict(prefs::kDisplayMixedMirrorModeParams); EXPECT_TRUE(pref_data->empty()); } TEST_F(DisplayPrefsTest, SaveTabletModeWithSingleDisplay) { UpdateDisplay("480x320/r@1.25"); const int64_t id0 = display::test::DisplayManagerTestApi(display_manager()) .SetFirstDisplayAsInternalDisplay(); EXPECT_FALSE(Shell::Get()->tablet_mode_controller()->InTabletMode()); const base::Value::Dict* properties = ReadPropertiesForDisplay(id0); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.25f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_90))); LoggedInAsUser(); // Turn on tablet mode. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(Shell::Get()->tablet_mode_controller()->InTabletMode()); // Change display settings. display_manager()->UpdateZoomFactor(id0, 1.5); display_manager()->SetDisplayRotation(id0, display::Display::ROTATE_270, display::Display::RotationSource::USER); // Verify the settings have been changed. EXPECT_FLOAT_EQ(display_manager()->GetDisplayInfo(id0).zoom_factor(), 1.5); EXPECT_EQ(display_manager()->GetDisplayInfo(id0).GetActiveRotation(), display::Display::ROTATE_270); properties = ReadPropertiesForDisplay(id0); // Zoom pref should store the new value. EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.5f))); // Rotation pref should remain at the original value. EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_90))); // Turn off tablet mode. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(Shell::Get()->tablet_mode_controller()->InTabletMode()); // Zoom should stay at the new value. EXPECT_FLOAT_EQ(display_manager()->GetDisplayInfo(id0).zoom_factor(), 1.5); // Rotation should restore to the original value. EXPECT_EQ(display_manager()->GetDisplayInfo(id0).GetActiveRotation(), display::Display::ROTATE_90); properties = ReadPropertiesForDisplay(id0); // Zoom pref should keep the new value. EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.5f))); // Rotation pref should remain at the original value. EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_90))); } TEST_F(DisplayPrefsTest, SaveTabletModeWithMixedExternalDisplays) { UpdateDisplay("480x320/r@1.25,640x480/l@1.3,320x240@1.2"); display::DisplayIdList ids = display_manager()->GetConnectedDisplayIdList(); display::test::DisplayManagerTestApi(display_manager()) .SetFirstDisplayAsInternalDisplay(); // Set up mixed mirror mode. (Mirror from the first display to the second // display, and extend to third display) const display::MixedMirrorModeParams mixed_params(ids[0], {ids[1]}); display_manager()->SetMirrorMode(display::MirrorMode::kMixed, mixed_params); EXPECT_TRUE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_EQ(display_manager()->mirroring_source_id(), ids[0]); EXPECT_THAT(display_manager()->GetMirroringDestinationDisplayIdList(), ElementsAre(ids[1])); // Mixed mirror mode params are properly set. const auto& initial_mixed_params = display_manager()->mixed_mirror_mode_params(); ASSERT_TRUE(initial_mixed_params); EXPECT_EQ(initial_mixed_params->source_id, mixed_params.source_id); EXPECT_EQ(initial_mixed_params->destination_ids, mixed_params.destination_ids); const std::set old_ext_mirror_info = display_manager()->external_display_mirror_info(); EXPECT_FALSE(old_ext_mirror_info.empty()); // Mixed mirror mode params prefs are properly saved. ExpectMixedMirrorModeParamsPrefs(ids[0], {ids[1]}); ExpectExternalDisplayMirrorPrefs(old_ext_mirror_info); EXPECT_FALSE(Shell::Get()->tablet_mode_controller()->InTabletMode()); // Verify initial stored display prefs. const base::Value::Dict* properties = ReadPropertiesForDisplay(ids[0]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.25f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_90))); properties = ReadPropertiesForDisplay(ids[1]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.3f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_270))); properties = ReadPropertiesForDisplay(ids[2]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.2f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_0))); LoggedInAsUser(); // Turn on tablet mode and make display changes. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(Shell::Get()->tablet_mode_controller()->InTabletMode()); EXPECT_TRUE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_EQ(display_manager()->mirroring_source_id(), ids[0]); EXPECT_THAT(display_manager()->GetMirroringDestinationDisplayIdList(), ElementsAre(ids[1], ids[2])); // Tablet mode forces normal mirror mode, so mixed params is empty. EXPECT_EQ(display_manager()->mixed_mirror_mode_params(), absl::nullopt); EXPECT_EQ(display_manager()->external_display_mirror_info(), old_ext_mirror_info); // Mixed mirror mode params pref should remain at the original value. ExpectMixedMirrorModeParamsPrefs(ids[0], {ids[1]}); ExpectExternalDisplayMirrorPrefs(old_ext_mirror_info); // Make changes to the display and verify. display_manager()->UpdateZoomFactor(ids[0], 1.5); display_manager()->SetDisplayRotation(ids[0], display::Display::ROTATE_180, display::Display::RotationSource::USER); EXPECT_FLOAT_EQ(display_manager()->GetDisplayInfo(ids[0]).zoom_factor(), 1.5); EXPECT_EQ(display_manager()->GetDisplayInfo(ids[0]).GetActiveRotation(), display::Display::ROTATE_180); // Simulate a reboot. LoadDisplayPreferences(); display_manager()->UpdateDisplays(); // Things should stay in tablet mode. EXPECT_TRUE(Shell::Get()->tablet_mode_controller()->InTabletMode()); EXPECT_TRUE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_EQ(display_manager()->mirroring_source_id(), ids[0]); // Currently, restarting in tablet mode reverts back to the original mixed // mirror mode, which may be surprising to users. This should be revisited by // crbug.com/733092. EXPECT_THAT(display_manager()->GetMirroringDestinationDisplayIdList(), ElementsAre(ids[1])); const auto& loaded_mixed_params = display_manager()->mixed_mirror_mode_params(); ASSERT_TRUE(loaded_mixed_params); EXPECT_EQ(loaded_mixed_params->source_id, mixed_params.source_id); EXPECT_EQ(loaded_mixed_params->destination_ids, mixed_params.destination_ids); EXPECT_EQ(display_manager()->external_display_mirror_info(), old_ext_mirror_info); // Mixed mirror mode params pref should remain at the original value. ExpectMixedMirrorModeParamsPrefs(ids[0], {ids[1]}); ExpectExternalDisplayMirrorPrefs(old_ext_mirror_info); // Check stored and loaded display prefs. properties = ReadPropertiesForDisplay(ids[0]); // Zoom factor should persist the new value. EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.5f))); EXPECT_FLOAT_EQ(display_manager()->GetDisplayInfo(ids[0]).zoom_factor(), 1.5); // Rotation should remain at the original value. EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_90))); EXPECT_EQ(display_manager()->GetDisplayInfo(ids[0]).GetActiveRotation(), display::Display::ROTATE_90); // Properties for the second display shouldn't change. properties = ReadPropertiesForDisplay(ids[1]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.3f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_270))); // Properties for the third display shouldn't change. properties = ReadPropertiesForDisplay(ids[2]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.2f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_0))); // Turn off tablet mode. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(Shell::Get()->tablet_mode_controller()->InTabletMode()); EXPECT_TRUE(display_manager()->IsInSoftwareMirrorMode()); EXPECT_EQ(display_manager()->mirroring_source_id(), ids[0]); EXPECT_THAT(display_manager()->GetMirroringDestinationDisplayIdList(), ElementsAre(ids[1])); // Original mixed mirror mode params are preserved. const auto& restored_mixed_params = display_manager()->mixed_mirror_mode_params(); ASSERT_TRUE(restored_mixed_params); EXPECT_EQ(restored_mixed_params->source_id, mixed_params.source_id); EXPECT_EQ(restored_mixed_params->destination_ids, mixed_params.destination_ids); EXPECT_EQ(display_manager()->external_display_mirror_info(), old_ext_mirror_info); // Mixed mirror mode params pref should remain at the original value. ExpectMixedMirrorModeParamsPrefs(ids[0], {ids[1]}); ExpectExternalDisplayMirrorPrefs(old_ext_mirror_info); // Check restored display prefs. properties = ReadPropertiesForDisplay(ids[0]); // Zoom factor should remain at the new value. EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.5f))); EXPECT_FLOAT_EQ(display_manager()->GetDisplayInfo(ids[0]).zoom_factor(), 1.5); // Rotation should be restored to the original value. EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_90))); EXPECT_EQ(display_manager()->GetDisplayInfo(ids[0]).GetActiveRotation(), display::Display::ROTATE_90); // Properties for the second display shouldn't change. properties = ReadPropertiesForDisplay(ids[1]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.3f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_270))); // Properties for the third display shouldn't change. properties = ReadPropertiesForDisplay(ids[2]); EXPECT_THAT(properties->FindDouble("display_zoom_factor"), Optional(DoubleEq(1.2f))); EXPECT_THAT(properties->FindInt("rotation"), Optional(static_cast(display::Display::ROTATE_0))); } } // namespace ash