chromium/ash/system/power/power_notification_controller.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 ASH_SYSTEM_POWER_POWER_NOTIFICATION_CONTROLLER_H_
#define ASH_SYSTEM_POWER_POWER_NOTIFICATION_CONTROLLER_H_

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/shell.h"
#include "ash/shell_observer.h"
#include "ash/system/power/battery_saver_controller.h"
#include "ash/system/power/power_status.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chromeos/dbus/power/power_manager_client.h"

namespace message_center {
class MessageCenter;
}  // namespace message_center

namespace ash {

class BatteryNotification;
class DualRoleNotification;

// Controller class to manage power/battery notifications.
class ASH_EXPORT PowerNotificationController
    : public PowerStatus::Observer,
      public chromeos::PowerManagerClient::Observer,
      public ash::ShellObserver {
 public:
  enum NotificationState {
    NOTIFICATION_NONE,

    // Low battery charge, different battery saver notification behavior.
    // Note: When battery saver is not available, both of these act like the
    // original low power notification.
    NOTIFICATION_BSM_ENABLING_AT_THRESHOLD,

    NOTIFICATION_BSM_THRESHOLD_OPT_IN,

    NOTIFICATION_GENERIC_LOW_POWER,

    // Critically low battery charge.
    NOTIFICATION_CRITICAL,
  };

  enum CriticalNotificationOutcome {
    // The device crashes, it includes the case when the battery is empty and
    // powerd does not have time to perform a graceful shutdown.
    Crashed = 1,
    // The device automatically shut down due to a low battery.
    LowBatteryShutdown = 2,
    // The critical notification is shown, its count should be greater than or
    // equal to the sum of all other outcomes.
    NotificationShown = 0,
    // The device is connected to a power source.
    PluggedIn = 3,
    // The device enters a suspended state.
    Suspended = 4,
    // The device is shut down gracefully by user.
    UserShutdown = 5,
    kMaxValue = UserShutdown,
  };

  // Time-based notification thresholds when on battery power.
  static constexpr int kCriticalMinutes = 5;
  static constexpr int kLowPowerMinutes = 15;
  static constexpr int kNoWarningMinutes = 30;

  // Percentage-based notification thresholds when using a low-power charger.
  static constexpr int kCriticalPercentage = 5;
  static constexpr int kLowPowerPercentage = 10;
  static constexpr int kNoWarningPercentage = 15;

  explicit PowerNotificationController(
      message_center::MessageCenter* message_center);

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

  ~PowerNotificationController() override;

  // static:
  static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);

  void NotifyUsbNotificationClosedByUser();
  void SetUserOptStatus(bool status);

  double GetLowPowerPercentage() const { return low_power_percentage_; }
  double GetCriticalPowerPercentage() const { return critical_percentage_; }
  double GetNoWarningPercentage() const { return no_warning_percentage_; }

  NotificationState GetNotificationState() const { return notification_state_; }

 private:
  FRIEND_TEST_ALL_PREFIXES(PowerNotificationControllerTest,
                           MaybeShowUsbChargerNotification);
  FRIEND_TEST_ALL_PREFIXES(PowerNotificationControllerTest,
                           UpdateNotificationState);
  friend class PowerNotificationControllerTest;
  friend class BatteryNotificationTest;

  // Overridden from PowerStatus::Observer.
  void OnPowerStatusChanged() override;

  // Overridden from PowerManagerClient::Observer:
  void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
  void ShutdownRequested(power_manager::RequestShutdownReason reason) override;
  void RestartRequested(power_manager::RequestRestartReason reason) override;

  // Overridden from ash::ShellObserver:
  void OnShellDestroying() override;

  // Shows a notification that a low-power USB charger has been connected.
  // Returns true if a notification was shown or explicitly hidden.
  bool MaybeShowUsbChargerNotification();

  // Shows a notification when dual-role devices are connected.
  void MaybeShowDualRoleNotification();

  // Records the outcome of a critical notification.
  void MaybeRecordCriticalNotificationOutcome(
      PowerNotificationController::CriticalNotificationOutcome outcome,
      base::TimeDelta duration);

  // Determines whether a Battery Saver Notification should be shown. Returns
  // true if a notification should be shown, or nullopt if none of the bsm
  // branches were triggered.
  std::optional<bool> HandleBatterySaverNotifications();

  // Sets |notification_state_|. Returns true if a notification should be shown.
  bool UpdateNotificationState();
  bool UpdateNotificationStateForRemainingTime();
  bool UpdateNotificationStateForRemainingPercentage();
  bool UpdateNotificationStateForRemainingPercentageBatterySaver();

  // Whether the device is plugged in during a critical battery state.
  bool PluggedInCriticalState();

  // Start a timer and update the kCriticalStateDuration every 15 seconds.
  void StartPeriodicUpdate();
  void UpdateCriticalNotificationDurationPrefs();

  // Reset the timestamp related to critical notification.
  void ResetCriticalNotificationTimestamp();

  static const char kUsbNotificationId[];

  raw_ptr<PrefService> local_state_;                             // Unowned.
  const raw_ptr<message_center::MessageCenter> message_center_;  // Unowned.
  std::unique_ptr<BatteryNotification> battery_notification_;
  std::unique_ptr<DualRoleNotification> dual_role_notification_;
  NotificationState notification_state_ = NOTIFICATION_NONE;

  // Was the battery full the last time OnPowerStatusChanged() was called?
  bool battery_was_full_ = false;

  // Was a USB charger connected the last time OnPowerStatusChanged() was
  // called?
  bool usb_charger_was_connected_ = false;

  // Was line power connected the last time onPowerStatusChanged() was called?
  bool line_power_was_connected_ = false;

  // Was the battery in critical state the last time onPowerStatusChanged() was
  // called?
  bool was_in_critical_state_ = false;

  // The remaining battery time the last time OnPowerStatusChanged() was called.
  // This value is utilized to determine the remaining battery time at the
  // moment the charger is connected.
  std::optional<base::TimeDelta> remaining_time_to_empty_from_critical_state_;

  // Has the user already dismissed a low-power notification? Should be set
  // back to false when all power sources are disconnected.
  bool usb_notification_dismissed_ = false;

  // Has the battery saver threshold been crossed? Also gets reset to false when
  // an AC charger is plugged in.
  bool battery_saver_triggered_ = false;

  // User opt status.
  bool user_opt_status_ = false;

  const double battery_saver_activation_charge_percent_;

  // Percentage-based notification thresholds for battery saver.
  // TODO(mwoj): Replace the static constexpr once data is collected from the
  // experiment.
  const int critical_percentage_;
  const int low_power_percentage_;
  const int no_warning_percentage_;

  // After critical notification shows, trigger
  // `UpdateCriticalNotificationDurationPrefs` periodically.
  base::RepeatingTimer timer_;

  // The time at which a critical notification is shown.
  base::TimeTicks critical_notification_shown_time_ = base::TimeTicks();

  // The observation on `ash::Shell`.
  base::ScopedObservation<ash::Shell, ash::ShellObserver> shell_observation_{
      this};
};

}  // namespace ash

#endif  // ASH_SYSTEM_POWER_POWER_NOTIFICATION_CONTROLLER_H_