chromium/chrome/browser/ash/policy/display/display_resolution_handler.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/policy/display/display_resolution_handler.h"

#include <optional>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/values.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "mojo/public/cpp/bindings/struct_traits.h"

namespace policy {

using DisplayUnitTraits =
    mojo::StructTraits<crosapi::mojom::DisplayUnitInfo::DataView,
                       crosapi::mojom::DisplayUnitInfoPtr>;

struct DisplayResolutionHandler::InternalDisplaySettings {
  int scale_percentage = 0;

  explicit InternalDisplaySettings(int scale_percentage)
      : scale_percentage(scale_percentage) {}

  bool operator==(const InternalDisplaySettings& rhs) const {
    return scale_percentage == rhs.scale_percentage;
  }

  bool operator!=(const InternalDisplaySettings& rhs) const {
    return !(*this == rhs);
  }

  // Create display config for the internal display using policy settings from
  // |internal_display_settings_|.
  crosapi::mojom::DisplayConfigPropertiesPtr ToDisplayConfigProperties() {
    auto new_config = crosapi::mojom::DisplayConfigProperties::New();
    // Converting percentage to factor.
    new_config->display_zoom_factor = scale_percentage / 100.0;
    return new_config;
  }

  // Get settings for the internal display from
  // |ash::kDeviceDisplayResolution| setting value.
  static std::unique_ptr<InternalDisplaySettings> FromPolicySetting(
      const base::Value::Dict* pref) {
    const std::optional<int> scale_value =
        pref->FindInt(ash::kDeviceDisplayResolutionKeyInternalScale);
    return scale_value ? std::make_unique<InternalDisplaySettings>(*scale_value)
                       : nullptr;
  }
};

struct DisplayResolutionHandler::ExternalDisplaySettings {
  bool use_native = false;
  int width = 0;
  int height = 0;
  std::optional<int> scale_percentage = std::nullopt;

  bool operator==(const ExternalDisplaySettings& rhs) const {
    return use_native == rhs.use_native && width == rhs.width &&
           height == rhs.height && scale_percentage == rhs.scale_percentage;
  }

  bool operator!=(const ExternalDisplaySettings& rhs) const {
    return !(*this == rhs);
  }

  // Check if either |use_native| flag is set and mode is native or the mode
  // has required resolution.
  bool IsSuitableDisplayMode(const crosapi::mojom::DisplayModePtr& mode) {
    return (use_native && mode->is_native) ||
           (!use_native && width == mode->size.width() &&
            height == mode->size.height());
  }

  // Create display config for the external display using policy settings from
  // |external_display_settings_|.
  crosapi::mojom::DisplayConfigPropertiesPtr ToDisplayConfigProperties(
      const std::vector<crosapi::mojom::DisplayModePtr>& display_modes) {
    bool found_suitable_mode = false;
    auto new_config = crosapi::mojom::DisplayConfigProperties::New();
    for (const crosapi::mojom::DisplayModePtr& mode : display_modes) {
      // Check if the current display mode has required resolution and its
      // refresh rate is higher than refresh rate of the already found mode.
      if (IsSuitableDisplayMode(mode) &&
          (!found_suitable_mode ||
           mode->refresh_rate > new_config->display_mode->refresh_rate)) {
        new_config->display_mode = mode->Clone();
        found_suitable_mode = true;
      }
    }
    // If we couldn't find the required mode and and scale percentage doesn't
    // need to be changed, we have nothing to do.
    if (!found_suitable_mode && !scale_percentage) {
      return crosapi::mojom::DisplayConfigPropertiesPtr();
    }

    if (scale_percentage) {
      // Converting percentage to the factor.
      new_config->display_zoom_factor = *scale_percentage / 100.0;
    }

    return new_config;
  }

  // Get settings for the external displays from
  // |ash::kDeviceDisplayResolution| setting value;
  static std::unique_ptr<ExternalDisplaySettings> FromPolicySetting(
      const base::Value::Dict* pref) {
    auto result = std::make_unique<ExternalDisplaySettings>();

    // Scale can be used for both native and non-native modes
    result->scale_percentage =
        pref->FindInt(ash::kDeviceDisplayResolutionKeyExternalScale);

    const std::optional<bool> use_native_value =
        pref->FindBool(ash::kDeviceDisplayResolutionKeyExternalUseNative);
    if (use_native_value && *use_native_value) {
      result->use_native = true;
      return result;
    }

    const std::optional<int> width_value =
        pref->FindInt(ash::kDeviceDisplayResolutionKeyExternalWidth);
    const std::optional<int> height_value =
        pref->FindInt(ash::kDeviceDisplayResolutionKeyExternalHeight);
    if (width_value && height_value) {
      result->width = *width_value;
      result->height = *height_value;
      return result;
    }

    return nullptr;
  }
};

DisplayResolutionHandler::DisplayResolutionHandler() = default;

DisplayResolutionHandler::~DisplayResolutionHandler() = default;

const char* DisplayResolutionHandler::SettingName() {
  return ash::kDeviceDisplayResolution;
}

// Reads |ash::kDeviceDisplayResolution| from CrosSettings and stores
// the settings in |recommended_|, |external_display_settings_| and
// |internal_display_settings_|. Also updates |policy_enabled_| flag.
void DisplayResolutionHandler::OnSettingUpdate() {
  policy_enabled_ = false;
  const base::Value::Dict* resolution_pref = nullptr;
  ash::CrosSettings::Get()->GetDictionary(ash::kDeviceDisplayResolution,
                                          &resolution_pref);
  if (!resolution_pref)
    return;

  std::unique_ptr<ExternalDisplaySettings> new_external_config =
      ExternalDisplaySettings::FromPolicySetting(resolution_pref);
  std::unique_ptr<InternalDisplaySettings> new_internal_config =
      InternalDisplaySettings::FromPolicySetting(resolution_pref);

  bool new_recommended = false;
  policy_enabled_ = new_external_config || new_internal_config;
  const std::optional<bool> recommended_value =
      resolution_pref->FindBool(ash::kDeviceDisplayResolutionKeyRecommended);

  if (recommended_value)
    new_recommended = *recommended_value;

  // We should reset locally stored settings and clear list of already updated
  // displays if any of the policy values were updated.
  bool should_reset_settings = false;
  should_reset_settings |=
      bool{new_external_config} != bool{external_display_settings_};
  should_reset_settings |= new_external_config && external_display_settings_ &&
                           *new_external_config != *external_display_settings_;
  should_reset_settings |=
      bool{new_internal_config} != bool{internal_display_settings_};
  should_reset_settings |= new_internal_config && internal_display_settings_ &&
                           *new_internal_config != *internal_display_settings_;
  should_reset_settings |= recommended_ != new_recommended;

  if (!should_reset_settings)
    return;

  resized_display_ids_.clear();
  external_display_settings_ = std::move(new_external_config);
  internal_display_settings_ = std::move(new_internal_config);
  recommended_ = new_recommended;
}

// Applies settings received with |OnSettingUpdate| to each supported display
// from |info_list| if |policy_enabled_| is true.
void DisplayResolutionHandler::ApplyChanges(
    crosapi::mojom::CrosDisplayConfigController* cros_display_config,
    const std::vector<crosapi::mojom::DisplayUnitInfoPtr>& info_list) {
  if (!policy_enabled_)
    return;
  for (const crosapi::mojom::DisplayUnitInfoPtr& display_unit_info :
       info_list) {
    std::string display_id = display_unit_info->id;
    // If policy value is marked as "recommended" we need to change the
    // resolution just once for each display. So we're just skipping the display
    // if it was resized since last settings update.
    if (recommended_ &&
        resized_display_ids_.find(display_id) != resized_display_ids_.end()) {
      continue;
    }

    crosapi::mojom::DisplayConfigPropertiesPtr new_config;
    if (display_unit_info->is_internal && internal_display_settings_) {
      new_config = internal_display_settings_->ToDisplayConfigProperties();
    } else if (!display_unit_info->is_internal && external_display_settings_) {
      new_config = external_display_settings_->ToDisplayConfigProperties(
          DisplayUnitTraits::available_display_modes(display_unit_info));
    }

    if (!new_config)
      continue;

    resized_display_ids_.insert(display_id);
    cros_display_config->SetDisplayProperties(
        display_unit_info->id, std::move(new_config),
        crosapi::mojom::DisplayConfigSource::kPolicy,
        base::BindOnce([](crosapi::mojom::DisplayConfigResult result) {
          if (result == crosapi::mojom::DisplayConfigResult::kSuccess) {
            VLOG(1) << "Successfully changed display mode.";
          } else {
            LOG(ERROR) << "Couldn't change display mode. Error code: "
                       << result;
          }
        }));
  }
}

}  // namespace policy