chromium/chrome/browser/ash/power/auto_screen_brightness/model_config_loader_impl.cc

// Copyright 2019 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/power/auto_screen_brightness/model_config_loader_impl.h"

#include <string>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/check_op.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_value_converter.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "content/public/browser/browser_thread.h"

namespace ash {
namespace power {
namespace auto_screen_brightness {

namespace {

// Model params are usually written to this path, unless it is a unibuild.
constexpr char kDefaultModelParamsPath[] =
    "/usr/share/chromeos-assets/autobrightness/model_params.json";

// Model params for unibuild devices will be written to device-specific
// directories. We need to read the relevant directory from the following system
// file path.
constexpr char kSystemPath[] =
    "/run/chromeos-config/v1/power/autobrightness/config-file/system-path";

// Reads string content from |model_params_path| if it exists.
// This should run in another thread to be non-blocking to the main thread (if
// |is_testing| is false).
std::string LoadModelParamsFromDisk(const base::FilePath& model_params_path,
                                    bool is_testing) {
  DCHECK(is_testing ||
         !content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));

  base::FilePath used_model_params_path = model_params_path;
  if (used_model_params_path.empty()) {
    // Only override supplied path if it is empty.
    const base::FilePath default_path = base::FilePath(kDefaultModelParamsPath);
    if (base::PathExists(default_path)) {
      // If default path exists, use it.
      used_model_params_path = default_path;
    } else {
      // If default path doesn't exist, try to get the path from the system
      // file.
      const base::FilePath system_path = base::FilePath(kSystemPath);
      if (base::PathExists(system_path)) {
        std::string dest_path_value;
        if (base::ReadFileToString(system_path, &dest_path_value)) {
          used_model_params_path = base::FilePath(dest_path_value);
        }
      }
    }
  }

  if (used_model_params_path.empty() ||
      !base::PathExists(used_model_params_path)) {
    VLOG(1) << "ABLoader model params path does not exist";
    return std::string();
  }

  std::string content;
  if (!base::ReadFileToString(used_model_params_path, &content)) {
    VLOG(1) << "ABLoader cannot load params from " << used_model_params_path;
    return std::string();
  }
  VLOG(1) << "ABLoader successfully loaded params from "
          << used_model_params_path;
  return content;
}

// ModelConfigFromJson and GlobalCurveFromJson are used to hold JSON-parsed
// params.
struct GlobalCurveFromJson {
  std::vector<std::unique_ptr<double>> log_lux;
  std::vector<std::unique_ptr<double>> brightness;
  static void RegisterJSONConverter(
      base::JSONValueConverter<GlobalCurveFromJson>* converter) {
    converter->RegisterRepeatedDouble("log_lux", &GlobalCurveFromJson::log_lux);
    converter->RegisterRepeatedDouble("brightness",
                                      &GlobalCurveFromJson::brightness);
  }
};

struct ModelConfigFromJson {
  double auto_brightness_als_horizon_seconds;
  bool enabled;
  GlobalCurveFromJson global_curve;
  std::string metrics_key;
  double model_als_horizon_seconds;

  ModelConfigFromJson()
      : auto_brightness_als_horizon_seconds(-1.0),
        enabled(false),
        model_als_horizon_seconds(-1.0) {}

  static void RegisterJSONConverter(
      base::JSONValueConverter<ModelConfigFromJson>* converter) {
    converter->RegisterDoubleField(
        "auto_brightness_als_horizon_seconds",
        &ModelConfigFromJson::auto_brightness_als_horizon_seconds);
    converter->RegisterBoolField("enabled", &ModelConfigFromJson::enabled);
    converter->RegisterNestedField("global_curve",
                                   &ModelConfigFromJson::global_curve);
    converter->RegisterStringField("metrics_key",
                                   &ModelConfigFromJson::metrics_key);
    converter->RegisterDoubleField(
        "model_als_horizon_seconds",
        &ModelConfigFromJson::model_als_horizon_seconds);
  }
};

}  // namespace

ModelConfigLoaderImpl::ModelConfigLoaderImpl()
    : ModelConfigLoaderImpl(
          base::FilePath(),
          base::ThreadPool::CreateSequencedTaskRunner(
              {base::TaskPriority::USER_VISIBLE, base::MayBlock(),
               base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
          false /* is_testing */) {}

ModelConfigLoaderImpl::~ModelConfigLoaderImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void ModelConfigLoaderImpl::AddObserver(ModelConfigLoader::Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(observer);
  observers_.AddObserver(observer);
  if (is_initialized_) {
    observer->OnModelConfigLoaded(
        is_model_config_valid_ ? std::optional<ModelConfig>(model_config_)
                               : std::nullopt);
  }
}

void ModelConfigLoaderImpl::RemoveObserver(
    ModelConfigLoader::Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

std::unique_ptr<ModelConfigLoaderImpl> ModelConfigLoaderImpl::CreateForTesting(
    const base::FilePath& model_params_path,
    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) {
  return base::WrapUnique(new ModelConfigLoaderImpl(
      model_params_path, blocking_task_runner, true /* is_testing */));
}

ModelConfigLoaderImpl::ModelConfigLoaderImpl(
    const base::FilePath& model_params_path,
    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
    bool is_testing)
    : blocking_task_runner_(blocking_task_runner), is_testing_(is_testing) {
  Init(model_params_path);
}

void ModelConfigLoaderImpl::Init(const base::FilePath& model_params_path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  blocking_task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&LoadModelParamsFromDisk, model_params_path, is_testing_),
      base::BindOnce(&ModelConfigLoaderImpl::OnModelParamsLoadedFromDisk,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ModelConfigLoaderImpl::InitFromParams() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  model_config_.auto_brightness_als_horizon_seconds =
      GetFieldTrialParamByFeatureAsInt(
          features::kAutoScreenBrightness,
          "auto_brightness_als_horizon_seconds",
          model_config_.auto_brightness_als_horizon_seconds);

  model_config_.enabled = GetFieldTrialParamByFeatureAsBool(
      features::kAutoScreenBrightness, "enabled", model_config_.enabled);

  model_config_.model_als_horizon_seconds = GetFieldTrialParamByFeatureAsInt(
      features::kAutoScreenBrightness, "model_als_horizon_seconds",
      model_config_.model_als_horizon_seconds);

  const std::string global_curve = GetFieldTrialParamValueByFeature(
      features::kAutoScreenBrightness, "global_curve");

  if (global_curve.empty()) {
    OnInitializationComplete();
    return;
  }

  base::StringPairs key_value_pairs;
  if (!base::SplitStringIntoKeyValuePairs(global_curve, ':', ',',
                                          &key_value_pairs)) {
    OnInitializationComplete();
    return;
  }

  std::vector<double> log_lux;
  std::vector<double> brightness;

  for (const auto& key_value : key_value_pairs) {
    double log_lux_val = 0;
    if (!base::StringToDouble(key_value.first, &log_lux_val)) {
      OnInitializationComplete();
      return;
    }

    double brightness_val = 0;
    if (!base::StringToDouble(key_value.second, &brightness_val)) {
      OnInitializationComplete();
      return;
    }

    log_lux.push_back(log_lux_val);
    brightness.push_back(brightness_val);
  }

  model_config_.log_lux = log_lux;
  model_config_.brightness = brightness;

  OnInitializationComplete();
}

void ModelConfigLoaderImpl::OnModelParamsLoadedFromDisk(
    const std::string& content) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (content.empty()) {
    InitFromParams();
    return;
  }

  std::optional<base::Value> value = base::JSONReader::Read(content);
  if (!value) {
    InitFromParams();
    return;
  }

  base::JSONValueConverter<ModelConfigFromJson> converter;
  ModelConfigFromJson loaded_model_configs;
  if (!converter.Convert(*value, &loaded_model_configs)) {
    InitFromParams();
    return;
  }

  model_config_.auto_brightness_als_horizon_seconds =
      loaded_model_configs.auto_brightness_als_horizon_seconds;

  model_config_.enabled = loaded_model_configs.enabled;

  std::vector<double> log_lux;
  for (const auto& log_lux_val : loaded_model_configs.global_curve.log_lux) {
    DCHECK(log_lux_val);
    model_config_.log_lux.push_back(*log_lux_val);
  }

  std::vector<double> brightness;
  for (const auto& brightness_val :
       loaded_model_configs.global_curve.brightness) {
    DCHECK(brightness_val);
    model_config_.brightness.push_back(*brightness_val);
  }

  model_config_.metrics_key = loaded_model_configs.metrics_key;
  model_config_.model_als_horizon_seconds =
      loaded_model_configs.model_als_horizon_seconds;

  InitFromParams();
}

void ModelConfigLoaderImpl::OnInitializationComplete() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  is_model_config_valid_ = IsValidModelConfig(model_config_);
  is_initialized_ = true;
  for (auto& observer : observers_) {
    observer.OnModelConfigLoaded(is_model_config_valid_
                                     ? std::optional<ModelConfig>(model_config_)
                                     : std::nullopt);
  }
}

}  // namespace auto_screen_brightness
}  // namespace power
}  // namespace ash