blob: d60e626ff1145d0cdea53c402c0e5c731572a64f [file] [log] [blame]
// 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 <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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/public/cpp/ash_pref_names.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/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chromeos/constants/chromeos_switches.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/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/test/touch_device_manager_test_api.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/gfx/geometry/vector3d_f.h"
namespace ash {
namespace {
const char kPrimaryIdKey[] = "primary-id";
const char kPositionKey[] = "position";
const char kOffsetKey[] = "offset";
const char kPlacementDisplayIdKey[] = "placement.display_id";
const char kPlacementParentDisplayIdKey[] = "placement.parent_display_id";
// The mean acceleration due to gravity on Earth in m/s^2.
const float kMeanGravity = -9.80665f;
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 {
protected:
DisplayPrefsTest() {}
~DisplayPrefsTest() override {}
void SetUp() override {
disable_provide_local_state();
AshTestBase::SetUp();
DisplayPrefs::RegisterLocalStatePrefs(local_state_.registry());
display_prefs()->SetPrefServiceForTest(&local_state_);
observer_ = std::make_unique<DisplayConfigurationObserver>();
observer_->OnDisplaysInitialized();
}
void TearDown() override {
observer_.reset();
AshTestBase::TearDown();
}
void LoggedInAsUser() { SimulateUserLogin("user1@test.com"); }
void LoggedInAsGuest() { SimulateGuestLogin(); }
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::DictionaryValue* pref_data = update.Get();
std::unique_ptr<base::Value> layout_value(new base::DictionaryValue());
base::Value* value = nullptr;
if (pref_data->Get(name, &value) && value != nullptr)
layout_value.reset(value->DeepCopy());
if (display::DisplayLayoutToJson(display_layout, layout_value.get()))
pref_data->Set(name, std::move(layout_value));
}
void StoreDisplayPropertyForList(const display::DisplayIdList& list,
const std::string& key,
std::unique_ptr<base::Value> value) {
std::string name = display::DisplayIdListToString(list);
DictionaryPrefUpdate update(local_state(), prefs::kSecondaryDisplays);
base::DictionaryValue* pref_data = update.Get();
base::Value* layout_value = pref_data->FindKey(name);
if (layout_value) {
static_cast<base::DictionaryValue*>(layout_value)
->Set(key, std::move(value));
} else {
std::unique_ptr<base::DictionaryValue> layout_value(
new base::DictionaryValue());
layout_value->SetBoolean(key, value != nullptr);
pref_data->Set(name, std::move(layout_value));
}
}
void StoreDisplayBoolPropertyForList(const display::DisplayIdList& list,
const std::string& key,
bool value) {
StoreDisplayPropertyForList(list, key,
std::make_unique<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::Int64ToString(id);
base::DictionaryValue* pref_data = update.Get();
auto insets_value = std::make_unique<base::DictionaryValue>();
insets_value->SetInteger("insets_top", insets.top());
insets_value->SetInteger("insets_left", insets.left());
insets_value->SetInteger("insets_bottom", insets.bottom());
insets_value->SetInteger("insets_right", insets.right());
pref_data->Set(name, std::move(insets_value));
}
display::Display::Rotation GetRotation() {
return ash::Shell::Get()
->display_manager()
->GetDisplayInfo(display::Display::InternalDisplayId())
.GetRotation(display::Display::RotationSource::ACCELEROMETER);
}
void StoreExternalDisplayMirrorInfo(
const std::set<int64_t>& external_display_mirror_info) {
ListPrefUpdate update(local_state(), prefs::kExternalDisplayMirrorInfo);
base::ListValue* pref_data = update.Get();
pref_data->Clear();
for (const auto& id : external_display_mirror_info)
pref_data->GetList().emplace_back(base::Value(base::Int64ToString(id)));
}
std::string GetRegisteredDisplayPlacementStr(
const display::DisplayIdList& list) {
return ash::Shell::Get()
->display_manager()
->layout_store()
->GetRegisteredDisplayLayout(list)
.placement_list[0]
.ToString();
}
chromeos::DisplayPowerState GetRequestedPowerState() const {
return ash::Shell::Get()->display_configurator()->GetRequestedPowerState();
}
PrefService* local_state() { return &local_state_; }
DisplayPrefs* display_prefs() { return ash::Shell::Get()->display_prefs(); }
private:
std::unique_ptr<WindowTreeHostManager::Observer> observer_;
TestingPrefServiceSimple local_state_;
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(DisplayPrefsTest);
};
class DisplayPrefsTestGuest : public DisplayPrefsTest {
public:
DisplayPrefsTestGuest() { set_start_session(false); }
private:
DISALLOW_COPY_AND_ASSIGN(DisplayPrefsTestGuest);
};
TEST_F(DisplayPrefsTest, ListedLayoutOverrides) {
UpdateDisplay("100x100,200x200");
display::DisplayIdList list = display_manager()->GetCurrentDisplayIdList();
display::DisplayIdList dummy_list =
display::test::CreateDisplayIdList2(list[0], list[1] + 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(
chromeos::switches::kFirstExecAfterBoot);
LoadDisplayPreferences();
// requested_power_state_ should be chromeos::DISPLAY_POWER_ALL_ON at boot
const base::Optional<chromeos::DisplayPowerState> requested_power_state =
ash::Shell::Get()
->display_configurator()
->GetRequestedPowerStateForTest();
ASSERT_NE(base::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, 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=2200000001, parent=2200000000, top, 20",
Shell::Get()
->display_manager()
->GetCurrentDisplayLayout()
.placement_list[0]
.ToString());
EXPECT_EQ("id=2200000001, parent=2200000000, top, 20",
GetRegisteredDisplayPlacementStr(list));
EXPECT_EQ("id=2200000002, parent=2200000000, left, 30",
GetRegisteredDisplayPlacementStr(dummy_list));
}
TEST_F(DisplayPrefsTest, BasicStores) {
ash::WindowTreeHostManager* window_tree_host_manager =
ash::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("200x200*2, 400x300#400x400|300x200*1.25");
int64_t id2 = display_manager()->GetSecondaryDisplay().id();
int64_t dummy_id = id2 + 1;
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(10, 11, 12, 13));
display_manager()->SetDisplayRotation(id1, display::Display::ROTATE_90,
display::Display::RotationSource::USER);
constexpr float zoom_factor_1 = 1.f / 2.25f;
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|.
uint32_t id_1 = 1234;
uint32_t port_1 = 5678;
const display::TouchDeviceIdentifier touch_device_identifier_1(id_1, port_1);
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);
uint32_t id_2 = 2345;
uint32_t port_2 = 3456;
const display::TouchDeviceIdentifier touch_device_identifier_2(id_2, port_2);
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.
uint32_t port_3 = 1357;
const display::TouchDeviceIdentifier touch_device_identifier_3(id_2, port_3);
display_manager()->SetTouchCalibrationData(
id2, point_pair_quad_1, touch_size_1, touch_device_identifier_1);
display_manager()->SetTouchCalibrationData(
id2, point_pair_quad_2, touch_size_2, touch_device_identifier_2);
display_manager()->SetTouchCalibrationData(
id2, point_pair_quad_2, touch_size_1, touch_device_identifier_3);
const base::DictionaryValue* displays =
local_state()->GetDictionary(prefs::kSecondaryDisplays);
const base::DictionaryValue* layout_value = nullptr;
std::string key = base::Int64ToString(id1) + "," + base::Int64ToString(id2);
std::string dummy_key =
base::Int64ToString(id1) + "," + base::Int64ToString(dummy_id);
EXPECT_TRUE(displays->GetDictionary(dummy_key, &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::ListValue* external_display_mirror_info =
local_state()->GetList(prefs::kExternalDisplayMirrorInfo);
EXPECT_EQ(0U, external_display_mirror_info->GetSize());
const base::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayProperties);
const base::DictionaryValue* property = nullptr;
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id1), &property));
int ui_scale = 0;
int rotation = 0;
EXPECT_TRUE(property->GetInteger("rotation", &rotation));
EXPECT_TRUE(property->GetInteger("ui-scale", &ui_scale));
EXPECT_EQ(1, rotation);
EXPECT_EQ(-1000, ui_scale);
double display_zoom_1;
EXPECT_TRUE(property->GetDouble("display_zoom_factor", &display_zoom_1));
EXPECT_NEAR(display_zoom_1, zoom_factor_1, 0.0001);
// Internal display never registered the resolution.
int width = 0, height = 0;
EXPECT_FALSE(property->GetInteger("width", &width));
EXPECT_FALSE(property->GetInteger("height", &height));
int top = 0, left = 0, bottom = 0, right = 0;
EXPECT_TRUE(property->GetInteger("insets_top", &top));
EXPECT_TRUE(property->GetInteger("insets_left", &left));
EXPECT_TRUE(property->GetInteger("insets_bottom", &bottom));
EXPECT_TRUE(property->GetInteger("insets_right", &right));
EXPECT_EQ(10, top);
EXPECT_EQ(11, left);
EXPECT_EQ(12, bottom);
EXPECT_EQ(13, 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;
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id2), &property));
EXPECT_TRUE(property->GetInteger("rotation", &rotation));
EXPECT_TRUE(property->GetInteger("ui-scale", &ui_scale));
EXPECT_EQ(0, rotation);
// ui_scale works only on 2x scale factor/1st display.
EXPECT_EQ(-1000, ui_scale);
double display_zoom_2;
EXPECT_TRUE(property->GetDouble("display_zoom_factor", &display_zoom_2));
EXPECT_NEAR(display_zoom_2, zoom_factor_2, 0.0001);
EXPECT_FALSE(property->GetInteger("insets_top", &top));
EXPECT_FALSE(property->GetInteger("insets_left", &left));
EXPECT_FALSE(property->GetInteger("insets_bottom", &bottom));
EXPECT_FALSE(property->GetInteger("insets_right", &right));
// Resolution is saved only when the resolution is set
// by DisplayManager::SetDisplayMode
width = 0;
height = 0;
EXPECT_FALSE(property->GetInteger("width", &width));
EXPECT_FALSE(property->GetInteger("height", &height));
display::ManagedDisplayMode mode(gfx::Size(300, 200), 60.0f, false, true,
1.25f /* device_scale_factor */);
display_manager()->SetDisplayMode(id2, mode);
window_tree_host_manager->SetPrimaryDisplayId(id2);
EXPECT_EQ(id2, display::Screen::GetScreen()->GetPrimaryDisplay().id());
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id1), &property));
width = 0;
height = 0;
// Internal display shouldn't store its resolution.
EXPECT_FALSE(property->GetInteger("width", &width));
EXPECT_FALSE(property->GetInteger("height", &height));
// External display's resolution must be stored this time because
// it's not best.
int device_scale_factor = 0;
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id2), &property));
EXPECT_TRUE(property->GetInteger("width", &width));
EXPECT_TRUE(property->GetInteger("height", &height));
EXPECT_TRUE(
property->GetInteger("device-scale-factor", &device_scale_factor));
EXPECT_EQ(300, width);
EXPECT_EQ(200, height);
EXPECT_EQ(1250, device_scale_factor);
// The layout is swapped.
EXPECT_TRUE(displays->GetDictionary(key, &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);
if (true)
return;
std::string primary_id_str;
EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
EXPECT_EQ(base::Int64ToString(id2), primary_id_str);
display_manager()->SetLayoutForCurrentDisplays(
display::test::CreateDisplayLayout(ash::Shell::Get()->display_manager(),
display::DisplayPlacement::BOTTOM,
20));
UpdateDisplay("1+0-200x200*2,1+0-200x200");
// Mirrored.
int offset = 0;
std::string position;
EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
EXPECT_TRUE(layout_value->GetString(kPositionKey, &position));
EXPECT_EQ("bottom", position);
EXPECT_TRUE(layout_value->GetInteger(kOffsetKey, &offset));
EXPECT_EQ(20, offset);
std::string id;
EXPECT_TRUE(layout_value->GetString(kPlacementDisplayIdKey, &id));
EXPECT_EQ(base::Int64ToString(id1), id);
EXPECT_TRUE(layout_value->GetString(kPlacementParentDisplayIdKey, &id));
EXPECT_EQ(base::Int64ToString(id2), id);
EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
EXPECT_EQ(base::Int64ToString(id2), primary_id_str);
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id1), &property));
EXPECT_FALSE(property->GetInteger("width", &width));
EXPECT_FALSE(property->GetInteger("height", &height));
external_display_mirror_info =
local_state()->GetList(prefs::kExternalDisplayMirrorInfo);
EXPECT_EQ(1U, external_display_mirror_info->GetSize());
EXPECT_EQ(base::Int64ToString(id2),
external_display_mirror_info->GetList()[0].GetString());
// External display's selected resolution must not change
// by mirroring.
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id2), &property));
EXPECT_TRUE(property->GetInteger("width", &width));
EXPECT_TRUE(property->GetInteger("height", &height));
EXPECT_EQ(300, width);
EXPECT_EQ(200, height);
// Set new display's selected resolution.
display_manager()->RegisterDisplayProperty(
id2 + 1, display::Display::ROTATE_0, 1.0f, nullptr, gfx::Size(500, 400),
1.0f, 1.0f);
UpdateDisplay("200x200*2, 600x500#600x500|500x400");
// Update key as the 2nd display gets new id.
id2 = display_manager()->GetSecondaryDisplay().id();
key = base::Int64ToString(id1) + "," + base::Int64ToString(id2);
EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
EXPECT_TRUE(layout_value->GetString(kPositionKey, &position));
EXPECT_EQ("right", position);
EXPECT_TRUE(layout_value->GetInteger(kOffsetKey, &offset));
EXPECT_EQ(0, offset);
EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
EXPECT_EQ(base::Int64ToString(id1), primary_id_str);
// Best resolution should not be saved.
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id2), &property));
EXPECT_FALSE(property->GetInteger("width", &width));
EXPECT_FALSE(property->GetInteger("height", &height));
// Set yet another new display's selected resolution.
display_manager()->RegisterDisplayProperty(
id2 + 1, display::Display::ROTATE_0, 1.0f, nullptr, gfx::Size(500, 400),
1.0f, 1.0f);
// Disconnect 2nd display first to generate new id for external display.
UpdateDisplay("200x200*2");
UpdateDisplay("200x200*2, 500x400#600x500|500x400%60.0f");
// Update key as the 2nd display gets new id.
id2 = display_manager()->GetSecondaryDisplay().id();
key = base::Int64ToString(id1) + "," + base::Int64ToString(id2);
EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
EXPECT_TRUE(layout_value->GetString(kPositionKey, &position));
EXPECT_EQ("right", position);
EXPECT_TRUE(layout_value->GetInteger(kOffsetKey, &offset));
EXPECT_EQ(0, offset);
EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
EXPECT_EQ(base::Int64ToString(id1), primary_id_str);
// External display's selected resolution must be updated.
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id2), &property));
EXPECT_TRUE(property->GetInteger("width", &width));
EXPECT_TRUE(property->GetInteger("height", &height));
EXPECT_EQ(500, width);
EXPECT_EQ(400, height);
}
TEST_F(DisplayPrefsTest, PreventStore) {
ResolutionNotificationController::SuppressTimerForTest();
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.
ash::Shell* shell = ash::Shell::Get();
display::ManagedDisplayMode old_mode(gfx::Size(400, 300));
display::ManagedDisplayMode new_mode(gfx::Size(500, 400));
EXPECT_TRUE(shell->resolution_notification_controller()
->PrepareNotificationAndSetDisplayMode(id, old_mode, new_mode,
base::OnceClosure()));
UpdateDisplay("500x400#500x400|400x300|300x200");
const base::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayProperties);
const base::DictionaryValue* property = nullptr;
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id), &property));
int width = 0, height = 0;
EXPECT_FALSE(property->GetInteger("width", &width));
EXPECT_FALSE(property->GetInteger("height", &height));
// Revert the change.
shell->resolution_notification_controller()->RevertResolutionChange(false);
base::RunLoop().RunUntilIdle();
// The specified resolution will be stored by SetDisplayMode.
ash::Shell::Get()->display_manager()->SetDisplayMode(
id, display::ManagedDisplayMode(gfx::Size(300, 200), 60.0f, false, true));
UpdateDisplay("300x200#500x400|400x300|300x200");
property = nullptr;
EXPECT_TRUE(properties->GetDictionary(base::Int64ToString(id), &property));
EXPECT_TRUE(property->GetInteger("width", &width));
EXPECT_TRUE(property->GetInteger("height", &height));
EXPECT_EQ(300, width);
EXPECT_EQ(200, height);
}
TEST_F(DisplayPrefsTest, StoreForSwappedDisplay) {
UpdateDisplay("100x100,200x200");
int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id();
int64_t id2 = display_manager()->GetSecondaryDisplay().id();
LoggedInAsUser();
SwapPrimaryDisplay();
ASSERT_EQ(id1, display_manager()->GetSecondaryDisplay().id());
std::string key = base::Int64ToString(id1) + "," + base::Int64ToString(id2);
const base::DictionaryValue* displays =
local_state()->GetDictionary(prefs::kSecondaryDisplays);
// Initial saved value is swapped.
{
const base::DictionaryValue* new_value = nullptr;
EXPECT_TRUE(displays->GetDictionary(key, &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::DictionaryValue* new_value = nullptr;
EXPECT_TRUE(displays->GetDictionary(key, &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::DictionaryValue* new_value = nullptr;
EXPECT_TRUE(displays->GetDictionary(key, &new_value));
display::DisplayLayout stored_layout;
EXPECT_TRUE(displays->GetDictionary(key, &new_value));
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) {
ash::WindowTreeHostManager* window_tree_host_manager =
ash::Shell::Get()->window_tree_host_manager();
UpdateDisplay("200x200*2,200x200");
LoggedInAsGuest();
int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id();
display::test::ScopedSetInternalDisplayId set_internal(
ash::Shell::Get()->display_manager(), id1);
int64_t id2 = 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(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("178x176", primary_display.bounds().size().ToString());
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_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);
LoadDisplayPreferences();
EXPECT_EQ(chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
GetRequestedPowerState());
}
TEST_F(DisplayPrefsTest, DontSaveAndRestoreAllOff) {
display_prefs()->StoreDisplayPowerStateForTest(
chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON);
LoadDisplayPreferences();
// DisplayPowerState should be ignored at boot.
EXPECT_EQ(chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
GetRequestedPowerState());
display_prefs()->StoreDisplayPowerStateForTest(
chromeos::DISPLAY_POWER_ALL_OFF);
EXPECT_EQ(chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
GetRequestedPowerState());
EXPECT_EQ("internal_off_external_on",
local_state()->GetString(prefs::kDisplayPowerState));
// Don't try to load
local_state()->SetString(prefs::kDisplayPowerState, "all_off");
LoadDisplayPreferences();
EXPECT_EQ(chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
GetRequestedPowerState());
}
// Tests that display configuration changes caused by TabletModeController
// are not saved.
TEST_F(DisplayPrefsTest, DontSaveTabletModeControllerRotations) {
ash::Shell* shell = ash::Shell::Get();
display::Display::SetInternalDisplayId(
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
scoped_refptr<AccelerometerUpdate> update(new AccelerometerUpdate());
update->Set(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD, 0.0f, 0.0f, kMeanGravity);
update->Set(ACCELEROMETER_SOURCE_SCREEN, 0.0f, -kMeanGravity, 0.0f);
ash::TabletModeController* controller =
ash::Shell::Get()->tablet_mode_controller();
controller->OnAccelerometerUpdated(update);
EXPECT_TRUE(controller->IsTabletModeWindowManagerEnabled());
// Trigger 90 degree rotation
update->Set(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD, -kMeanGravity, 0.0f,
0.0f);
update->Set(ACCELEROMETER_SOURCE_SCREEN, -kMeanGravity, 0.0f, 0.0f);
controller->OnAccelerometerUpdated(update);
shell->screen_orientation_controller()->OnAccelerometerUpdated(update);
EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
const base::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayProperties);
const base::DictionaryValue* property = nullptr;
EXPECT_TRUE(properties->GetDictionary(
base::Int64ToString(display::Display::InternalDisplayId()), &property));
int rotation = -1;
EXPECT_TRUE(property->GetInteger("rotation", &rotation));
EXPECT_EQ(display::Display::ROTATE_0, rotation);
// Trigger a save, the acceleration rotation should not be saved as the user
// rotation.
display_prefs()->MaybeStoreDisplayPrefs();
properties = local_state()->GetDictionary(prefs::kDisplayProperties);
property = nullptr;
EXPECT_TRUE(properties->GetDictionary(
base::Int64ToString(display::Display::InternalDisplayId()), &property));
rotation = -1;
EXPECT_TRUE(property->GetInteger("rotation", &rotation));
EXPECT_EQ(display::Display::ROTATE_0, rotation);
}
// Tests that the rotation state is saved without a user being logged in.
TEST_F(DisplayPrefsTest, StoreRotationStateNoLogin) {
display::Display::SetInternalDisplayId(
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::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayRotationLock);
bool rotation_lock;
EXPECT_TRUE(properties->GetBoolean("lock", &rotation_lock));
EXPECT_EQ(current_rotation_lock, rotation_lock);
int orientation;
display::Display::Rotation current_rotation =
GetCurrentInternalDisplayRotation();
EXPECT_TRUE(properties->GetInteger("orientation", &orientation));
EXPECT_EQ(current_rotation, orientation);
}
// Tests that the rotation state is saved when a guest is logged in.
TEST_F(DisplayPrefsTest, StoreRotationStateGuest) {
display::Display::SetInternalDisplayId(
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::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayRotationLock);
bool rotation_lock;
EXPECT_TRUE(properties->GetBoolean("lock", &rotation_lock));
EXPECT_EQ(current_rotation_lock, rotation_lock);
int orientation;
display::Display::Rotation current_rotation =
GetCurrentInternalDisplayRotation();
EXPECT_TRUE(properties->GetInteger("orientation", &orientation));
EXPECT_EQ(current_rotation, orientation);
}
// Tests that the rotation state is saved when a normal user is logged in.
TEST_F(DisplayPrefsTest, StoreRotationStateNormalUser) {
display::Display::SetInternalDisplayId(
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::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayRotationLock);
bool rotation_lock;
EXPECT_TRUE(properties->GetBoolean("lock", &rotation_lock));
EXPECT_EQ(current_rotation_lock, rotation_lock);
int orientation;
display::Display::Rotation current_rotation =
GetCurrentInternalDisplayRotation();
EXPECT_TRUE(properties->GetInteger("orientation", &orientation));
EXPECT_EQ(current_rotation, 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::Display::SetInternalDisplayId(
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
scoped_refptr<AccelerometerUpdate> update(new AccelerometerUpdate());
update->Set(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD, 0.0f, 0.0f, kMeanGravity);
update->Set(ACCELEROMETER_SOURCE_SCREEN, 0.0f, -kMeanGravity, 0.0f);
ash::TabletModeController* tablet_mode_controller =
ash::Shell::Get()->tablet_mode_controller();
tablet_mode_controller->OnAccelerometerUpdated(update);
EXPECT_TRUE(tablet_mode_controller->IsTabletModeWindowManagerEnabled());
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::Display::SetInternalDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id());
ASSERT_FALSE(local_state()->HasPrefPath(prefs::kDisplayRotationLock));
ash::Shell::Get()->screen_orientation_controller()->ToggleUserRotationLock();
EXPECT_TRUE(local_state()->HasPrefPath(prefs::kDisplayRotationLock));
const base::DictionaryValue* properties =
local_state()->GetDictionary(prefs::kDisplayRotationLock);
bool rotation_lock;
EXPECT_TRUE(properties->GetBoolean("lock", &rotation_lock));
}
TEST_F(DisplayPrefsTest, SaveUnifiedMode) {
LoggedInAsUser();
display_manager()->SetUnifiedDesktopEnabled(true);
UpdateDisplay("200x200,100x100");
display::DisplayIdList list = display_manager()->GetCurrentDisplayIdList();
EXPECT_EQ(
"400x200",
display::Screen::GetScreen()->GetPrimaryDisplay().size().ToString());
const base::DictionaryValue* secondary_displays =
local_state()->GetDictionary(prefs::kSecondaryDisplays);
const base::DictionaryValue* new_value = nullptr;
EXPECT_TRUE(secondary_displays->GetDictionary(
display::DisplayIdListToString(list), &new_value));
display::DisplayLayout stored_layout;
EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout));
EXPECT_TRUE(stored_layout.default_unified);
const base::DictionaryValue* displays =
local_state()->GetDictionary(prefs::kDisplayProperties);
int64_t unified_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
EXPECT_FALSE(
displays->GetDictionary(base::Int64ToString(unified_id), &new_value));
display::test::SetDisplayResolution(display_manager(), unified_id,
gfx::Size(200, 100));
EXPECT_EQ(
"200x100",
display::Screen::GetScreen()->GetPrimaryDisplay().size().ToString());
EXPECT_FALSE(
displays->GetDictionary(base::Int64ToString(unified_id), &new_value));
// Mirror mode should remember if the default mode was unified.
display_manager()->SetMirrorMode(display::MirrorMode::kNormal, base::nullopt);
ASSERT_TRUE(secondary_displays->GetDictionary(
display::DisplayIdListToString(list), &new_value));
EXPECT_TRUE(display::JsonToDisplayLayout(*new_value, &stored_layout));
EXPECT_TRUE(stored_layout.default_unified);
display_manager()->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
ASSERT_TRUE(secondary_displays->GetDictionary(
display::DisplayIdListToString(list), &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);
ASSERT_TRUE(secondary_displays->GetDictionary(
display::DisplayIdListToString(list), &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, 500, 500));
display::ManagedDisplayInfo second_display_info =
display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 500));
std::vector<display::ManagedDisplayInfo> display_info_list;
display_info_list.emplace_back(first_display_info);
display_manager()->OnNativeDisplaysChanged(display_info_list);
ash::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",
std::make_unique<base::Value>(base::Int64ToString(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, base::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("200x200,200x200,300x300");
display::DisplayIdList list = display_manager()->GetCurrentDisplayIdList();
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::DictionaryValue* secondary_displays =
local_state()->GetDictionary(prefs::kSecondaryDisplays);
const base::DictionaryValue* new_value = nullptr;
EXPECT_TRUE(secondary_displays->GetDictionary(
display::DisplayIdListToString(list), &new_value));
}
TEST_F(DisplayPrefsTest, RestoreThreeDisplays) {
LoggedInAsUser();
int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id();
display::DisplayIdList list =
display::test::CreateDisplayIdListN(3, id1, id1 + 1, id1 + 2);
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("200x200,200x200,300x300");
display::DisplayIdList new_list =
display_manager()->GetCurrentDisplayIdList();
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, 200, 200),
display_manager()->GetDisplayForId(list[0]).bounds());
EXPECT_EQ(gfx::Rect(-200, 0, 200, 200),
display_manager()->GetDisplayForId(list[1]).bounds());
EXPECT_EQ(gfx::Rect(-100, 200, 300, 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_manager()->GetSecondaryDisplay().id();
gfx::Size touch_size_2(300, 300);
display::TouchCalibrationData data_2(point_pair_quad, touch_size_2);
display::TouchDeviceIdentifier identifier(12345);
display_manager()->SetTouchCalibrationData(id_2, point_pair_quad,
touch_size_2, identifier);
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(
chromeos::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, 500, 500));
display::ManagedDisplayInfo second_display_info =
display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 500));
std::vector<display::ManagedDisplayInfo> display_info_list;
// There's no external display now.
display_info_list.push_back(display::CreateDisplayInfo(
internal_display_id, gfx::Rect(0, 0, 100, 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::ListValue* pref_external_display_mirror_info =
local_state()->GetList(prefs::kExternalDisplayMirrorInfo);
EXPECT_EQ(1U, pref_external_display_mirror_info->GetSize());
EXPECT_EQ(base::Int64ToString(first_display_masked_id),
pref_external_display_mirror_info->GetList()[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->GetSize());
EXPECT_EQ(base::Int64ToString(first_display_masked_id),
pref_external_display_mirror_info->GetList()[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->GetSize());
EXPECT_EQ(base::Int64ToString(first_display_masked_id),
pref_external_display_mirror_info->GetList()[0].GetString());
EXPECT_EQ(base::Int64ToString(second_display_masked_id),
pref_external_display_mirror_info->GetList()[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->GetSize());
EXPECT_EQ(base::Int64ToString(second_display_masked_id),
pref_external_display_mirror_info->GetList()[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->GetSize());
EXPECT_EQ(base::Int64ToString(second_display_masked_id),
pref_external_display_mirror_info->GetList()[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->GetSize());
}
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(display::CreateDisplayInfo(
internal_display_id, gfx::Rect(0, 0, 100, 100)));
display_info_list.push_back(
display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 500)));
display_info_list.push_back(
display::CreateDisplayInfo(second_display_id, gfx::Rect(2, 2, 500, 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);
base::Optional<display::MixedMirrorModeParams> mixed_params(
base::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::DictionaryValue* pref_data =
local_state()->GetDictionary(prefs::kDisplayMixedMirrorModeParams);
EXPECT_EQ(base::Int64ToString(internal_display_id),
pref_data->FindKey("mirroring_source_id")->GetString());
const base::Value* destination_ids_value =
pref_data->FindKey("mirroring_destination_ids");
EXPECT_EQ(1U, destination_ids_value->GetList().size());
EXPECT_EQ(base::Int64ToString(first_display_id),
destination_ids_value->GetList()[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);
base::Optional<display::MixedMirrorModeParams> new_mixed_params(
base::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()->GetDictionary(prefs::kDisplayMixedMirrorModeParams);
EXPECT_EQ(base::Int64ToString(first_display_id),
pref_data->FindKey("mirroring_source_id")->GetString());
destination_ids_value = pref_data->FindKey("mirroring_destination_ids");
EXPECT_EQ(1U, destination_ids_value->GetList().size());
EXPECT_EQ(base::Int64ToString(second_display_id),
destination_ids_value->GetList()[0].GetString());
// Turn off mirror mode.
display_manager()->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
EXPECT_FALSE(display_manager()->IsInMirrorMode());
// Check the preferences.
pref_data =
local_state()->GetDictionary(prefs::kDisplayMixedMirrorModeParams);
EXPECT_TRUE(pref_data->empty());
}
} // namespace ash