chromium/chromeos/dbus/power/power_manager_client.cc

// 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 "chromeos/dbus/power/power_manager_client.h"

#include <stdint.h>

#include <algorithm>
#include <memory>
#include <unordered_map>
#include <utility>

#include "base/command_line.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_device_source.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/unguessable_token.h"
#include "chromeos/dbus/constants/dbus_switches.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "chromeos/dbus/power_manager/input_event.pb.h"
#include "chromeos/dbus/power_manager/peripheral_battery_status.pb.h"
#include "chromeos/dbus/power_manager/switch_states.pb.h"
#include "chromeos/dbus/power_manager/thermal.pb.h"
#include "components/device_event_log/device_event_log.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

namespace chromeos {

namespace {

PowerManagerClient* g_instance = nullptr;

// Maximum amount of time that the power manager will wait for Chrome to
// say that it's ready for the system to be suspended, in milliseconds.
const int kSuspendDelayTimeoutMs = 5000;

// Human-readable description of Chrome's suspend delay.
const char kSuspendDelayDescription[] = "chrome";

// Returns a modified version of |proto| where fields are consistent.
power_manager::PowerSupplyProperties SanitizePowerSupplyProperties(
    const power_manager::PowerSupplyProperties& proto) {
  power_manager::PowerSupplyProperties sanitized = proto;

  if (sanitized.battery_state() ==
      power_manager::PowerSupplyProperties_BatteryState_FULL) {
    sanitized.set_battery_percent(100.0);
  }

  if (!sanitized.is_calculating_battery_time()) {
    const bool on_line_power =
        sanitized.external_power() !=
        power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED;
    if ((on_line_power && sanitized.battery_time_to_full_sec() < 0) ||
        (!on_line_power && sanitized.battery_time_to_empty_sec() < 0)) {
      sanitized.set_is_calculating_battery_time(true);
    }
  }
  return sanitized;
}

// Converts a LidState value from a power_manager::SwitchStates proto to the
// corresponding PowerManagerClient::LidState value.
PowerManagerClient::LidState GetLidStateFromProtoEnum(
    power_manager::SwitchStates::LidState state) {
  switch (state) {
    case power_manager::SwitchStates_LidState_OPEN:
      return PowerManagerClient::LidState::OPEN;
    case power_manager::SwitchStates_LidState_CLOSED:
      return PowerManagerClient::LidState::CLOSED;
    case power_manager::SwitchStates_LidState_NOT_PRESENT:
      return PowerManagerClient::LidState::NOT_PRESENT;
  }
  NOTREACHED_IN_MIGRATION() << "Unhandled lid state " << state;
  return PowerManagerClient::LidState::NOT_PRESENT;
}

// Converts a TabletMode value from a power_manager::SwitchStates proto to the
// corresponding PowerManagerClient::TabletMode value.
PowerManagerClient::TabletMode GetTabletModeFromProtoEnum(
    power_manager::SwitchStates::TabletMode mode) {
  switch (mode) {
    case power_manager::SwitchStates_TabletMode_ON:
      return PowerManagerClient::TabletMode::ON;
    case power_manager::SwitchStates_TabletMode_OFF:
      return PowerManagerClient::TabletMode::OFF;
    case power_manager::SwitchStates_TabletMode_UNSUPPORTED:
      return PowerManagerClient::TabletMode::UNSUPPORTED;
  }
  NOTREACHED_IN_MIGRATION() << "Unhandled tablet mode " << mode;
  return PowerManagerClient::TabletMode::UNSUPPORTED;
}

// Converts a ThermalState value from a power_manager::ThermalEvent proto to the
// corresponding base::PowerThermalObserver::DeviceThermalState value.
base::PowerThermalObserver::DeviceThermalState GetThermalStateFromProtoEnum(
    power_manager::ThermalEvent::ThermalState state) {
  switch (state) {
    case power_manager::ThermalEvent_ThermalState_UNKNOWN:
      return base::PowerThermalObserver::DeviceThermalState::kUnknown;
    case power_manager::ThermalEvent_ThermalState_NOMINAL:
      return base::PowerThermalObserver::DeviceThermalState::kNominal;
    case power_manager::ThermalEvent_ThermalState_FAIR:
      return base::PowerThermalObserver::DeviceThermalState::kFair;
    case power_manager::ThermalEvent_ThermalState_SERIOUS:
      return base::PowerThermalObserver::DeviceThermalState::kSerious;
    case power_manager::ThermalEvent_ThermalState_CRITICAL:
      return base::PowerThermalObserver::DeviceThermalState::kCritical;
  }
  NOTREACHED_IN_MIGRATION() << "Unhandled thermal state " << state;
  return base::PowerThermalObserver::DeviceThermalState::kUnknown;
}

// Callback for D-Bus call made in |CreateArcTimers|.
void OnCreateArcTimersDBusMethod(
    DBusMethodCallback<std::vector<PowerManagerClient::TimerId>> callback,
    dbus::Response* response) {
  if (response == nullptr) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  dbus::MessageReader reader(response);
  dbus::MessageReader array_reader(nullptr);
  if (!reader.PopArray(&array_reader)) {
    POWER_LOG(ERROR) << "No timer ids returned";
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::vector<PowerManagerClient::TimerId> timer_ids;
  while (array_reader.HasMoreData()) {
    int32_t timer_id;
    if (!array_reader.PopInt32(&timer_id)) {
      POWER_LOG(ERROR) << "Failed to pop timer id";
      std::move(callback).Run(std::nullopt);
      return;
    }
    timer_ids.push_back(timer_id);
  }
  std::move(callback).Run(std::move(timer_ids));
}

// Callback for D-Bus call made in |StartArcTimer| and |DeleteArcTimers|.
void OnVoidDBusMethod(VoidDBusMethodCallback callback,
                      dbus::Response* response) {
  std::move(callback).Run(response != nullptr);
}

}  // namespace

// The PowerManagerClient implementation used in production.
class PowerManagerClientImpl : public PowerManagerClient {
 public:
  PowerManagerClientImpl()
      : origin_thread_id_(base::PlatformThread::CurrentId()) {}

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

  ~PowerManagerClientImpl() override {
    // Here we should unregister suspend notifications from powerd,
    // however:
    // - The lifetime of the PowerManagerClientImpl can extend past that of
    //   the objectproxy,
    // - power_manager can already detect that the client is gone and
    //   unregister our suspend delay.
  }

  void Init(dbus::Bus* bus) {
    power_manager_proxy_ = bus->GetObjectProxy(
        power_manager::kPowerManagerServiceName,
        dbus::ObjectPath(power_manager::kPowerManagerServicePath));

    power_manager_proxy_->SetNameOwnerChangedCallback(
        base::BindRepeating(&PowerManagerClientImpl::NameOwnerChangedReceived,
                            weak_ptr_factory_.GetWeakPtr()));

    power_manager_proxy_->WaitForServiceToBeAvailable(
        base::BindOnce(&PowerManagerClientImpl::NotifyServiceBecameAvailable,
                       weak_ptr_factory_.GetWeakPtr()));

    // Listen to D-Bus signals emitted by powerd.
    typedef void (PowerManagerClientImpl::*SignalMethod)(dbus::Signal*);
    const std::pair<const char*, SignalMethod> kSignalMethods[] = {
        {power_manager::kScreenBrightnessChangedSignal,
         &PowerManagerClientImpl::ScreenBrightnessChangedReceived},
        {power_manager::kAmbientLightSensorEnabledChangedSignal,
         &PowerManagerClientImpl::AmbientLightSensorEnabledChangedReceived},
        {power_manager::kKeyboardAmbientLightSensorEnabledChangedSignal,
         &PowerManagerClientImpl::
             KeyboardAmbientLightSensorEnabledChangedReceived},
        {power_manager::kAmbientColorTemperatureChangedSignal,
         &PowerManagerClientImpl::AmbientColorTemperatureChangedReceived},
        {power_manager::kKeyboardBrightnessChangedSignal,
         &PowerManagerClientImpl::KeyboardBrightnessChangedReceived},
        {power_manager::kScreenIdleStateChangedSignal,
         &PowerManagerClientImpl::ScreenIdleStateChangedReceived},
        {power_manager::kInactivityDelaysChangedSignal,
         &PowerManagerClientImpl::InactivityDelaysChangedReceived},
        {power_manager::kBatterySaverModeStateChanged,
         &PowerManagerClientImpl::BatterySaverModeStateChangedReceived},
        {power_manager::kPeripheralBatteryStatusSignal,
         &PowerManagerClientImpl::PeripheralBatteryStatusReceived},
        {power_manager::kPowerSupplyPollSignal,
         &PowerManagerClientImpl::PowerSupplyPollReceived},
        {power_manager::kInputEventSignal,
         &PowerManagerClientImpl::InputEventReceived},
        {power_manager::kSuspendImminentSignal,
         &PowerManagerClientImpl::SuspendImminentReceived},
        {power_manager::kSuspendDoneSignal,
         &PowerManagerClientImpl::SuspendDoneReceived},
        {power_manager::kDarkSuspendImminentSignal,
         &PowerManagerClientImpl::DarkSuspendImminentReceived},
        {power_manager::kIdleActionImminentSignal,
         &PowerManagerClientImpl::IdleActionImminentReceived},
        {power_manager::kIdleActionDeferredSignal,
         &PowerManagerClientImpl::IdleActionDeferredReceived},
        {power_manager::kThermalEventSignal,
         &PowerManagerClientImpl::ThermalEventReceived},
    };
    auto on_connected_callback =
        base::BindRepeating(&PowerManagerClientImpl::SignalConnected,
                            weak_ptr_factory_.GetWeakPtr());
    for (const auto& p : kSignalMethods) {
      power_manager_proxy_->ConnectToSignal(
          power_manager::kPowerManagerInterface, p.first,
          base::BindRepeating(p.second, weak_ptr_factory_.GetWeakPtr()),
          on_connected_callback);
    }

    RegisterSuspendDelays();
    RequestStatusUpdate();
    RequestThermalState();
  }

  // PowerManagerClient overrides:

  void AddObserver(Observer* observer) override {
    DCHECK(observer);  // http://crbug.com/119976
    observers_.AddObserver(observer);

    if (service_available_)
      observer->PowerManagerBecameAvailable(*service_available_);
  }

  void RemoveObserver(Observer* observer) override {
    observers_.RemoveObserver(observer);
  }

  bool HasObserver(const Observer* observer) const override {
    return observers_.HasObserver(observer);
  }

  void SetRenderProcessManagerDelegate(
      base::WeakPtr<RenderProcessManagerDelegate> delegate) override {
    DCHECK(!render_process_manager_delegate_)
        << "There can be only one! ...RenderProcessManagerDelegate";
    render_process_manager_delegate_ = delegate;
  }

  void DecreaseScreenBrightness(bool allow_off) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kDecreaseScreenBrightnessMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendBool(allow_off);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void IncreaseScreenBrightness() override {
    SimpleMethodCallToPowerManager(
        power_manager::kIncreaseScreenBrightnessMethod);
  }

  void HasKeyboardBacklight(DBusMethodCallback<bool> callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kHasKeyboardBacklightMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetHasKeyboardBacklight,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void DecreaseKeyboardBrightness() override {
    SimpleMethodCallToPowerManager(
        power_manager::kDecreaseKeyboardBrightnessMethod);
  }

  void IncreaseKeyboardBrightness() override {
    SimpleMethodCallToPowerManager(
        power_manager::kIncreaseKeyboardBrightnessMethod);
  }

  const std::optional<power_manager::PowerSupplyProperties>& GetLastStatus()
      override {
    return proto_;
  }

  void SetScreenBrightness(
      const power_manager::SetBacklightBrightnessRequest& request) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetScreenBrightnessMethod);
    if (!dbus::MessageWriter(&method_call).AppendProtoAsArrayOfBytes(request)) {
      POWER_LOG(ERROR) << "Error serializing "
                       << power_manager::kSetScreenBrightnessMethod
                       << " request";
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetScreenBrightnessPercent(
      DBusMethodCallback<double> callback) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kGetScreenBrightnessPercentMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PowerManagerClientImpl::OnGetScreenOrKeyboardBrightnessPercent,
            weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetAmbientLightSensorEnabled(
      const power_manager::SetAmbientLightSensorEnabledRequest& request)
      override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kSetAmbientLightSensorEnabledMethod);
    if (!dbus::MessageWriter(&method_call).AppendProtoAsArrayOfBytes(request)) {
      POWER_LOG(ERROR) << "Error serializing "
                       << power_manager::kSetAmbientLightSensorEnabledMethod
                       << " request";
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetAmbientLightSensorEnabled(
      DBusMethodCallback<bool> callback) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kGetAmbientLightSensorEnabledMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetAmbientLightSensorEnabled,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void HasAmbientLightSensor(DBusMethodCallback<bool> callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kHasAmbientLightSensorMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetHasAmbientLightSensor,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetKeyboardBrightnessPercent(
      DBusMethodCallback<double> callback) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kGetKeyboardBrightnessPercentMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PowerManagerClientImpl::OnGetScreenOrKeyboardBrightnessPercent,
            weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetKeyboardBrightness(
      const power_manager::SetBacklightBrightnessRequest& request) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetKeyboardBrightnessMethod);
    if (!dbus::MessageWriter(&method_call).AppendProtoAsArrayOfBytes(request)) {
      POWER_LOG(ERROR) << "Error serializing "
                       << power_manager::kSetKeyboardBrightnessMethod
                       << " request";
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void RequestStatusUpdate() override {
    POWER_LOG(USER) << "RequestStatusUpdate";
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kGetPowerSupplyPropertiesMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PowerManagerClientImpl::OnGetPowerSupplyPropertiesMethod,
            weak_ptr_factory_.GetWeakPtr()));
  }

  void RequestAllPeripheralBatteryUpdate() override {
    POWER_LOG(USER) << "RequestAllPeripheralBatteryUpdate";
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kRefreshAllPeripheralBatteryMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PowerManagerClientImpl::OnRefreshAllPeripheralBatteryMethod,
            weak_ptr_factory_.GetWeakPtr()));
  }

  void RequestThermalState() override {
    POWER_LOG(USER) << "RequestThermalState";
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kGetThermalStateMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetCurrentThermalStateMethod,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  void RequestSuspend(std::optional<uint64_t> wakeup_count,
                      int32_t duration_secs,
                      power_manager::RequestSuspendFlavor flavor) override {
    auto wakeup_count_value = wakeup_count.value_or(-1ULL);
    POWER_LOG(USER) << "RequestSuspend: wakeup_count=" << wakeup_count_value
                    << ", duration_secs=" << duration_secs
                    << ", flavor=" << flavor;
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kRequestSuspendMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendUint64(wakeup_count_value);
    writer.AppendInt32(duration_secs);
    writer.AppendUint32(flavor);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void RequestRestart(power_manager::RequestRestartReason reason,
                      const std::string& description) override {
    POWER_LOG(USER) << "RequestRestart: " << reason << " (" << description
                    << ")";
    for (auto& observer : observers_)
      observer.RestartRequested(reason);
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kRequestRestartMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendInt32(reason);
    writer.AppendString(description);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void RequestShutdown(power_manager::RequestShutdownReason reason,
                       const std::string& description) override {
    POWER_LOG(USER) << "RequestShutdown: " << reason << " (" << description
                    << ")";
    for (auto& observer : observers_)
      observer.ShutdownRequested(reason);
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kRequestShutdownMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendInt32(reason);
    writer.AppendString(description);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void NotifyUserActivity(power_manager::UserActivityType type) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kHandleUserActivityMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendInt32(type);

    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void NotifyVideoActivity(bool is_fullscreen) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kHandleVideoActivityMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendBool(is_fullscreen);

    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void NotifyWakeNotification() override {
    SimpleMethodCallToPowerManager(
        power_manager::kHandleWakeNotificationMethod);
  }

  void SetPolicy(const power_manager::PowerManagementPolicy& policy) override {
    POWER_LOG(USER) << "SetPolicy";
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetPolicyMethod);
    dbus::MessageWriter writer(&method_call);
    if (!writer.AppendProtoAsArrayOfBytes(policy)) {
      POWER_LOG(ERROR) << "Error calling " << power_manager::kSetPolicyMethod;
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void SetIsProjecting(bool is_projecting) override {
    POWER_LOG(USER) << "SetIsProjecting";
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetIsProjectingMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendBool(is_projecting);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
    last_is_projecting_ = is_projecting;
  }

  void SetPowerSource(const std::string& id) override {
    POWER_LOG(USER) << "SetPowerSource: " << id;
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetPowerSourceMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(id);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void SetBacklightsForcedOff(bool forced_off) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetBacklightsForcedOffMethod);
    dbus::MessageWriter(&method_call).AppendBool(forced_off);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetBacklightsForcedOff(DBusMethodCallback<bool> callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kGetBacklightsForcedOffMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetBacklightsForcedOff,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void ToggleKeyboardBacklight() override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kToggleKeyboardBacklightMethod);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void SetKeyboardAmbientLightSensorEnabled(
      const power_manager::SetAmbientLightSensorEnabledRequest& request)
      override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kSetKeyboardAmbientLightSensorEnabledMethod);
    if (!dbus::MessageWriter(&method_call).AppendProtoAsArrayOfBytes(request)) {
      POWER_LOG(ERROR)
          << "Error serializing "
          << power_manager::kSetKeyboardAmbientLightSensorEnabledMethod
          << " request";
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetKeyboardAmbientLightSensorEnabled(
      DBusMethodCallback<bool> callback) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kGetKeyboardAmbientLightSensorEnabledMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PowerManagerClientImpl::OnGetKeyboardAmbientLightSensorEnabled,
            weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetBatterySaverModeState(
      DBusMethodCallback<power_manager::BatterySaverModeState> callback)
      override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kGetBatterySaverModeState);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetBatterySaverModeState,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetBatterySaverModeState(
      const power_manager::SetBatterySaverModeStateRequest& request) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kSetBatterySaverModeState);
    dbus::MessageWriter writer(&method_call);
    if (!writer.AppendProtoAsArrayOfBytes(request)) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kSetBatterySaverModeState;
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetSwitchStates(DBusMethodCallback<SwitchStates> callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kGetSwitchStatesMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetSwitchStates,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetInactivityDelays(
      DBusMethodCallback<power_manager::PowerManagementPolicy::Delays> callback)
      override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kGetInactivityDelaysMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetInactivityDelays,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void BlockSuspend(const base::UnguessableToken& token,
                    const std::string& debug_info) override {
    DCHECK(OnOriginThread());
    DCHECK(suspend_is_pending_);
    DCHECK(token);

    suspend_readiness_registry_[token.ToString()] = {
        pending_suspend_id_, suspending_from_dark_resume_, debug_info};
  }

  void UnblockSuspend(const base::UnguessableToken& token) override {
    DCHECK(OnOriginThread());
    auto registration = suspend_readiness_registry_.find(token.ToString());

    if (registration == suspend_readiness_registry_.end() ||
        registration->second.suspend_id != pending_suspend_id_ ||
        registration->second.suspending_from_dark_resume !=
            suspending_from_dark_resume_ ||
        !suspend_is_pending_) {
      return;
    }

    suspend_readiness_registry_.erase(registration);
    MaybeReportSuspendReadiness();
  }

  void CreateArcTimers(
      const std::string& tag,
      std::vector<std::pair<clockid_t, base::ScopedFD>> arc_timer_requests,
      DBusMethodCallback<std::vector<TimerId>> callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kCreateArcTimersMethod);

    // Write mojo arguments i.e. client's tag and array of {int, fd} as a D-Bus
    // message.
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(tag);
    dbus::MessageWriter array_writer(nullptr);
    writer.OpenArray("(ih)", &array_writer);
    for (const auto& request : arc_timer_requests) {
      dbus::MessageWriter struct_writer(nullptr);
      array_writer.OpenStruct(&struct_writer);
      struct_writer.AppendInt32(static_cast<int32_t>(request.first));
      // This dups the file descriptor and the original one stored as
      // base::ScopedFD in |arc_timer_requests| will be closed when the function
      // ends and it goes out of scope.
      struct_writer.AppendFileDescriptor(request.second.get());
      array_writer.CloseContainer(&struct_writer);
    }
    writer.CloseContainer(&array_writer);

    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&OnCreateArcTimersDBusMethod, std::move(callback)));
  }

  void StartArcTimer(TimerId timer_id,
                     base::TimeTicks absolute_expiration_time,
                     VoidDBusMethodCallback callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kStartArcTimerMethod);

    // Write clock id and 64-bit expiration time ticks value as a D-Bus message.
    dbus::MessageWriter writer(&method_call);
    writer.AppendInt32(timer_id);
    // The absolute ticks are still being sent because base::TimeTicks() returns
    // 0.
    writer.AppendInt64(
        (absolute_expiration_time - base::TimeTicks()).InMicroseconds());

    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&OnVoidDBusMethod, std::move(callback)));
  }

  void DeleteArcTimers(const std::string& tag,
                       VoidDBusMethodCallback callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kDeleteArcTimersMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(tag);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&OnVoidDBusMethod, std::move(callback)));
  }

  base::TimeDelta GetDarkSuspendDelayTimeout() override {
    return max_dark_suspend_delay_timeout_;
  }

  void SetExternalDisplayALSBrightness(bool enabled) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kSetExternalDisplayALSBrightnessMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendBool(enabled);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetExternalDisplayALSBrightness(
      DBusMethodCallback<bool> callback) override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kGetExternalDisplayALSBrightnessMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PowerManagerClientImpl::OnGetExternalDisplayALSBrightness,
            weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void ChargeNowForAdaptiveCharging() override {
    dbus::MethodCall method_call(
        power_manager::kPowerManagerInterface,
        power_manager::kChargeNowForAdaptiveChargingMethod);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void GetChargeHistoryForAdaptiveCharging(
      DBusMethodCallback<power_manager::ChargeHistoryState> callback) override {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 power_manager::kGetChargeHistoryMethod);
    power_manager_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PowerManagerClientImpl::OnGetChargeHistory,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

 private:
  // Returns true if the current thread is the origin thread.
  bool OnOriginThread() {
    return base::PlatformThread::CurrentId() == origin_thread_id_;
  }

  // Called when a dbus signal is initially connected.
  void SignalConnected(const std::string& interface_name,
                       const std::string& signal_name,
                       bool success) {
    if (!success)
      POWER_LOG(ERROR) << "Failed to connect to signal " << signal_name << ".";
  }

  // Makes a method call to power manager with no arguments and no response.
  void SimpleMethodCallToPowerManager(const std::string& method_name) {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 method_name);
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  void NotifyServiceBecameAvailable(bool available) {
    service_available_ = available;
    for (auto& observer : observers_)
      observer.PowerManagerBecameAvailable(available);
  }

  void NameOwnerChangedReceived(const std::string& old_owner,
                                const std::string& new_owner) {
    POWER_LOG(EVENT) << "Power manager restarted. Old owner: "
                     << (old_owner.empty() ? "[none]" : old_owner.c_str())
                     << " New owner: "
                     << (new_owner.empty() ? "[none]" : new_owner.c_str());
    suspend_is_pending_ = false;
    pending_suspend_id_ = -1;
    suspending_from_dark_resume_ = false;
    if (!new_owner.empty()) {
      POWER_LOG(EVENT) << "Sending initial state to power manager";
      RegisterSuspendDelays();
      SetIsProjecting(last_is_projecting_);
      for (auto& observer : observers_)
        observer.PowerManagerRestarted();
    }
  }

  void NotifyInitialization() {
    for (auto& observer : observers_)
      observer.PowerManagerInitialized();
  }

  void ScreenBrightnessChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::BacklightBrightnessChange proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kScreenBrightnessChangedSignal
                       << " signal";
      return;
    }
    POWER_LOG(DEBUG) << "Screen brightness changed to " << proto.percent()
                     << ": cause " << proto.cause();
    for (auto& observer : observers_)
      observer.ScreenBrightnessChanged(proto);
  }

  void AmbientLightSensorEnabledChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::AmbientLightSensorChange proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kAmbientLightSensorEnabledChangedSignal
                       << " signal";
      return;
    }
    POWER_LOG(DEBUG) << "Ambient Light Sensor enabled changed to "
                     << proto.sensor_enabled() << ": cause " << proto.cause();
    for (auto& observer : observers_) {
      observer.AmbientLightSensorEnabledChanged(proto);
    }
  }

  void KeyboardAmbientLightSensorEnabledChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::AmbientLightSensorChange proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR)
          << "Unable to decode protocol buffer from "
          << power_manager::kKeyboardAmbientLightSensorEnabledChangedSignal
          << " signal";
      return;
    }
    POWER_LOG(DEBUG) << "Keyboard ambient Light Sensor enabled changed to "
                     << proto.sensor_enabled() << ": cause " << proto.cause();
    for (auto& observer : observers_) {
      observer.KeyboardAmbientLightSensorEnabledChanged(proto);
    }
  }

  void AmbientColorTemperatureChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    int32_t color_temperature = 0;
    if (!reader.PopInt32(&color_temperature)) {
      POWER_LOG(ERROR) << "Unable to decode read ambient color from "
                       << power_manager::kAmbientColorTemperatureChangedSignal
                       << " signal";
      return;
    }

    for (auto& observer : observers_)
      observer.AmbientColorChanged(color_temperature);
  }

  void KeyboardBrightnessChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::BacklightBrightnessChange proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kKeyboardBrightnessChangedSignal
                       << " signal";
      return;
    }
    POWER_LOG(DEBUG) << "Keyboard brightness changed to " << proto.percent()
                     << ": cause " << proto.cause();
    for (auto& observer : observers_)
      observer.KeyboardBrightnessChanged(proto);
  }

  void ScreenIdleStateChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::ScreenIdleState proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kScreenIdleStateChangedSignal
                       << " signal";
      return;
    }
    for (auto& observer : observers_)
      observer.ScreenIdleStateChanged(proto);
  }

  void InactivityDelaysChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::PowerManagementPolicy::Delays proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kInactivityDelaysChangedSignal
                       << " signal";
      return;
    }
    for (auto& observer : observers_)
      observer.InactivityDelaysChanged(proto);
  }

  void BatterySaverModeStateChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::BatterySaverModeState proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kBatterySaverModeStateChanged
                       << " signal";
      return;
    }
    for (auto& observer : observers_) {
      observer.BatterySaverModeStateChanged(proto);
    }
  }

  void PeripheralBatteryStatusReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::PeripheralBatteryStatus protobuf_status;
    if (!reader.PopArrayOfBytesAsProto(&protobuf_status)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kPeripheralBatteryStatusSignal
                       << " signal";
      return;
    }

    std::string path = protobuf_status.path();
    std::string name = protobuf_status.name();
    int level = protobuf_status.has_level() ? protobuf_status.level() : -1;
    power_manager::PeripheralBatteryStatus_ChargeStatus status =
        protobuf_status.has_charge_status()
            ? protobuf_status.charge_status()
            : power_manager::
                  PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_UNKNOWN;
    bool active_update = protobuf_status.has_active_update()
                             ? protobuf_status.active_update()
                             : false;
    std::string serial_number = "";
    if (protobuf_status.has_serial_number())
      serial_number = protobuf_status.serial_number();

    for (auto& observer : observers_)
      observer.PeripheralBatteryStatusReceived(path, name, level, status,
                                               serial_number, active_update);
  }

  void PowerSupplyPollReceived(dbus::Signal* signal) {
    POWER_LOG(DEBUG) << "Received power supply poll signal.";
    dbus::MessageReader reader(signal);
    power_manager::PowerSupplyProperties protobuf;
    if (reader.PopArrayOfBytesAsProto(&protobuf)) {
      HandlePowerSupplyProperties(protobuf);
    } else {
      POWER_LOG(ERROR) << "Unable to decode "
                       << power_manager::kPowerSupplyPollSignal << " signal";
    }
  }

  void OnGetCurrentThermalStateMethod(dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetThermalStateMethod;
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::ThermalEvent protobuf;
    if (!reader.PopArrayOfBytesAsProto(&protobuf)) {
      POWER_LOG(ERROR) << "Unable to decode "
                       << power_manager::kGetThermalStateMethod << " response";
      return;
    }

    POWER_LOG(USER) << "Got " << power_manager::kGetThermalStateMethod
                    << " response:"
                    << " thermal_state=" << protobuf.thermal_state()
                    << " timestamp=" << protobuf.timestamp();
    base::PowerMonitorDeviceSource::ThermalEventReceived(
        GetThermalStateFromProtoEnum(protobuf.thermal_state()));
  }

  void OnGetPowerSupplyPropertiesMethod(dbus::Response* response) {
    // This is the last callback to run after all the initialization in |Init|.
    // Notify all observers that the initialization is complete.
    absl::Cleanup notify_runner = [this] { NotifyInitialization(); };

    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetPowerSupplyPropertiesMethod;
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::PowerSupplyProperties protobuf;
    if (reader.PopArrayOfBytesAsProto(&protobuf)) {
      HandlePowerSupplyProperties(protobuf);
    } else {
      POWER_LOG(ERROR) << "Unable to decode "
                       << power_manager::kGetPowerSupplyPropertiesMethod
                       << " response";
    }
  }

  void OnRefreshAllPeripheralBatteryMethod(dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kRefreshAllPeripheralBatteryMethod;
      return;
    }
  }

  void OnGetHasAmbientLightSensor(DBusMethodCallback<bool> callback,
                                  dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    bool has_ambient_light_sensor = false;
    if (!reader.PopBool(&has_ambient_light_sensor)) {
      POWER_LOG(ERROR) << "Error reading response from powerd: "
                       << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(has_ambient_light_sensor);
  }

  void OnGetAmbientLightSensorEnabled(DBusMethodCallback<bool> callback,
                                      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    bool is_ambient_light_sensor_enabled = false;
    if (!reader.PopBool(&is_ambient_light_sensor_enabled)) {
      POWER_LOG(ERROR) << "Error reading response from powerd: "
                       << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(is_ambient_light_sensor_enabled);
  }

  void OnGetScreenOrKeyboardBrightnessPercent(
      DBusMethodCallback<double> callback,
      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    double percent = 0.0;
    if (!reader.PopDouble(&percent)) {
      POWER_LOG(ERROR) << "Error reading response from powerd: "
                       << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(percent);
  }

  void OnGetHasKeyboardBacklight(DBusMethodCallback<bool> callback,
                                 dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kHasKeyboardBacklightMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    bool state = false;
    if (!reader.PopBool(&state)) {
      POWER_LOG(ERROR) << "Error reading response from powerd: "
                       << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(state);
  }

  void OnGetKeyboardAmbientLightSensorEnabled(DBusMethodCallback<bool> callback,
                                              dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR)
          << "Error calling "
          << power_manager::kGetKeyboardAmbientLightSensorEnabledMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    bool state = false;
    if (!reader.PopBool(&state)) {
      POWER_LOG(ERROR) << "Error reading response from powerd: "
                       << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(state);
  }

  void OnGetBacklightsForcedOff(DBusMethodCallback<bool> callback,
                                dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetBacklightsForcedOffMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    bool state = false;
    if (!reader.PopBool(&state)) {
      POWER_LOG(ERROR) << "Error reading response from powerd: "
                       << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(state);
  }

  void OnGetBatterySaverModeState(
      DBusMethodCallback<power_manager::BatterySaverModeState> callback,
      dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetBatterySaverModeState;
      std::move(callback).Run(std::nullopt);
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::BatterySaverModeState proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Error parsing response from "
                       << power_manager::kGetBatterySaverModeState;
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(proto);
  }

  void OnGetSwitchStates(DBusMethodCallback<SwitchStates> callback,
                         dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetSwitchStatesMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::SwitchStates proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Error parsing response from "
                       << power_manager::kGetSwitchStatesMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(
        SwitchStates{GetLidStateFromProtoEnum(proto.lid_state()),
                     GetTabletModeFromProtoEnum(proto.tablet_mode())});
  }

  void OnGetChargeHistory(
      DBusMethodCallback<power_manager::ChargeHistoryState> callback,
      dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetChargeHistoryMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }

    // powerd returns an error response if the charge history is not
    // initialized yet.
    if (response->GetMessageType() ==
        dbus::ErrorResponse::MessageType::MESSAGE_ERROR) {
      POWER_LOG(ERROR) << "Cannot get charge history from "
                       << power_manager::kGetChargeHistoryMethod
                       << " because it's not initialized yet.";
      std::move(callback).Run(std::nullopt);
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::ChargeHistoryState proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Error parsing response from "
                       << power_manager::kGetChargeHistoryMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(proto);
  }

  void OnGetInactivityDelays(
      DBusMethodCallback<power_manager::PowerManagementPolicy::Delays> callback,
      dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetInactivityDelaysMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::PowerManagementPolicy::Delays proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Error parsing response from "
                       << power_manager::kGetInactivityDelaysMethod;
      std::move(callback).Run(std::nullopt);
      return;
    }
    std::move(callback).Run(proto);
  }

  void OnGetExternalDisplayALSBrightness(DBusMethodCallback<bool> callback,
                                         dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling "
                       << power_manager::kGetExternalDisplayALSBrightnessMethod;
      std::move(callback).Run(false);
      return;
    }

    dbus::MessageReader reader(response);
    bool enabled = false;
    if (!reader.PopBool(&enabled)) {
      POWER_LOG(ERROR) << "Error parsing response from "
                       << power_manager::kGetExternalDisplayALSBrightnessMethod;
      std::move(callback).Run(false);
      return;
    }
    std::move(callback).Run(enabled);
  }

  void HandlePowerSupplyProperties(
      const power_manager::PowerSupplyProperties& proto) {
    proto_ = SanitizePowerSupplyProperties(proto);
    for (auto& observer : observers_)
      observer.PowerChanged(*proto_);
    const bool on_battery =
        proto_->external_power() ==
        power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED;
    base::PowerMonitorDeviceSource::SetPowerSource(
        on_battery
            ? base::PowerStateObserver::BatteryPowerStatus::kBatteryPower
            : base::PowerStateObserver::BatteryPowerStatus::kExternalPower);
  }

  void HandleRegisterSuspendDelayReply(bool dark_suspend,
                                       const std::string& method_name,
                                       dbus::Response* response) {
    if (!response) {
      POWER_LOG(ERROR) << "Error calling " << method_name;
      return;
    }

    dbus::MessageReader reader(response);
    power_manager::RegisterSuspendDelayReply protobuf;
    if (!reader.PopArrayOfBytesAsProto(&protobuf)) {
      POWER_LOG(ERROR) << "Unable to parse reply from " << method_name;
      return;
    }

    if (dark_suspend) {
      dark_suspend_delay_id_ = protobuf.delay_id();
      has_dark_suspend_delay_id_ = true;

      // Set |max_dark_suspend_delay_timeout_| to the minimum time power manager
      // guarantees before resuspending.
      max_dark_suspend_delay_timeout_ =
          base::Milliseconds(protobuf.min_delay_timeout_ms());

      POWER_LOG(EVENT) << "Registered dark suspend delay "
                       << dark_suspend_delay_id_;
    } else {
      suspend_delay_id_ = protobuf.delay_id();
      has_suspend_delay_id_ = true;
      POWER_LOG(EVENT) << "Registered suspend delay " << suspend_delay_id_;
    }
  }

  void SuspendImminentReceived(dbus::Signal* signal) {
    HandleSuspendImminent(false /* in_dark_resume */, signal);
  }

  void DarkSuspendImminentReceived(dbus::Signal* signal) {
    HandleSuspendImminent(true /* in_dark_resume */, signal);
  }

  void HandleSuspendImminent(bool in_dark_resume, dbus::Signal* signal) {
    std::string signal_name = signal->GetMember();
    if ((in_dark_resume && !has_dark_suspend_delay_id_) ||
        (!in_dark_resume && !has_suspend_delay_id_)) {
      POWER_LOG(ERROR) << "Received unrequested " << signal_name << " signal";
      return;
    }

    dbus::MessageReader reader(signal);
    power_manager::SuspendImminent proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << signal_name << " signal";
      return;
    }

    POWER_LOG(EVENT) << "Got " << signal_name
                     << " signal announcing suspend attempt "
                     << proto.suspend_id();

    // If a previous suspend is pending from the same state we are currently in
    // (fully powered on or in dark resume), then something's gone a little
    // wonky.
    if (suspend_is_pending_ && suspending_from_dark_resume_ == in_dark_resume) {
      POWER_LOG(ERROR) << "Got " << signal_name
                       << " signal about pending suspend attempt "
                       << proto.suspend_id()
                       << " while still waiting on attempt "
                       << pending_suspend_id_;
    }

    pending_suspend_id_ = proto.suspend_id();
    suspend_is_pending_ = true;
    suspending_from_dark_resume_ = in_dark_resume;
    suspend_readiness_registry_.clear();

    // Record the fact that observers are being notified to ensure that we don't
    // report readiness prematurely if one of them calls BlockSuspend() and then
    // runs UnblockSuspend() synchonously instead of asynchronously.
    notifying_observers_about_suspend_imminent_ = true;
    if (suspending_from_dark_resume_) {
      for (auto& observer : observers_)
        observer.DarkSuspendImminent();
    } else {
      for (auto& observer : observers_)
        observer.SuspendImminent(proto.reason());
    }
    notifying_observers_about_suspend_imminent_ = false;

    base::PowerMonitorDeviceSource::HandleSystemSuspending();
    MaybeReportSuspendReadiness();
  }

  void SuspendDoneReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::SuspendDone proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kSuspendDoneSignal << " signal";
      return;
    }

    const base::TimeDelta duration =
        base::TimeDelta::FromInternalValue(proto.suspend_duration());
    POWER_LOG(EVENT) << "Got " << power_manager::kSuspendDoneSignal
                     << " signal:"
                     << " suspend_id=" << proto.suspend_id()
                     << " duration=" << duration.InSeconds() << " sec"
                     << " deepest_state=" << proto.deepest_state();

    // RenderProcessManagerDelegate is only notified that suspend is imminent
    // when readiness is being reported to powerd. If the suspend attempt was
    // cancelled before then, we shouldn't notify the delegate about completion.
    const bool cancelled_while_regular_suspend_pending =
        suspend_is_pending_ && !suspending_from_dark_resume_;
    if (render_process_manager_delegate_ &&
        !cancelled_while_regular_suspend_pending)
      render_process_manager_delegate_->SuspendDone();

    // powerd always pairs each SuspendImminent signal with SuspendDone before
    // starting the next suspend attempt, so we should no longer report
    // readiness for any in-progress suspend attempts.
    pending_suspend_id_ = -1;
    suspend_is_pending_ = false;
    suspending_from_dark_resume_ = false;

    // powerd gives clients a limited amount of time to report suspend
    // readiness. Log the stragglers within Chrome to aid in debugging.
    for (const auto& it : suspend_readiness_registry_) {
      LOG(WARNING) << "Didn't report suspend readiness due to "
                   << it.second.debug_info;
    }
    suspend_readiness_registry_.clear();

    for (auto& observer : observers_)
      observer.SuspendDoneEx(proto);
    base::PowerMonitorDeviceSource::HandleSystemResumed();
  }

  void IdleActionImminentReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::IdleActionImminent proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kIdleActionImminentSignal << " signal";
      return;
    }
    for (auto& observer : observers_) {
      observer.IdleActionImminent(
          base::TimeDelta::FromInternalValue(proto.time_until_idle_action()));
    }
  }

  void IdleActionDeferredReceived(dbus::Signal* signal) {
    for (auto& observer : observers_)
      observer.IdleActionDeferred();
  }

  void InputEventReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::InputEvent proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kInputEventSignal << " signal";
      return;
    }

    base::TimeTicks timestamp =
        base::TimeTicks::FromInternalValue(proto.timestamp());
    POWER_LOG(USER) << "Got " << power_manager::kInputEventSignal << " signal:"
                    << " type=" << proto.type()
                    << " timestamp=" << proto.timestamp();
    switch (proto.type()) {
      case power_manager::InputEvent_Type_POWER_BUTTON_DOWN:
      case power_manager::InputEvent_Type_POWER_BUTTON_UP: {
        const bool down =
            (proto.type() == power_manager::InputEvent_Type_POWER_BUTTON_DOWN);
        for (auto& observer : observers_)
          observer.PowerButtonEventReceived(down, timestamp);

        // Tell powerd that Chrome has handled power button presses.
        if (down) {
          dbus::MethodCall method_call(
              power_manager::kPowerManagerInterface,
              power_manager::kHandlePowerButtonAcknowledgmentMethod);
          dbus::MessageWriter writer(&method_call);
          writer.AppendInt64(proto.timestamp());
          power_manager_proxy_->CallMethod(
              &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
              base::DoNothing());
        }
        break;
      }
      case power_manager::InputEvent_Type_LID_OPEN:
        for (auto& observer : observers_)
          observer.LidEventReceived(LidState::OPEN, timestamp);
        break;
      case power_manager::InputEvent_Type_LID_CLOSED:
        for (auto& observer : observers_)
          observer.LidEventReceived(LidState::CLOSED, timestamp);
        break;
      case power_manager::InputEvent_Type_TABLET_MODE_ON:
        for (auto& observer : observers_)
          observer.TabletModeEventReceived(TabletMode::ON, timestamp);
        break;
      case power_manager::InputEvent_Type_TABLET_MODE_OFF:
        for (auto& observer : observers_)
          observer.TabletModeEventReceived(TabletMode::OFF, timestamp);
        break;
      default:
        // TODO(henryhsu): handle the missing cases.
        break;
    }
  }

  void ThermalEventReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    power_manager::ThermalEvent proto;
    if (!reader.PopArrayOfBytesAsProto(&proto)) {
      POWER_LOG(ERROR) << "Unable to decode protocol buffer from "
                       << power_manager::kThermalEventSignal << " signal";
      return;
    }

    POWER_LOG(USER) << "Got " << power_manager::kThermalEventSignal
                    << " signal:"
                    << " thermal_state=" << proto.thermal_state()
                    << " timestamp=" << proto.timestamp();

    base::PowerMonitorDeviceSource::ThermalEventReceived(
        GetThermalStateFromProtoEnum(proto.thermal_state()));
  }

  void RegisterSuspendDelayImpl(
      const std::string& method_name,
      const power_manager::RegisterSuspendDelayRequest& protobuf_request,
      dbus::ObjectProxy::ResponseCallback callback) {
    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 method_name);
    dbus::MessageWriter writer(&method_call);

    if (!writer.AppendProtoAsArrayOfBytes(protobuf_request)) {
      POWER_LOG(ERROR) << "Error constructing message for " << method_name;
      return;
    }

    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     std::move(callback));
  }

  // Registers suspend delays with the power manager.  This is usually only
  // called at startup, but if the power manager restarts, we need to create new
  // delays.
  void RegisterSuspendDelays() {
    // Throw out any old delay that was registered.
    suspend_delay_id_ = -1;
    has_suspend_delay_id_ = false;
    dark_suspend_delay_id_ = -1;
    has_dark_suspend_delay_id_ = false;

    power_manager::RegisterSuspendDelayRequest protobuf_request;
    base::TimeDelta timeout = base::Milliseconds(kSuspendDelayTimeoutMs);
    protobuf_request.set_timeout(timeout.ToInternalValue());
    protobuf_request.set_description(kSuspendDelayDescription);

    RegisterSuspendDelayImpl(
        power_manager::kRegisterSuspendDelayMethod, protobuf_request,
        base::BindOnce(&PowerManagerClientImpl::HandleRegisterSuspendDelayReply,
                       weak_ptr_factory_.GetWeakPtr(), false,
                       power_manager::kRegisterSuspendDelayMethod));

    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
            chromeos::switches::kRegisterMaxDarkSuspendDelay)) {
      // Negative timeout means request maximum delay.
      protobuf_request.set_timeout(-1);
    }
    RegisterSuspendDelayImpl(
        power_manager::kRegisterDarkSuspendDelayMethod, protobuf_request,
        base::BindOnce(&PowerManagerClientImpl::HandleRegisterSuspendDelayReply,
                       weak_ptr_factory_.GetWeakPtr(), true,
                       power_manager::kRegisterDarkSuspendDelayMethod));
  }

  // Reports suspend readiness to powerd if no observers are still holding
  // suspend readiness callbacks.
  void MaybeReportSuspendReadiness() {
    DCHECK(suspend_is_pending_);

    // Avoid reporting suspend readiness if some observers have yet to be
    // notified about the pending attempt.
    if (notifying_observers_about_suspend_imminent_)
      return;

    if (!suspend_readiness_registry_.empty())
      return;

    std::string method_name;
    int32_t delay_id = -1;
    if (suspending_from_dark_resume_) {
      method_name = power_manager::kHandleDarkSuspendReadinessMethod;
      delay_id = dark_suspend_delay_id_;
    } else {
      method_name = power_manager::kHandleSuspendReadinessMethod;
      delay_id = suspend_delay_id_;
    }

    if (render_process_manager_delegate_ && !suspending_from_dark_resume_)
      render_process_manager_delegate_->SuspendImminent();

    dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
                                 method_name);
    dbus::MessageWriter writer(&method_call);

    POWER_LOG(EVENT) << "Announcing readiness of suspend delay " << delay_id
                     << " for suspend attempt " << pending_suspend_id_;
    power_manager::SuspendReadinessInfo protobuf_request;
    protobuf_request.set_delay_id(delay_id);
    protobuf_request.set_suspend_id(pending_suspend_id_);

    pending_suspend_id_ = -1;
    suspend_is_pending_ = false;

    if (!writer.AppendProtoAsArrayOfBytes(protobuf_request)) {
      POWER_LOG(ERROR) << "Error constructing message for " << method_name;
      return;
    }
    power_manager_proxy_->CallMethod(&method_call,
                                     dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                     base::DoNothing());
  }

  // Origin thread (i.e. the UI thread in production).
  base::PlatformThreadId origin_thread_id_;

  raw_ptr<dbus::ObjectProxy, LeakedDanglingUntriaged> power_manager_proxy_ =
      nullptr;
  base::ObserverList<Observer>::Unchecked observers_;

  std::optional<bool> service_available_;

  // The delay ID obtained from the RegisterSuspendDelay request.
  int32_t suspend_delay_id_ = -1;
  bool has_suspend_delay_id_ = false;

  // The delay ID obtained from the RegisterDarkSuspendDelay request.
  int32_t dark_suspend_delay_id_ = -1;
  bool has_dark_suspend_delay_id_ = false;

  // The maximum time power manager will wait before resuspending from a dark
  // resume.
  base::TimeDelta max_dark_suspend_delay_timeout_;

  // powerd-supplied ID corresponding to an imminent (either regular or dark)
  // suspend attempt that is currently being delayed.
  int32_t pending_suspend_id_ = -1;
  bool suspend_is_pending_ = false;

  // Set to true when the suspend currently being delayed was triggered during a
  // dark resume.  Since |pending_suspend_id_| and |suspend_is_pending_| are
  // both shared by normal and dark suspends, |suspending_from_dark_resume_|
  // helps distinguish the context within which these variables are being used.
  bool suspending_from_dark_resume_ = false;

  struct SuspendInfo {
    int32_t suspend_id;
    bool suspending_from_dark_resume;
    std::string debug_info;
  };

  // A map that holds BlockSuspend() registrations. It maps from the token (in
  // string form) to details about the suspend.
  std::unordered_map<std::string, SuspendInfo> suspend_readiness_registry_;

  // Inspected by MaybeReportSuspendReadiness() to avoid prematurely notifying
  // powerd about suspend readiness while |observers_|' SuspendImminent()
  // methods are being called by HandleSuspendImminent().
  bool notifying_observers_about_suspend_imminent_ = false;

  // Last state passed to SetIsProjecting().
  bool last_is_projecting_ = false;

  // The last proto received from D-Bus; initially empty.
  std::optional<power_manager::PowerSupplyProperties> proto_;

  // The delegate used to manage the power consumption of Chrome's renderer
  // processes.
  base::WeakPtr<RenderProcessManagerDelegate> render_process_manager_delegate_;

  // Note: This should remain the last member so it'll be destroyed and
  // invalidate its weak pointers before any other members are destroyed.
  base::WeakPtrFactory<PowerManagerClientImpl> weak_ptr_factory_{this};
};

PowerManagerClient::PowerManagerClient() {
  DCHECK(!g_instance);
  g_instance = this;
}

PowerManagerClient::~PowerManagerClient() {
  DCHECK_EQ(g_instance, this);
  g_instance = nullptr;
}

// static
void PowerManagerClient::Initialize(dbus::Bus* bus) {
  DCHECK(bus);
  DCHECK(!g_instance);
  auto* power_manager_client = new PowerManagerClientImpl();
  power_manager_client->Init(bus);
  g_instance = power_manager_client;
}

// static
void PowerManagerClient::InitializeFake() {
  new FakePowerManagerClient();
}

// static
void PowerManagerClient::Shutdown() {
  delete g_instance;
}

// static
PowerManagerClient* PowerManagerClient::Get() {
  return g_instance;
}

void PowerManagerClient::Observer::SuspendDoneEx(
    const power_manager::SuspendDone& proto) {
  const base::TimeDelta duration =
      base::TimeDelta::FromInternalValue(proto.suspend_duration());
  this->SuspendDone(duration);
}

}  // namespace chromeos