chromium/chrome/browser/ash/power/auto_screen_brightness/modeller_impl.h

// 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.

#ifndef CHROME_BROWSER_ASH_POWER_AUTO_SCREEN_BRIGHTNESS_MODELLER_IMPL_H_
#define CHROME_BROWSER_ASH_POWER_AUTO_SCREEN_BRIGHTNESS_MODELLER_IMPL_H_

#include <memory>
#include <optional>

#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/power/auto_screen_brightness/als_reader.h"
#include "chrome/browser/ash/power/auto_screen_brightness/als_samples.h"
#include "chrome/browser/ash/power/auto_screen_brightness/brightness_monitor.h"
#include "chrome/browser/ash/power/auto_screen_brightness/gaussian_trainer.h"
#include "chrome/browser/ash/power/auto_screen_brightness/model_config.h"
#include "chrome/browser/ash/power/auto_screen_brightness/model_config_loader.h"
#include "chrome/browser/ash/power/auto_screen_brightness/modeller.h"
#include "chrome/browser/ash/power/auto_screen_brightness/utils.h"
#include "chrome/browser/profiles/profile.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/base/user_activity/user_activity_observer.h"

namespace ash {
namespace power {
namespace auto_screen_brightness {

struct Model {
  Model();
  Model(const std::optional<MonotoneCubicSpline>& global_curve,
        const std::optional<MonotoneCubicSpline>& personal_curve,
        int iteration_count);
  Model(const Model& model);
  ~Model();
  std::optional<MonotoneCubicSpline> global_curve;
  std::optional<MonotoneCubicSpline> personal_curve;
  int iteration_count = 0;
};

// Real implementation of Modeller.
// It monitors user-requested brightness changes, ambient light values and
// trains personal brightness curves when user remains idle for a period of
// time.
// An object of this class must be used on the same thread that created this
// object.
class ModellerImpl : public Modeller,
                     public AlsReader::Observer,
                     public BrightnessMonitor::Observer,
                     public ModelConfigLoader::Observer,
                     public ui::UserActivityObserver {
 public:
  static constexpr char kModelDir[] = "autobrightness";
  static constexpr char kGlobalCurveFileName[] = "global_curve";
  static constexpr char kPersonalCurveFileName[] = "personal_curve";
  static constexpr char kModelIterationCountFileName[] = "iteration_count";

  // Global curve, personal curve and training iteration count will be saved to
  // the file paths below.
  struct ModelSavingSpec {
    base::FilePath global_curve;
    base::FilePath personal_curve;
    base::FilePath iteration_count;
  };

  // ModellerImpl has weak dependencies on all parameters except |trainer|.
  ModellerImpl(const Profile* profile,
               AlsReader* als_reader,
               BrightnessMonitor* brightness_monitor,
               ModelConfigLoader* model_config_loader,
               ui::UserActivityDetector* user_activity_detector,
               std::unique_ptr<Trainer> trainer);

  ModellerImpl(const ModellerImpl&) = delete;
  ModellerImpl& operator=(const ModellerImpl&) = delete;

  ~ModellerImpl() override;

  // Modeller overrides:
  void AddObserver(Modeller::Observer* observer) override;
  void RemoveObserver(Modeller::Observer* observer) override;

  // AlsReader::Observer overrides:
  void OnAmbientLightUpdated(int lux) override;
  void OnAlsReaderInitialized(AlsReader::AlsInitStatus status) override;

  // BrightnessMonitor::Observer overrides:
  void OnBrightnessMonitorInitialized(bool success) override;
  void OnUserBrightnessChanged(double old_brightness_percent,
                               double new_brightness_percent) override;
  void OnUserBrightnessChangeRequested() override;

  // ModelConfigLoader::Observer overrides:
  void OnModelConfigLoaded(std::optional<ModelConfig> model_config) override;

  // ui::UserActivityObserver overrides:
  void OnUserActivity(const ui::Event* event) override;

  // ModellerImpl has weak dependencies on all parameters except |trainer|.
  static std::unique_ptr<ModellerImpl> CreateForTesting(
      const Profile* profile,
      AlsReader* als_reader,
      BrightnessMonitor* brightness_monitor,
      ModelConfigLoader* model_config_loader,
      ui::UserActivityDetector* user_activity_detector,
      std::unique_ptr<Trainer> trainer,
      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
      const base::TickClock* tick_clock);

  // Current average log ambient light.
  std::optional<double> AverageAmbientForTesting(base::TimeTicks now);

  // Current number of training data points stored, which will be used for next
  // training.
  size_t NumberTrainingDataPointsForTesting() const;

  // Returns |max_training_data_points_| for unit tests.
  size_t GetMaxTrainingDataPointsForTesting() const;

  base::TimeDelta GetTrainingDelayForTesting() const;

  ModelConfig GetModelConfigForTesting() const;

  // Returns ModelSavingSpec used to store models. It also creates intermediate
  // directories if they do not exist. The returned paths will be empty on
  // failures.
  static ModelSavingSpec GetModelSavingSpecFromProfilePath(
      const base::FilePath& profile_path);

 private:
  // ModellerImpl has weak dependencies on all parameters except |trainer|.
  ModellerImpl(const Profile* profile,
               AlsReader* als_reader,
               BrightnessMonitor* brightness_monitor,
               ModelConfigLoader* model_config_loader,
               ui::UserActivityDetector* user_activity_detector,
               std::unique_ptr<Trainer> trainer,
               scoped_refptr<base::SequencedTaskRunner> task_runner,
               const base::TickClock* tick_clock,
               bool is_testing = false);

  // Called after we've attempted to read model saving spec from the user
  // profile.
  void OnModelSavingSpecReadFromProfile(const ModelSavingSpec& spec);

  // Called to handle a status change in one of the dependencies (ALS,
  // brightness monitor, model config loader) of the modeller. If all
  // dependencies are successfully initialized, attempts initialization of
  // the modeller (curve loading, parameter customization) and notifies
  // observers about the result.
  void HandleStatusUpdate();

  // Applies customizations from model configs. Returns whether it is
  // successful.
  bool ApplyCustomization();

  // Called as soon as |is_modeller_enabled_| has its value set. It will notify
  // all observers.
  void OnInitializationComplete();

  // Notifies a given observer about the state of the modeller. Will provide
  // either
  // - no curves (if modeller is disabled),
  // - just a global curve (if no personal curve is available), or
  // - both a global and personal curve.
  void NotifyObserverInitStatus(Modeller::Observer& observer);

  // Sets the global and personal curves based on the model read from disk. If
  // the model is invalid or not based on the current model config, instead
  // resets the global and personal curves.
  void OnModelLoadedFromDisk(const Model& model);

  void OnModelSavedToDisk(bool is_successful);

  // Called after we've set trainer's initial curves.
  void OnSetInitialCurves(bool is_personal_curve_valid);

  // Either starts training immediately or delays it for |training_delay_|.
  // Training starts immediately if |training_delay_| is 0 or number of training
  // points reached |max_training_data_points_|.
  // This function is called after a user brightness change signal is received
  // (that will be used as an example), and when a user activity is detected.
  // It's also called after initial curves are set.
  // Nothing will happen if model is not enabled.
  void ScheduleTrainerStart();

  // Starts model training and runs it in non UI thread. Also clears
  // |data_cache_|.
  void StartTraining();

  // Called after training is complete.
  void OnTrainingFinished(const TrainingResult& result);

  // Erase all info related to the personal curve.
  void ErasePersonalCurve();

  // If |is_testing_| is false, we check curve saving/loading and training jobs
  // are running on non-UI thread.
  const bool is_testing_ = false;

  // If number of recorded training data has reached |max_training_data_points_|
  // we start training immediately, without waiting for user to become idle for
  // |training_delay_|. This can be overridden by experiment flag
  // "max_training_data_points".
  size_t max_training_data_points_ = 100;

  // Once user remains idle for |training_delay_|, we start training the model.
  // If this value is 0, we will not need to wait for user to remain inactive.
  // This can be overridden by experiment flag "training_delay_in_seconds".
  base::TimeDelta training_delay_ = base::Seconds(0);

  // If personal curve error is above this threshold, the curve will not be
  // exported. The error is expressed in terms of percentages.
  double curve_error_tolerance_ = 5.0;

  base::ScopedObservation<AlsReader, AlsReader::Observer>
      als_reader_observation_{this};

  base::ScopedObservation<BrightnessMonitor, BrightnessMonitor::Observer>
      brightness_monitor_observation_{this};

  base::ScopedObservation<ModelConfigLoader, ModelConfigLoader::Observer>
      model_config_loader_observation_{this};

  base::ScopedObservation<ui::UserActivityDetector, ui::UserActivityObserver>
      user_activity_observation_{this};

  // Background task runner for IO work (loading a curve from disk and writing a
  // curve to disk) and training jobs.
  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
  std::unique_ptr<Trainer, base::OnTaskRunnerDeleter> trainer_;

  // This will be replaced by a mock tick clock during tests.
  const raw_ptr<const base::TickClock> tick_clock_;

  base::OneShotTimer model_timer_;

  std::optional<AlsReader::AlsInitStatus> als_init_status_;
  std::optional<bool> brightness_monitor_success_;

  // |model_config_exists_| will remain nullopt until |OnModelConfigLoaded| is
  // called. Its value will then be set to true if the input model config exists
  // (not nullopt), else its value will be false.
  std::optional<bool> model_config_exists_;
  ModelConfig model_config_;

  // Whether this modeller has initialized successfully, including connecting
  // to AlsReader, BrightnessMonitor and loading a Trainer.
  // Initially has no value. Guaranteed to have a value after the completion of
  // |OnModelLoadedFromDisk|.
  std::optional<bool> is_modeller_enabled_;

  std::optional<ModelSavingSpec> model_saving_spec_;

  // Whether the initial global curve is reset to the one constructed from
  // model config. It is true if there is no saved model loaded from the disk
  // or if the saved global curve is different from the curve from model config.
  // If this flag is true, then the global curve is saved to the disk the first
  // time a personal curve is trained and saved to disk; it will be set to false
  // after the first saving is done.
  bool global_curve_reset_ = false;

  // |model_| will be set after initialization is complete and updated each time
  // training is done with a new curve.
  Model model_;

  // |initial_global_curve_| is constructed from model config.
  std::optional<MonotoneCubicSpline> initial_global_curve_;

  // Recent log ambient values.
  std::unique_ptr<AmbientLightSampleBuffer> log_als_values_;

  std::vector<TrainingDataPoint> data_cache_;

  base::ObserverList<Modeller::Observer> observers_;

  // Training start time.
  std::optional<base::TimeTicks> training_start_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<ModellerImpl> weak_ptr_factory_{this};
};

// Saves |model| to disk at location specified by |model_saving_spec| and
// returns whether it was successful. This should run in another thread to be
// non-blocking to the main thread (if |is_testing| is false).
// Not every components of |model| will be saved:
// 1. |global_curve| is saved only if |save_global_curve| is true.
// 2. |personal_curve| is saved only if |save_personal_curve| is true.
// 3. |iteration_count| is always saved.
bool SaveModelToDisk(const ModellerImpl::ModelSavingSpec& model_saving_spec,
                     const Model& model,
                     bool save_global_curve,
                     bool save_personal_curve,
                     bool is_testing);

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

#endif  // CHROME_BROWSER_ASH_POWER_AUTO_SCREEN_BRIGHTNESS_MODELLER_IMPL_H_