// 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 <stddef.h>
#include <string>
#include <utility>
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/system/sys_info.h"
#include "base/values.h"
#include "components/metrics/structured/structured_events.h"
#include "components/metrics/structured/structured_metrics_client.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/display/display_features.h"
#include "ui/display/display_switches.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/util/display_manager_util.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/util/display_util.h"
#include "ui/gfx/geometry/insets.h"
#include "url/url_canon.h"
#include "url/url_util.h"
using chromeos::DisplayPowerState;
namespace ash {
namespace {
constexpr char kInsetsTopKey[] = "insets_top";
constexpr char kInsetsLeftKey[] = "insets_left";
constexpr char kInsetsBottomKey[] = "insets_bottom";
constexpr char kInsetsRightKey[] = "insets_right";
constexpr char kTouchCalibrationWidth[] = "touch_calibration_width";
constexpr char kTouchCalibrationHeight[] = "touch_calibration_height";
constexpr char kTouchCalibrationPointPairs[] = "touch_calibration_point_pairs";
constexpr char kTouchAssociationTimestamp[] = "touch_association_timestamp";
constexpr char kTouchAssociationCalibrationData[] =
"touch_association_calibration_data";
constexpr char kTouchDeviceIdentifier[] = "touch_device_identifer";
constexpr char kPortAssociationDisplayId[] = "port_association_display_id";
constexpr char kMirroringSourceId[] = "mirroring_source_id";
constexpr char kMirroringDestinationIds[] = "mirroring_destination_ids";
constexpr char kDisplayZoom[] = "display_zoom_factor";
constexpr char kDisplayZoomMap[] = "display_zoom_factor_map";
constexpr char kDisplayPowerAllOn[] = "all_on";
constexpr char kDisplayPowerInternalOffExternalOn[] =
"internal_off_external_on";
constexpr char kDisplayPowerInternalOnExternalOff[] =
"internal_on_external_off";
constexpr char kVariableRefreshRateState[] = "vrr_state";
constexpr char kVsyncRateMin[] = "vsync_rate_min";
constexpr double kDefaultDisplayZoomValue = 1.0;
// This kind of boilerplates should be done by base::JSONValueConverter but it
// doesn't support classes like gfx::Insets for now.
// TODO(mukai): fix base::JSONValueConverter and use it here.
bool ValueToInsets(const base::Value::Dict& dict, gfx::Insets* insets) {
DCHECK(insets);
std::optional<int> top = dict.FindInt(kInsetsTopKey);
std::optional<int> left = dict.FindInt(kInsetsLeftKey);
std::optional<int> bottom = dict.FindInt(kInsetsBottomKey);
std::optional<int> right = dict.FindInt(kInsetsRightKey);
if (top && left && bottom && right) {
*insets = gfx::Insets::TLBR(*top, *left, *bottom, *right);
return true;
}
return false;
}
void InsetsToValue(const gfx::Insets& insets, base::Value::Dict& dict) {
dict.Set(kInsetsTopKey, insets.top());
dict.Set(kInsetsLeftKey, insets.left());
dict.Set(kInsetsBottomKey, insets.bottom());
dict.Set(kInsetsRightKey, insets.right());
}
// Unmarshalls the string containing CalibrationPointPairQuad and populates
// |point_pair_quad| with the unmarshalled data.
bool ParseTouchCalibrationStringValue(
const std::string& str,
display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad) {
DCHECK(point_pair_quad);
int x = 0, y = 0;
std::vector<std::string> parts = base::SplitString(
str, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
size_t total = point_pair_quad->size();
gfx::Point display_point, touch_point;
for (std::size_t row = 0; row < total; row++) {
if (!base::StringToInt(parts[row * total], &x) ||
!base::StringToInt(parts[row * total + 1], &y)) {
return false;
}
display_point.SetPoint(x, y);
if (!base::StringToInt(parts[row * total + 2], &x) ||
!base::StringToInt(parts[row * total + 3], &y)) {
return false;
}
touch_point.SetPoint(x, y);
(*point_pair_quad)[row] = std::make_pair(display_point, touch_point);
}
return true;
}
// Retrieves touch calibration associated data from the dictionary and stores
// it in an instance of TouchCalibrationData struct.
bool ValueToTouchData(const base::Value::Dict& dict,
display::TouchCalibrationData* touch_calibration_data) {
display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad =
&(touch_calibration_data->point_pairs);
const std::string* str = dict.FindString(kTouchCalibrationPointPairs);
if (!str) {
return false;
}
if (!ParseTouchCalibrationStringValue(*str, point_pair_quad)) {
return false;
}
std::optional<int> width = dict.FindInt(kTouchCalibrationWidth);
std::optional<int> height = dict.FindInt(kTouchCalibrationHeight);
if (!width || !height) {
return false;
}
touch_calibration_data->bounds = gfx::Size(*width, *height);
return true;
}
// Stores the touch calibration data into the dictionary.
void TouchDataToValue(
const display::TouchCalibrationData& touch_calibration_data,
base::Value::Dict& dict) {
std::string str;
for (std::size_t row = 0; row < touch_calibration_data.point_pairs.size();
row++) {
str += base::NumberToString(
touch_calibration_data.point_pairs[row].first.x()) +
" ";
str += base::NumberToString(
touch_calibration_data.point_pairs[row].first.y()) +
" ";
str += base::NumberToString(
touch_calibration_data.point_pairs[row].second.x()) +
" ";
str += base::NumberToString(
touch_calibration_data.point_pairs[row].second.y());
if (row != touch_calibration_data.point_pairs.size() - 1) {
str += " ";
}
}
dict.Set(kTouchCalibrationPointPairs, str);
dict.Set(kTouchCalibrationWidth, touch_calibration_data.bounds.width());
dict.Set(kTouchCalibrationHeight, touch_calibration_data.bounds.height());
}
display::DisplayManager* GetDisplayManager() {
return Shell::Get()->display_manager();
}
// Returns true if the current user can write display preferences to
// Local State.
bool UserCanSaveDisplayPreference() {
SessionControllerImpl* controller = Shell::Get()->session_controller();
auto user_type = controller->GetUserType();
if (!user_type) {
return false;
}
return *user_type == user_manager::UserType::kRegular ||
*user_type == user_manager::UserType::kChild ||
*user_type == user_manager::UserType::kKioskApp ||
(*user_type == user_manager::UserType::kPublicAccount &&
Shell::Get()->local_state()->GetBoolean(
prefs::kAllowMGSToStoreDisplayProperties));
}
void LoadDisplayLayouts(PrefService* local_state) {
display::DisplayLayoutStore* layout_store =
GetDisplayManager()->layout_store();
for (const auto it : local_state->GetDict(prefs::kSecondaryDisplays)) {
std::unique_ptr<display::DisplayLayout> layout(new display::DisplayLayout);
if (!display::JsonToDisplayLayout(it.second, layout.get())) {
LOG(WARNING) << "Invalid preference value for " << it.first;
continue;
}
if (base::Contains(it.first, ",")) {
std::vector<std::string> ids_str = base::SplitString(
it.first, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::vector<int64_t> ids;
for (std::string id_str : ids_str) {
int64_t id;
if (!base::StringToInt64(id_str, &id)) {
continue;
}
ids.push_back(id);
}
display::DisplayIdList list = display::GenerateDisplayIdList(ids);
layout_store->RegisterLayoutForDisplayIdList(list, std::move(layout));
}
}
}
void LoadDisplayProperties(PrefService* local_state) {
for (const auto it : local_state->GetDict(prefs::kDisplayProperties)) {
const base::Value::Dict* dict_value = it.second.GetIfDict();
if (!dict_value) {
continue;
}
int64_t id = display::kInvalidDisplayId;
if (!base::StringToInt64(it.first, &id) ||
id == display::kInvalidDisplayId) {
continue;
}
const gfx::Insets* insets_to_set = nullptr;
display::Display::Rotation rotation = display::Display::ROTATE_0;
if (std::optional<int> rotation_value = dict_value->FindInt("rotation")) {
rotation = static_cast<display::Display::Rotation>(*rotation_value);
}
int width = dict_value->FindInt("width").value_or(0);
int height = dict_value->FindInt("height").value_or(0);
gfx::Size resolution_in_pixels(width, height);
float device_scale_factor = 1.0;
if (std::optional<int> dsf_value =
dict_value->FindInt("device-scale-factor")) {
device_scale_factor = static_cast<float>(*dsf_value) / 1000.0f;
}
// Default refresh rate is 60 Hz, until
// DisplayManager::OnNativeDisplaysChanged() updates us with the actual
// display info.
double refresh_rate = 60.0;
bool is_interlaced = false;
if (display::features::IsListAllDisplayModesEnabled()) {
refresh_rate =
dict_value->FindDouble("refresh-rate").value_or(refresh_rate);
std::optional<bool> is_interlaced_opt =
dict_value->FindBool("interlaced");
is_interlaced = is_interlaced_opt.value_or(false);
}
gfx::Insets insets;
if (ValueToInsets(*dict_value, &insets)) {
insets_to_set = &insets;
}
display::DisplaySizeToZoomFactorMap display_zoom_map;
if (const auto* display_zoom_dict = dict_value->FindDict(kDisplayZoomMap)) {
for (const auto iter : *display_zoom_dict) {
display_zoom_map[iter.first] =
iter.second.GetIfDouble().value_or(kDefaultDisplayZoomValue);
}
}
display::VariableRefreshRateState variable_refresh_rate_state =
display::VariableRefreshRateState::kVrrNotCapable;
if (std::optional<int> vrr_state_value =
dict_value->FindInt(kVariableRefreshRateState)) {
variable_refresh_rate_state =
static_cast<display::VariableRefreshRateState>(*vrr_state_value);
}
std::optional<float> vsync_rate_min = dict_value->FindDouble(kVsyncRateMin);
const double display_zoom =
dict_value->FindDouble(kDisplayZoom).value_or(kDefaultDisplayZoomValue);
GetDisplayManager()->RegisterDisplayProperty(
id, rotation, insets_to_set, resolution_in_pixels, device_scale_factor,
display_zoom, display_zoom_map, refresh_rate, is_interlaced,
variable_refresh_rate_state, vsync_rate_min);
}
}
void LoadDisplayRotationState(PrefService* local_state) {
const base::Value::Dict& properties =
local_state->GetDict(prefs::kDisplayRotationLock);
const std::optional<bool> rotation_lock = properties.FindBool("lock");
if (!rotation_lock) {
return;
}
const std::optional<int> rotation = properties.FindInt("orientation");
if (!rotation) {
return;
}
GetDisplayManager()->RegisterDisplayRotationProperties(
*rotation_lock, static_cast<display::Display::Rotation>(*rotation));
}
void LoadDisplayTouchAssociations(PrefService* local_state) {
display::TouchDeviceManager::TouchAssociationMap touch_associations;
for (const auto item :
local_state->GetDict(prefs::kDisplayTouchAssociations)) {
uint32_t identifier_raw;
if (!base::StringToUint(item.first, &identifier_raw)) {
continue;
}
display::TouchDeviceIdentifier identifier(identifier_raw);
touch_associations.emplace(
identifier, display::TouchDeviceManager::AssociationInfoMap());
if (!item.second.is_dict()) {
continue;
}
for (const auto association_info_item : item.second.GetDict()) {
display::TouchDeviceManager::TouchAssociationInfo info;
int64_t display_id;
if (!base::StringToInt64(association_info_item.first, &display_id)) {
continue;
}
std::optional<double> value =
association_info_item.second.GetDict().FindDouble(
kTouchAssociationTimestamp);
if (!value) {
continue;
}
info.timestamp = base::Time().FromSecondsSinceUnixEpoch(*value);
const base::Value::Dict* calibration_data_dict =
association_info_item.second.GetDict().FindDict(
kTouchAssociationCalibrationData);
if (!calibration_data_dict) {
continue;
}
ValueToTouchData(*calibration_data_dict, &info.calibration_data);
touch_associations.at(identifier).emplace(display_id, info);
}
}
// Retrieve all the legacy format identifiers. This should be removed after
// a couple of milestones when everything is stable.
const display::TouchDeviceIdentifier& fallback_identifier =
display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier();
for (const auto it : local_state->GetDict(prefs::kDisplayProperties)) {
const base::Value::Dict* dict_value = it.second.GetIfDict();
if (!dict_value) {
continue;
}
int64_t id = display::kInvalidDisplayId;
if (!base::StringToInt64(it.first, &id) ||
id == display::kInvalidDisplayId) {
continue;
}
display::TouchCalibrationData calibration_data;
display::TouchCalibrationData* calibration_data_to_set = nullptr;
if (ValueToTouchData(*dict_value, &calibration_data)) {
calibration_data_to_set = &calibration_data;
}
if (calibration_data_to_set) {
if (!base::Contains(touch_associations, fallback_identifier)) {
touch_associations.emplace(
fallback_identifier,
display::TouchDeviceManager::AssociationInfoMap());
}
display::TouchDeviceManager::TouchAssociationInfo info;
info.calibration_data = *calibration_data_to_set;
touch_associations.at(fallback_identifier).emplace(id, info);
}
}
// Retrieve port association information.
display::TouchDeviceManager::PortAssociationMap port_associations;
for (const auto item :
local_state->GetDict(prefs::kDisplayTouchPortAssociations)) {
// Retrieve the secondary id that identifies the port.
uint32_t secondary_id_raw;
if (!base::StringToUint(item.first, &secondary_id_raw)) {
continue;
}
if (!item.second.is_dict()) {
continue;
}
// Retrieve the touch device identifier that identifies the touch device.
const std::string* value =
item.second.GetDict().FindString(kTouchDeviceIdentifier);
if (!value) {
continue;
}
uint32_t identifier_raw;
if (!base::StringToUint(*value, &identifier_raw)) {
continue;
}
// Retrieve the display that the touch device identified by |identifier_raw|
// was associated with.
value = item.second.GetDict().FindString(kPortAssociationDisplayId);
if (!value) {
continue;
}
int64_t display_id;
if (!base::StringToInt64(*value, &display_id)) {
continue;
}
port_associations.emplace(
std::piecewise_construct,
std::forward_as_tuple(identifier_raw, secondary_id_raw),
std::forward_as_tuple(display_id));
}
GetDisplayManager()->touch_device_manager()->RegisterTouchAssociations(
touch_associations, port_associations);
}
// Loads mirror info for each external display, the info will later be used to
// restore mirror mode.
void LoadExternalDisplayMirrorInfo(PrefService* local_state) {
const base::Value::List& pref_data =
local_state->GetList(prefs::kExternalDisplayMirrorInfo);
std::set<int64_t> external_display_mirror_info;
for (const auto& it : pref_data) {
const std::string* display_id_str = it.GetIfString();
if (!display_id_str) {
continue;
}
int64_t display_id;
if (!base::StringToInt64(*display_id_str, &display_id)) {
continue;
}
external_display_mirror_info.emplace(display_id);
}
GetDisplayManager()->set_external_display_mirror_info(
external_display_mirror_info);
}
// Loads mixed mirror mode parameters which will later be used to restore mixed
// mirror mode. Return false if the parameters fail to be loaded.
void LoadDisplayMixedMirrorModeParams(PrefService* local_state) {
const base::Value::Dict& pref_data =
local_state->GetDict(prefs::kDisplayMixedMirrorModeParams);
// This function is called once for system (re)start, so the parameters should
// be empty.
DCHECK(!GetDisplayManager()->mixed_mirror_mode_params());
auto* mirroring_source_id_string = pref_data.FindString(kMirroringSourceId);
if (!mirroring_source_id_string) {
return;
}
int64_t mirroring_source_id;
if (!base::StringToInt64(*mirroring_source_id_string, &mirroring_source_id)) {
return;
}
auto* mirroring_destination_ids_list =
pref_data.FindList(kMirroringDestinationIds);
if (!mirroring_destination_ids_list) {
return;
}
display::DisplayIdList mirroring_destination_ids;
for (const auto& entry : *mirroring_destination_ids_list) {
int64_t id;
if (!base::StringToInt64(entry.GetString(), &id)) {
return;
}
mirroring_destination_ids.emplace_back(id);
}
GetDisplayManager()->set_mixed_mirror_mode_params(
std::optional<display::MixedMirrorModeParams>(
std::in_place, mirroring_source_id, mirroring_destination_ids));
}
void StoreDisplayLayoutPref(PrefService* pref_service,
const display::DisplayIdList& list,
const display::DisplayLayout& display_layout) {
DCHECK(display::DisplayLayout::Validate(list, display_layout));
std::string name = display::DisplayIdListToString(list);
ScopedDictPrefUpdate update(pref_service, prefs::kSecondaryDisplays);
base::Value::Dict* layout_dict = update->EnsureDict(name);
// This call modifies `layout_dict` in place.
display::DisplayLayoutToJson(display_layout, *layout_dict);
}
void StoreCurrentDisplayLayoutPrefs(PrefService* pref_service) {
display::DisplayManager* display_manager = GetDisplayManager();
if (!UserCanSaveDisplayPreference() ||
display_manager->num_connected_displays() < 2) {
return;
}
display::DisplayIdList list = display_manager->GetConnectedDisplayIdList();
const display::DisplayLayout& display_layout =
display_manager->layout_store()->GetRegisteredDisplayLayout(list);
if (!display::DisplayLayout::Validate(list, display_layout)) {
// We should never apply an invalid layout, if we do, it persists and the
// user has no way of fixing it except by deleting the local state.
LOG(ERROR) << "Attempting to store an invalid display layout in the local"
<< " state. Skipping.";
return;
}
StoreDisplayLayoutPref(pref_service, list, display_layout);
}
void StoreCurrentDisplayProperties(PrefService* pref_service) {
display::DisplayManager* display_manager = GetDisplayManager();
ScopedDictPrefUpdate update(pref_service, prefs::kDisplayProperties);
base::Value::Dict& pref_data = update.Get();
// Pre-process data related to legacy touch calibration to opitmize lookup.
const display::TouchDeviceIdentifier& fallback_identifier =
display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier();
display::TouchDeviceManager::AssociationInfoMap legacy_data_map;
if (base::Contains(
display_manager->touch_device_manager()->touch_associations(),
fallback_identifier)) {
legacy_data_map =
display_manager->touch_device_manager()->touch_associations().at(
fallback_identifier);
}
size_t num = display_manager->GetNumDisplays();
for (size_t i = 0; i < num; ++i) {
const display::Display& display = display_manager->GetDisplayAt(i);
int64_t id = display.id();
display::ManagedDisplayInfo info = display_manager->GetDisplayInfo(id);
base::Value::Dict property_value;
// Don't save the display preference in unified mode because its
// size and modes can change depending on the combination of displays.
if (display_manager->IsInUnifiedMode()) {
continue;
}
// Don't save rotation when in tablet mode, so that if the device is
// rebooted into clamshell mode, it won't have an unexpected rotation.
// https://crbug.com/733092.
// But we should keep any original value so that it can be restored when
// exiting tablet mode.
if (display::Screen::GetScreen()->InTabletMode()) {
const base::Value::Dict* original_property =
pref_data.FindDict(base::NumberToString(id));
if (original_property) {
std::optional<int> original_rotation =
original_property->FindInt("rotation");
if (original_rotation) {
property_value.Set("rotation", *original_rotation);
}
}
} else {
property_value.Set("rotation",
static_cast<int>(info.GetRotation(
display::Display::RotationSource::USER)));
}
display::ManagedDisplayMode mode;
if (!display.IsInternal() &&
display_manager->GetSelectedModeForDisplayId(id, &mode) &&
!mode.native()) {
property_value.Set("width", mode.size().width());
property_value.Set("height", mode.size().height());
property_value.Set("device-scale-factor",
static_cast<int>(mode.device_scale_factor() * 1000));
if (display::features::IsListAllDisplayModesEnabled()) {
property_value.Set("interlaced", mode.is_interlaced());
property_value.Set("refresh-rate", mode.refresh_rate());
}
}
if (!info.overscan_insets_in_dip().IsEmpty()) {
InsetsToValue(info.overscan_insets_in_dip(), property_value);
}
// Store the legacy format touch calibration data. This can be removed after
// a couple of milestones when every device has migrated to the new format.
if (legacy_data_map.size() && base::Contains(legacy_data_map, id)) {
TouchDataToValue(legacy_data_map.at(id).calibration_data, property_value);
}
property_value.Set(kDisplayZoom, info.zoom_factor());
base::Value::Dict display_zoom_dict;
for (const auto& it : info.zoom_factor_map()) {
display_zoom_dict.Set(it.first, it.second);
}
property_value.Set(kDisplayZoomMap, std::move(display_zoom_dict));
property_value.Set(kVariableRefreshRateState,
static_cast<int>(info.variable_refresh_rate_state()));
if (const std::optional<float>& vsync_rate_min = info.vsync_rate_min()) {
property_value.Set(kVsyncRateMin, vsync_rate_min.value());
}
pref_data.Set(base::NumberToString(id), std::move(property_value));
}
}
bool GetDisplayPowerStateFromString(const std::string& state_string,
chromeos::DisplayPowerState* power_state) {
if (state_string == kDisplayPowerAllOn) {
*power_state = chromeos::DISPLAY_POWER_ALL_ON;
} else if (state_string == kDisplayPowerInternalOffExternalOn) {
*power_state = chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON;
} else if (state_string == kDisplayPowerInternalOnExternalOff) {
*power_state = chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF;
} else {
// Don't restore ALL_OFF state. http://crbug.com/318456.
return false;
}
return true;
}
void StoreDisplayPowerState(PrefService* pref_service,
DisplayPowerState power_state) {
const char* state_string = nullptr;
switch (power_state) {
case chromeos::DISPLAY_POWER_ALL_ON:
state_string = kDisplayPowerAllOn;
break;
case chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON:
state_string = kDisplayPowerInternalOffExternalOn;
break;
case chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF:
state_string = kDisplayPowerInternalOnExternalOff;
break;
case chromeos::DISPLAY_POWER_ALL_OFF:
// Don't store ALL_OFF state. http://crbug.com/318456.
break;
}
if (state_string) {
pref_service->Set(prefs::kDisplayPowerState, base::Value(state_string));
}
}
void StoreCurrentDisplayPowerState(PrefService* pref_service) {
StoreDisplayPowerState(
pref_service,
Shell::Get()->display_configurator()->GetRequestedPowerState());
}
void StoreDisplayRotationPrefs(PrefService* pref_service,
display::Display::Rotation rotation,
bool rotation_lock) {
ScopedDictPrefUpdate update(pref_service, prefs::kDisplayRotationLock);
update->Set("lock", rotation_lock);
update->Set("orientation", static_cast<int>(rotation));
}
void StoreCurrentDisplayRotationLockPrefs(PrefService* pref_service) {
if (!display::HasInternalDisplay()) {
return;
}
display::Display::Rotation rotation =
GetDisplayManager()
->GetDisplayInfo(display::Display::InternalDisplayId())
.GetRotation(display::Display::RotationSource::ACCELEROMETER);
bool rotation_lock = Shell::Get()
->display_manager()
->registered_internal_display_rotation_lock();
StoreDisplayRotationPrefs(pref_service, rotation, rotation_lock);
}
void StoreDisplayTouchAssociations(PrefService* pref_service) {
display::TouchDeviceManager* touch_device_manager =
GetDisplayManager()->touch_device_manager();
ScopedDictPrefUpdate update(pref_service, prefs::kDisplayTouchAssociations);
base::Value::Dict& pref_data = update.Get();
pref_data.clear();
const display::TouchDeviceManager::TouchAssociationMap& touch_associations =
touch_device_manager->touch_associations();
for (const auto& association : touch_associations) {
base::Value::Dict association_info_map_value;
for (const auto& association_info : association.second) {
// Iteration for each pair of <Display ID, TouchAssociationInfo>.
base::Value::Dict association_info_value;
// Parsing each member of TouchAssociationInfo and storing them in
// |association_info_value|.
// Serialize timestamp.
association_info_value.Set(
kTouchAssociationTimestamp,
association_info.second.timestamp.InSecondsFSinceUnixEpoch());
// Serialize TouchCalibrationData.
base::Value::Dict calibration_data_value;
TouchDataToValue(association_info.second.calibration_data,
calibration_data_value);
association_info_value.Set(kTouchAssociationCalibrationData,
std::move(calibration_data_value));
// Move the searialzed TouchAssociationInfo stored in
// |association_info_value| to |association_info_map_value| against the
// display id as key. This is a 1 to 1 mapping of a single entry from
// AssociationInfoMap to its serialized form.
association_info_map_value.Set(
base::NumberToString(association_info.first),
std::move(association_info_value));
}
if (association_info_map_value.empty()) {
continue;
}
// Move the already serialized entry of AssociationInfoMap from
// |association_info_map_value| to |pref_data| against the
// TouchDeviceIdentifier as key. This is a 1 to 1 mapping of a single entry
// from TouchAssociationMap to its serialized form.
pref_data.Set(association.first.ToString(),
std::move(association_info_map_value));
}
// Store the port mappings. What display a touch device connected to a
// particular port is associated with.
ScopedDictPrefUpdate update_port(pref_service,
prefs::kDisplayTouchPortAssociations);
base::Value::Dict& port_pref_data = update_port.Get();
port_pref_data.clear();
const display::TouchDeviceManager::PortAssociationMap& port_associations =
touch_device_manager->port_associations();
// For each port identified by the secondary id of TouchDeviceIdentifier,
// we store the touch device and the display associated with it.
for (const auto& association : port_associations) {
base::Value::Dict association_info_value;
association_info_value.Set(kTouchDeviceIdentifier,
association.first.ToString());
association_info_value.Set(kPortAssociationDisplayId,
base::NumberToString(association.second));
port_pref_data.Set(association.first.SecondaryIdToString(),
std::move(association_info_value));
}
}
void ReportToPopularityMetricsAndStore(PrefService* pref_service) {
// NOTE: This number must change every time we add/remove/edit any fields to
// force the device to resubmit a report with updated fields.
constexpr uint64_t kCurrentVersion = 1;
auto cached_version =
pref_service->GetUint64(prefs::kDisplayPopularityRevNumber);
if (cached_version != kCurrentVersion) {
pref_service->ClearPref(prefs::kDisplayPopularityUserReportedDisplays);
pref_service->SetUint64(prefs::kDisplayPopularityRevNumber,
kCurrentVersion);
}
base::Value::List cached_list =
pref_service->GetList(prefs::kDisplayPopularityUserReportedDisplays)
.Clone();
for (int64_t id : GetDisplayManager()->GetConnectedDisplayIdList()) {
const display::ManagedDisplayInfo& display =
GetDisplayManager()->GetDisplayInfo(id);
// We don't want to report internal panels.
if (display::IsInternalDisplayId(id)) {
continue;
}
std::string display_id = base::NumberToString(display.edid_display_id());
// If we've already reported that display, don't report it again.
if (base::Contains(cached_list, display_id)) {
continue;
}
const display::ManagedDisplayInfo::ManagedDisplayModeList& modes =
display.display_modes();
CHECK(modes.size());
auto it = std::find_if(
modes.begin(), modes.end(),
[](const display::ManagedDisplayMode& mode) { return mode.native(); });
const display::ManagedDisplayMode* native_mode =
it == modes.end() ? nullptr : &(*it);
CHECK(it != modes.end());
int product_id;
base::StringToInt(display.product_id(), &product_id);
metrics::structured::StructuredMetricsClient::Record(std::move(
metrics::structured::events::v2::popular_displays::MonitorInfo()
.SetDisplayName(display.name())
.SetManufacturerId(display.manufacturer_id())
.SetProductId(product_id)
.SetNativeModeSize(native_mode->size().ToString())
.SetNativeModeRefreshRate(native_mode->refresh_rate())
.SetPhysicalSize(display.physical_size().ToString())
.SetConnectionType(
display::DisplayConnectionTypeString(display.connection_type()))
.SetIsVrrCapable(
display.variable_refresh_rate_state() <
display::VariableRefreshRateState::kVrrNotCapable)));
cached_list.Append(display_id);
}
pref_service->SetList(prefs::kDisplayPopularityUserReportedDisplays,
std::move(cached_list));
}
// Stores mirror info for each external display.
void StoreExternalDisplayMirrorInfo(PrefService* pref_service) {
ScopedListPrefUpdate update(pref_service, prefs::kExternalDisplayMirrorInfo);
base::Value::List& pref_data = update.Get();
pref_data.clear();
const std::set<int64_t>& external_display_mirror_info =
GetDisplayManager()->external_display_mirror_info();
for (const auto& id : external_display_mirror_info) {
pref_data.Append(base::NumberToString(id));
}
}
// Stores mixed mirror mode parameters. Clear the preferences if
// |mixed_mirror_mode_params| is null.
void StoreDisplayMixedMirrorModeParams(
PrefService* pref_service,
const std::optional<display::MixedMirrorModeParams>& mixed_params) {
ScopedDictPrefUpdate update(pref_service,
prefs::kDisplayMixedMirrorModeParams);
base::Value::Dict& pref_data = update.Get();
pref_data.clear();
if (!mixed_params) {
return;
}
pref_data.Set(kMirroringSourceId,
base::NumberToString(mixed_params->source_id));
base::Value::List mirroring_destination_ids_list;
for (const auto& id : mixed_params->destination_ids) {
mirroring_destination_ids_list.Append(base::NumberToString(id));
}
pref_data.Set(kMirroringDestinationIds,
std::move(mirroring_destination_ids_list));
}
void StoreCurrentDisplayMixedMirrorModeParams(PrefService* pref_service) {
StoreDisplayMixedMirrorModeParams(
pref_service, GetDisplayManager()->mixed_mirror_mode_params());
}
} // namespace
// static
void DisplayPrefs::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kSecondaryDisplays);
registry->RegisterDictionaryPref(prefs::kDisplayProperties);
registry->RegisterStringPref(prefs::kDisplayPowerState, kDisplayPowerAllOn);
registry->RegisterDictionaryPref(prefs::kDisplayRotationLock);
registry->RegisterDictionaryPref(prefs::kDisplayTouchAssociations);
registry->RegisterDictionaryPref(prefs::kDisplayTouchPortAssociations);
registry->RegisterListPref(prefs::kExternalDisplayMirrorInfo);
registry->RegisterDictionaryPref(prefs::kDisplayMixedMirrorModeParams);
registry->RegisterBooleanPref(prefs::kAllowMGSToStoreDisplayProperties,
false);
registry->RegisterListPref(prefs::kDisplayPopularityUserReportedDisplays);
registry->RegisterUint64Pref(prefs::kDisplayPopularityRevNumber, 0);
}
DisplayPrefs::DisplayPrefs(PrefService* local_state)
: local_state_(local_state) {
Shell::Get()->session_controller()->AddObserver(this);
// |local_state_| could be null in tests.
if (local_state_) {
LoadDisplayPreferences();
}
}
DisplayPrefs::~DisplayPrefs() {
Shell::Get()->session_controller()->RemoveObserver(this);
}
void DisplayPrefs::OnFirstSessionStarted() {
if (store_requested_) {
MaybeStoreDisplayPrefs();
}
}
void DisplayPrefs::MaybeStoreDisplayPrefs() {
DCHECK(local_state_);
// Stores the power state regardless of the login status, because the power
// state respects to the current status (close/open) of the lid which can be
// changed in any situation. See http://crbug.com/285360
StoreCurrentDisplayPowerState(local_state_);
StoreCurrentDisplayRotationLockPrefs(local_state_);
// We cannot really decide whether to store display prefs until there is an
// active user session. |OnFirstSessionStarted()| should eventually attempt to
// do a store in this case.
if (!Shell::Get()->session_controller()->GetUserType()) {
store_requested_ = true;
return;
}
// There are multiple scenarios where we don't want to save display prefs.
// Some user types are not allowed, we don't want to change them while a
// display change confirmation dialog is still visible, etc.
if (!UserCanSaveDisplayPreference() ||
!Shell::Get()->ShouldSaveDisplaySettings()) {
return;
}
store_requested_ = false;
// Don't save certain display properties when in tablet mode, so if
// the device is rebooted in clamshell mode, it won't have an unexpected
// mirroring layout. https://crbug.com/733092.
if (!display::Screen::GetScreen()->InTabletMode()) {
StoreCurrentDisplayLayoutPrefs(local_state_);
StoreExternalDisplayMirrorInfo(local_state_);
StoreCurrentDisplayMixedMirrorModeParams(local_state_);
}
StoreCurrentDisplayProperties(local_state_);
StoreDisplayTouchAssociations(local_state_);
ReportToPopularityMetricsAndStore(local_state_);
// The display prefs need to be committed immediately to guarantee they're not
// lost, and are restored properly on reboot. https://crbug.com/936884.
// This sends a request via mojo to commit the prefs to disk.
local_state_->CommitPendingWrite();
}
void DisplayPrefs::LoadDisplayPreferences() {
LoadDisplayLayouts(local_state_);
LoadDisplayProperties(local_state_);
LoadExternalDisplayMirrorInfo(local_state_);
LoadDisplayMixedMirrorModeParams(local_state_);
LoadDisplayRotationState(local_state_);
LoadDisplayTouchAssociations(local_state_);
// Now that the display prefs have been loaded, request to reconfigure the
// displays, but signal the display manager to restore the mirror state of
// external displays from the loaded prefs (if any).
Shell::Get()
->display_manager()
->set_should_restore_mirror_mode_from_display_prefs(true);
Shell::Get()->display_configurator()->OnConfigurationChanged();
// Ensure that we have a reasonable initial display power state if
// powerd fails to send us one over D-Bus. Otherwise, we won't restore
// displays correctly after retaking control when changing virtual terminals.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kFirstExecAfterBoot)) {
Shell::Get()->display_configurator()->InitializeDisplayPowerState();
return;
}
// Restore DisplayPowerState:
const std::string value =
local_state_->GetValue(prefs::kDisplayPowerState).GetString();
chromeos::DisplayPowerState power_state;
if (GetDisplayPowerStateFromString(value, &power_state)) {
Shell::Get()->display_configurator()->SetInitialDisplayPower(power_state);
}
}
void DisplayPrefs::StoreDisplayRotationPrefsForTest(
display::Display::Rotation rotation,
bool rotation_lock) {
StoreDisplayRotationPrefs(local_state_, rotation, rotation_lock);
}
void DisplayPrefs::StoreDisplayLayoutPrefForTest(
const display::DisplayIdList& list,
const display::DisplayLayout& layout) {
StoreDisplayLayoutPref(local_state_, list, layout);
}
void DisplayPrefs::StoreDisplayPowerStateForTest(
DisplayPowerState power_state) {
StoreDisplayPowerState(local_state_, power_state);
}
void DisplayPrefs::LoadTouchAssociationPreferenceForTest() {
LoadDisplayTouchAssociations(local_state_);
}
void DisplayPrefs::LoadDisplayPrefsForTest() {
CHECK_IS_TEST();
LoadDisplayPreferences();
}
void DisplayPrefs::StoreLegacyTouchDataForTest(
int64_t display_id,
const display::TouchCalibrationData& data) {
ScopedDictPrefUpdate update(local_state_, prefs::kDisplayProperties);
base::Value::Dict property_value;
TouchDataToValue(data, property_value);
update->Set(base::NumberToString(display_id), std::move(property_value));
}
bool DisplayPrefs::ParseTouchCalibrationStringForTest(
const std::string& str,
display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad) {
return ParseTouchCalibrationStringValue(str, point_pair_quad);
}
void DisplayPrefs::StoreDisplayMixedMirrorModeParamsForTest(
const std::optional<display::MixedMirrorModeParams>& mixed_params) {
StoreDisplayMixedMirrorModeParams(local_state_, mixed_params);
}
bool DisplayPrefs::IsDisplayAvailableInPref(int64_t display_id) const {
for (const auto it : local_state_->GetDict(prefs::kDisplayProperties)) {
int64_t id = display::kInvalidDisplayId;
if (base::StringToInt64(it.first, &id) && id == display_id) {
return true;
}
}
return false;
}
} // namespace ash