chromium/ash/display/display_prefs_unittest.cc

// Copyright 2012 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/display/display_prefs.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#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 "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/command_line.h"
#include "base/functional/callback.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/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/manager/util/display_manager_test_util.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<display::TouchDeviceIdentifier,
  //           display::TouchDeviceManager::AssociationInfoMap>
  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<int64_t, display::TouchDeviceManager::TouchAssociationInfo>
    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<DisplayConfigurationObserver>();
    observer_->OnDisplaysInitialized();
  }

  void TearDown() override {
    observer_.reset();
    AshTestBase::TearDown();
  }

  void LoggedInAsUser() { SimulateUserLogin("[email protected]"); }

  void LoggedInAsGuest() { SimulateGuestLogin(); }

  void LoggedInAsPublicAccount() {
    SimulateUserLogin("[email protected]", user_manager::UserType::kPublicAccount);
  }

  void LoadDisplayPreferences() { display_prefs()->LoadDisplayPreferences(); }

  display::ManagedDisplayInfo 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;
  }

  // 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);
    ScopedDictPrefUpdate 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* layout_dict = update->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);

    ScopedDictPrefUpdate update(local_state(), prefs::kSecondaryDisplays);
    if (base::Value::Dict* existing_layout_value = update->FindDict(name)) {
      existing_layout_value->Set(key, std::move(value));
    } else {
      base::Value::Dict layout_dict;
      layout_dict.Set(key, true);
      update->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) {
    ScopedDictPrefUpdate update(local_state(), prefs::kDisplayProperties);
    const std::string name = base::NumberToString(id);

    base::Value::Dict insets_value;
    insets_value.Set("insets_top", insets.top());
    insets_value.Set("insets_left", insets.left());
    insets_value.Set("insets_bottom", insets.bottom());
    insets_value.Set("insets_right", insets.right());
    update->Set(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<int64_t>& external_display_mirror_info) {
    ScopedListPrefUpdate update(local_state(),
                                prefs::kExternalDisplayMirrorInfo);
    base::Value::List& pref_data = update.Get();
    pref_data.clear();
    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()->GetDict(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<std::string> 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()->GetDict(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<int64_t>& display_ids) {
    std::vector<std::string> 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()->GetList(prefs::kExternalDisplayMirrorInfo);
    std::set<int64_t> 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<display::DisplayManagerObserver> 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::SynthesizeDisplayIdFromSeed(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 std::optional<chromeos::DisplayPowerState> requested_power_state =
      display_configurator()->GetRequestedPowerStateForTest();
  ASSERT_NE(std::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::SynthesizeDisplayIdFromSeed(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<display::DisplayLayout> 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,
      /*apply_spatial_calibration=*/true);
  display_manager()->SetTouchCalibrationData(
      id2, point_pair_quad_2, touch_size_2, touchdevice_2,
      /*apply_spatial_calibration=*/true);
  display_manager()->SetTouchCalibrationData(
      id2, point_pair_quad_2, touch_size_1, touchdevice_3,
      /*apply_spatial_calibration=*/true);

  const base::Value::Dict& displays =
      local_state()->GetDict(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()->GetList(prefs::kExternalDisplayMirrorInfo);
  EXPECT_EQ(0U, external_display_mirror_info->size());

  const base::Value::Dict& properties =
      local_state()->GetDict(prefs::kDisplayProperties);
  const base::Value::Dict* property =
      properties.FindDict(base::NumberToString(id1));
  EXPECT_TRUE(property);
  EXPECT_EQ(1, property->FindInt("rotation"));

  std::optional<double> display_zoom_1 =
      property->FindDouble("display_zoom_factor");
  ASSERT_TRUE(display_zoom_1);
  EXPECT_NEAR(*display_zoom_1, zoom_factor_1, 0.0001);

  const base::Value::Dict* display_zoom_dict_1 =
      property->FindDict("display_zoom_factor_map");
  std::optional<double> display_zoom_from_map_1 =
      display_zoom_dict_1->FindDouble("300x200");
  ASSERT_TRUE(display_zoom_from_map_1);
  EXPECT_NEAR(*display_zoom_from_map_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"));

  std::optional<double> display_zoom_2 =
      property->FindDouble("display_zoom_factor");
  ASSERT_TRUE(display_zoom_2);
  EXPECT_NEAR(*display_zoom_2, zoom_factor_2, 0.0001);

  const base::Value::Dict* display_zoom_dict_2 =
      property->FindDict("display_zoom_factor_map");
  std::optional<double> display_zoom_from_map_2 =
      display_zoom_dict_2->FindDouble("500x400");
  ASSERT_TRUE(display_zoom_from_map_2);
  EXPECT_NEAR(*display_zoom_from_map_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()->GetList(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::SynthesizeDisplayIdFromSeed(id2), display::Display::ROTATE_0,
      /*overscan_insets=*/nullptr, /*resolution_in_pixels=*/gfx::Size(500, 400),
      /*device_scale_factor=*/1.0f, /*display_zoom_factor=*/1.0f,
      /*display_zoom_factor_map=*/{}, /*refresh_rate=*/60.f,
      /*is_interlaced=*/false,
      /*variable_refresh_rate_state=*/
      display::VariableRefreshRateState::kVrrNotCapable,
      /*vsync_rate_min=*/std::nullopt);

  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::SynthesizeDisplayIdFromSeed(id2), display::Display::ROTATE_0,
      /*overscan_insets=*/nullptr, /*resolution_in_pixels=*/gfx::Size(500, 400),
      /*device_scale_factor=*/1.0f, /*display_zoom_factor=*/1.0f,
      /*display_zoom_factor_map=*/{}, /*refresh_rate=*/60.f,
      /*is_interlaced=*/false,
      /*variable_refresh_rate_state=*/
      display::VariableRefreshRateState::kVrrNotCapable,
      /*vsync_rate_min=*/std::nullopt);
  // 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()->GetDict(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()->GetDict(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<bool> {
 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(std::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(display::Screen::GetScreen()->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()->GetDict(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()->GetDict(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()->GetDict(prefs::kDisplayRotationLock);
  std::optional<bool> 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()->GetDict(prefs::kDisplayRotationLock);
  std::optional<bool> 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()->GetDict(prefs::kDisplayRotationLock);
  std::optional<bool> 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(display::Screen::GetScreen()->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()->GetDict(prefs::kDisplayRotationLock);
  std::optional<bool> 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()->GetDict(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()->GetDict(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, std::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, std::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 =
      CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 600, 500));
  display::ManagedDisplayInfo second_display_info =
      CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 600, 500));
  std::vector<display::ManagedDisplayInfo> 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<int64_t> 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, std::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()->GetDict(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,
      /*apply_spatial_calibration=*/true);

  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 =
      CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 600, 500));
  display::ManagedDisplayInfo second_display_info =
      CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 600, 500));
  std::vector<display::ManagedDisplayInfo> display_info_list;

  // There's no external display now.
  display_info_list.push_back(
      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<int64_t> 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()->GetList(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()->GetList(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()->GetList(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()->GetList(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()->GetList(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()->GetList(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 =
      CreateDisplayInfo(external_display_id, gfx::Rect(1, 1, 600, 500));

  // Both internal and external displays connect before the prefs are loaded.
  std::vector<display::ManagedDisplayInfo> display_info_list;
  display_info_list.push_back(
      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<int64_t> 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, std::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::ManagedDisplayInfo> display_info_list;
  display_info_list.push_back(
      CreateDisplayInfo(internal_display_id, gfx::Rect(0, 0, 200, 100)));
  display_info_list.push_back(
      CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 600, 500)));
  display_info_list.push_back(
      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);
  std::optional<display::MixedMirrorModeParams> mixed_params(
      std::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()->GetDict(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);
  std::optional<display::MixedMirrorModeParams> new_mixed_params(
      std::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()->GetDict(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, std::nullopt);
  EXPECT_FALSE(display_manager()->IsInMirrorMode());

  // Check the preferences.
  pref_data = &local_state()->GetDict(prefs::kDisplayMixedMirrorModeParams);
  EXPECT_TRUE(pref_data->empty());
}

TEST_F(DisplayPrefsTest, SaveTabletModeWithSingleDisplay) {
  UpdateDisplay("480x320/[email protected]");

  const int64_t id0 = display::test::DisplayManagerTestApi(display_manager())
                          .SetFirstDisplayAsInternalDisplay();

  EXPECT_FALSE(display::Screen::GetScreen()->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<int>(display::Display::ROTATE_90)));

  LoggedInAsUser();

  // Turn on tablet mode.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(display::Screen::GetScreen()->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<int>(display::Display::ROTATE_90)));

  // Turn off tablet mode.
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(display::Screen::GetScreen()->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<int>(display::Display::ROTATE_90)));
}

TEST_F(DisplayPrefsTest, SaveTabletModeWithMixedExternalDisplays) {
  UpdateDisplay("480x320/[email protected],640x480/[email protected],[email protected]");

  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<int64_t> 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(display::Screen::GetScreen()->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<int>(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<int>(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<int>(display::Display::ROTATE_0)));

  LoggedInAsUser();

  // Turn on tablet mode and make display changes.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(display::Screen::GetScreen()->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(), std::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(display::Screen::GetScreen()->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<int>(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<int>(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<int>(display::Display::ROTATE_0)));

  // Turn off tablet mode.
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(display::Screen::GetScreen()->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<int>(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<int>(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<int>(display::Display::ROTATE_0)));
}

TEST_F(DisplayPrefsTest, IsDisplayAvailableInPref) {
  int64_t id = display::Screen::GetScreen()->GetPrimaryDisplay().id();

  // Display is not available in prefs before adding the display.
  EXPECT_FALSE(display_prefs()->IsDisplayAvailableInPref(id));

  // Display is available in prefs after adding the display.
  UpdateDisplay("300x200");
  EXPECT_TRUE(display_prefs()->IsDisplayAvailableInPref(id));
}

}  // namespace ash