#include "services/device/battery/battery_status_manager_linux.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/message_loop/message_pump_type.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "base/version.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "dbus/property.h"
#include "dbus/values_util.h"
#include "services/device/battery/battery_status_manager_linux-inl.h"
namespace device {
namespace {
const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier";
class UPowerProperties : public dbus::PropertySet {
public:
UPowerProperties(dbus::ObjectProxy* object_proxy,
const PropertyChangedCallback callback);
UPowerProperties(const UPowerProperties&) = delete;
UPowerProperties& operator=(const UPowerProperties&) = delete;
~UPowerProperties() override;
base::Version daemon_version();
private:
dbus::Property<std::string> daemon_version_;
};
UPowerProperties::UPowerProperties(dbus::ObjectProxy* object_proxy,
const PropertyChangedCallback callback)
: dbus::PropertySet(object_proxy, kUPowerInterfaceName, callback) {
RegisterProperty(kUPowerPropertyDaemonVersion, &daemon_version_);
}
UPowerProperties::~UPowerProperties() {}
base::Version UPowerProperties::daemon_version() {
return (daemon_version_.is_valid() || daemon_version_.GetAndBlock())
? base::Version(daemon_version_.value())
: base::Version();
}
class UPowerObject {
public:
using PropertyChangedCallback = dbus::PropertySet::PropertyChangedCallback;
UPowerObject(dbus::Bus* dbus,
const PropertyChangedCallback property_changed_callback);
UPowerObject(const UPowerObject&) = delete;
UPowerObject& operator=(const UPowerObject&) = delete;
~UPowerObject();
std::vector<dbus::ObjectPath> EnumerateDevices();
dbus::ObjectPath GetDisplayDevice();
dbus::ObjectProxy* proxy() { return proxy_; }
UPowerProperties* properties() { return properties_.get(); }
private:
raw_ptr<dbus::Bus> dbus_;
raw_ptr<dbus::ObjectProxy> proxy_;
std::unique_ptr<UPowerProperties> properties_;
};
UPowerObject::UPowerObject(
dbus::Bus* dbus,
const PropertyChangedCallback property_changed_callback)
: dbus_(dbus),
proxy_(dbus_->GetObjectProxy(kUPowerServiceName,
dbus::ObjectPath(kUPowerPath))),
properties_(
std::make_unique<UPowerProperties>(proxy_,
property_changed_callback)) {}
UPowerObject::~UPowerObject() {
properties_.reset();
dbus_->RemoveObjectProxy(kUPowerServiceName, proxy_->object_path(),
base::DoNothing());
}
std::vector<dbus::ObjectPath> UPowerObject::EnumerateDevices() {
std::vector<dbus::ObjectPath> paths;
dbus::MethodCall method_call(kUPowerServiceName,
kUPowerMethodEnumerateDevices);
std::unique_ptr<dbus::Response> response(proxy_->CallMethodAndBlock(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
if (response) {
dbus::MessageReader reader(response.get());
reader.PopArrayOfObjectPaths(&paths);
}
return paths;
}
dbus::ObjectPath UPowerObject::GetDisplayDevice() {
dbus::ObjectPath display_device_path;
if (!proxy_)
return display_device_path;
dbus::MethodCall method_call(kUPowerServiceName,
kUPowerMethodGetDisplayDevice);
std::unique_ptr<dbus::Response> response(proxy_->CallMethodAndBlock(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
if (response) {
dbus::MessageReader reader(response.get());
reader.PopObjectPath(&display_device_path);
}
return display_device_path;
}
class BatteryProperties : public dbus::PropertySet {
public:
BatteryProperties(dbus::ObjectProxy* object_proxy,
const PropertyChangedCallback callback);
BatteryProperties(const BatteryProperties&) = delete;
BatteryProperties& operator=(const BatteryProperties&) = delete;
~BatteryProperties() override;
void ConnectSignals() override;
void Invalidate();
bool is_present(bool default_value = false);
double percentage(double default_value = 100);
uint32_t state(uint32_t default_value = UPOWER_DEVICE_STATE_UNKNOWN);
int64_t time_to_empty(int64_t default_value = 0);
int64_t time_to_full(int64_t default_value = 0);
uint32_t type(uint32_t default_value = UPOWER_DEVICE_TYPE_UNKNOWN);
private:
bool connected_ = false;
dbus::Property<bool> is_present_;
dbus::Property<double> percentage_;
dbus::Property<uint32_t> state_;
dbus::Property<int64_t> time_to_empty_;
dbus::Property<int64_t> time_to_full_;
dbus::Property<uint32_t> type_;
};
BatteryProperties::BatteryProperties(dbus::ObjectProxy* object_proxy,
const PropertyChangedCallback callback)
: dbus::PropertySet(object_proxy, kUPowerDeviceInterfaceName, callback) {
RegisterProperty(kUPowerDevicePropertyIsPresent, &is_present_);
RegisterProperty(kUPowerDevicePropertyPercentage, &percentage_);
RegisterProperty(kUPowerDevicePropertyState, &state_);
RegisterProperty(kUPowerDevicePropertyTimeToEmpty, &time_to_empty_);
RegisterProperty(kUPowerDevicePropertyTimeToFull, &time_to_full_);
RegisterProperty(kUPowerDevicePropertyType, &type_);
}
BatteryProperties::~BatteryProperties() {}
void BatteryProperties::ConnectSignals() {
if (!connected_) {
connected_ = true;
dbus::PropertySet::ConnectSignals();
}
}
void BatteryProperties::Invalidate() {
is_present_.set_valid(false);
percentage_.set_valid(false);
state_.set_valid(false);
time_to_empty_.set_valid(false);
time_to_full_.set_valid(false);
type_.set_valid(false);
}
bool BatteryProperties::is_present(bool default_value) {
return (is_present_.is_valid() || is_present_.GetAndBlock())
? is_present_.value()
: default_value;
}
double BatteryProperties::percentage(double default_value) {
return (percentage_.is_valid() || percentage_.GetAndBlock())
? percentage_.value()
: default_value;
}
uint32_t BatteryProperties::state(uint32_t default_value) {
return (state_.is_valid() || state_.GetAndBlock()) ? state_.value()
: default_value;
}
int64_t BatteryProperties::time_to_empty(int64_t default_value) {
return (time_to_empty_.is_valid() || time_to_empty_.GetAndBlock())
? time_to_empty_.value()
: default_value;
}
int64_t BatteryProperties::time_to_full(int64_t default_value) {
return (time_to_full_.is_valid() || time_to_full_.GetAndBlock())
? time_to_full_.value()
: default_value;
}
uint32_t BatteryProperties::type(uint32_t default_value) {
return (type_.is_valid() || type_.GetAndBlock()) ? type_.value()
: default_value;
}
class BatteryObject {
public:
using PropertyChangedCallback = dbus::PropertySet::PropertyChangedCallback;
BatteryObject(dbus::Bus* dbus,
const dbus::ObjectPath& device_path,
const PropertyChangedCallback& property_changed_callback);
BatteryObject(const BatteryObject&) = delete;
BatteryObject& operator=(const BatteryObject&) = delete;
~BatteryObject();
bool IsValid() const;
dbus::ObjectProxy* proxy() { return proxy_; }
BatteryProperties* properties() { return properties_.get(); }
private:
raw_ptr<dbus::Bus> dbus_;
raw_ptr<dbus::ObjectProxy> proxy_;
std::unique_ptr<BatteryProperties> properties_;
};
BatteryObject::BatteryObject(
dbus::Bus* dbus,
const dbus::ObjectPath& device_path,
const PropertyChangedCallback& property_changed_callback)
: dbus_(dbus),
proxy_(dbus_->GetObjectProxy(kUPowerServiceName, device_path)),
properties_(
std::make_unique<BatteryProperties>(proxy_,
property_changed_callback)) {}
BatteryObject::~BatteryObject() {
properties_.reset();
dbus_->RemoveObjectProxy(kUPowerServiceName, proxy_->object_path(),
base::DoNothing());
}
bool BatteryObject::IsValid() const {
return properties_->is_present() &&
properties_->type() == UPOWER_DEVICE_TYPE_BATTERY;
}
mojom::BatteryStatus ComputeWebBatteryStatus(BatteryProperties* properties) {
mojom::BatteryStatus status;
uint32_t state = properties->state();
status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING &&
state != UPOWER_DEVICE_STATE_EMPTY;
status.level = round(properties->percentage()) / 100.f;
switch (state) {
case UPOWER_DEVICE_STATE_CHARGING: {
int64_t time_to_full = properties->time_to_full();
status.charging_time = (time_to_full > 0)
? time_to_full
: std::numeric_limits<double>::infinity();
break;
}
case UPOWER_DEVICE_STATE_DISCHARGING: {
int64_t time_to_empty = properties->time_to_empty();
if (time_to_empty > 0)
status.discharging_time = time_to_empty;
status.charging_time = std::numeric_limits<double>::infinity();
break;
}
case UPOWER_DEVICE_STATE_FULL: {
break;
}
default: {
status.charging_time = std::numeric_limits<double>::infinity();
}
}
return status;
}
}
class BatteryStatusManagerLinux::BatteryStatusNotificationThread
: public base::Thread {
public:
explicit BatteryStatusNotificationThread(
const BatteryStatusService::BatteryUpdateCallback& callback)
: base::Thread(kBatteryNotifierThreadName), callback_(callback) {}
BatteryStatusNotificationThread(const BatteryStatusNotificationThread&) =
delete;
BatteryStatusNotificationThread& operator=(
const BatteryStatusNotificationThread&) = delete;
~BatteryStatusNotificationThread() override {
task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&BatteryStatusNotificationThread::ShutdownDBusConnection,
base::Unretained(this)));
Stop();
}
void StartListening() {
DCHECK(OnWatcherThread());
if (upower_)
return;
if (!system_bus_)
InitDBus();
upower_ = std::make_unique<UPowerObject>(
system_bus_.get(), UPowerObject::PropertyChangedCallback());
upower_->proxy()->ConnectToSignal(
kUPowerServiceName, kUPowerSignalDeviceAdded,
base::BindRepeating(&BatteryStatusNotificationThread::DeviceAdded,
base::Unretained(this)),
base::DoNothing());
upower_->proxy()->ConnectToSignal(
kUPowerServiceName, kUPowerSignalDeviceRemoved,
base::BindRepeating(&BatteryStatusNotificationThread::DeviceRemoved,
base::Unretained(this)),
base::DoNothing());
FindBatteryDevice();
}
void StopListening() {
DCHECK(OnWatcherThread());
ShutdownDBusConnection();
}
void SetDBusForTesting(dbus::Bus* bus) { system_bus_ = bus; }
private:
bool OnWatcherThread() const {
return task_runner()->BelongsToCurrentThread();
}
void InitDBus() {
DCHECK(OnWatcherThread());
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
options.connection_type = dbus::Bus::PRIVATE;
system_bus_ = base::MakeRefCounted<dbus::Bus>(options);
}
bool IsDaemonVersionBelow_0_99() {
DCHECK(OnWatcherThread());
base::Version daemon_version = upower_->properties()->daemon_version();
return daemon_version.IsValid() &&
daemon_version.CompareTo(base::Version("0.99")) < 0;
}
void FindBatteryDevice() {
DCHECK(OnWatcherThread());
if (!system_bus_)
return;
std::unique_ptr<BatteryObject> current = std::move(battery_);
auto UseCurrentOrCreateBattery =
[¤t, this](const dbus::ObjectPath& device_path) {
if (current && current->proxy()->object_path() == device_path)
return std::move(current);
return CreateBattery(device_path);
};
dbus::ObjectPath display_device_path;
if (!IsDaemonVersionBelow_0_99())
display_device_path = upower_->GetDisplayDevice();
if (display_device_path.IsValid()) {
auto battery = UseCurrentOrCreateBattery(display_device_path);
if (battery->IsValid())
battery_ = std::move(battery);
}
if (!battery_) {
for (const auto& device_path : upower_->EnumerateDevices()) {
auto battery = UseCurrentOrCreateBattery(device_path);
if (!battery->IsValid())
continue;
if (battery_) {
LOG(WARNING) << "multiple batteries found, "
<< "using status data of the first battery only.";
} else {
battery_ = std::move(battery);
}
}
}
if (!battery_) {
callback_.Run(mojom::BatteryStatus());
return;
}
battery_->properties()->ConnectSignals();
NotifyBatteryStatus();
if (IsDaemonVersionBelow_0_99()) {
battery_->proxy()->ConnectToSignal(
kUPowerDeviceInterfaceName, kUPowerDeviceSignalChanged,
base::BindRepeating(&BatteryStatusNotificationThread::BatteryChanged,
base::Unretained(this)),
base::DoNothing());
}
}
void ShutdownDBusConnection() {
DCHECK(OnWatcherThread());
if (!system_bus_)
return;
battery_.reset();
upower_.reset();
task_runner()->PostTask(
FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, system_bus_));
system_bus_ = nullptr;
}
std::unique_ptr<BatteryObject> CreateBattery(
const dbus::ObjectPath& device_path) {
return std::make_unique<BatteryObject>(
system_bus_.get(), device_path,
base::BindRepeating(
&BatteryStatusNotificationThread::BatteryPropertyChanged,
base::Unretained(this)));
}
void DeviceAdded(dbus::Signal* ) {
DCHECK(OnWatcherThread());
FindBatteryDevice();
}
void DeviceRemoved(dbus::Signal* signal) {
DCHECK(OnWatcherThread());
if (!battery_)
return;
dbus::MessageReader reader(signal);
dbus::ObjectPath removed_device_path;
switch (reader.GetDataType()) {
case dbus::Message::DataType::STRING: {
std::string removed_device_path_string;
if (reader.PopString(&removed_device_path_string))
removed_device_path = dbus::ObjectPath(removed_device_path_string);
break;
}
case dbus::Message::DataType::OBJECT_PATH:
reader.PopObjectPath(&removed_device_path);
break;
default:
break;
}
if (!removed_device_path.IsValid() ||
battery_->proxy()->object_path() == removed_device_path) {
FindBatteryDevice();
}
}
void BatteryPropertyChanged(const std::string& ) {
DCHECK(OnWatcherThread());
NotifyBatteryStatus();
}
void BatteryChanged(dbus::Signal* ) {
DCHECK(OnWatcherThread());
DCHECK(battery_);
battery_->properties()->Invalidate();
NotifyBatteryStatus();
}
void NotifyBatteryStatus() {
DCHECK(OnWatcherThread());
if (!system_bus_ || !battery_ || notifying_battery_status_)
return;
notifying_battery_status_ = true;
callback_.Run(ComputeWebBatteryStatus(battery_->properties()));
notifying_battery_status_ = false;
}
const BatteryStatusService::BatteryUpdateCallback callback_;
scoped_refptr<dbus::Bus> system_bus_;
std::unique_ptr<UPowerObject> upower_;
std::unique_ptr<BatteryObject> battery_;
bool notifying_battery_status_ = false;
};
BatteryStatusManagerLinux::BatteryStatusManagerLinux(
const BatteryStatusService::BatteryUpdateCallback& callback)
: callback_(callback) {}
BatteryStatusManagerLinux::~BatteryStatusManagerLinux() {}
bool BatteryStatusManagerLinux::StartListeningBatteryChange() {
if (!StartNotifierThreadIfNecessary())
return false;
notifier_thread_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&BatteryStatusNotificationThread::StartListening,
base::Unretained(notifier_thread_.get())));
return true;
}
void BatteryStatusManagerLinux::StopListeningBatteryChange() {
if (!notifier_thread_)
return;
notifier_thread_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&BatteryStatusNotificationThread::StopListening,
base::Unretained(notifier_thread_.get())));
}
bool BatteryStatusManagerLinux::StartNotifierThreadIfNecessary() {
if (notifier_thread_)
return true;
auto notifier_thread =
std::make_unique<BatteryStatusNotificationThread>(callback_);
if (!notifier_thread->StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0))) {
LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName
<< " thread";
return false;
}
notifier_thread_ = std::move(notifier_thread);
return true;
}
base::Thread* BatteryStatusManagerLinux::GetNotifierThreadForTesting() {
return notifier_thread_.get();
}
std::unique_ptr<BatteryStatusManagerLinux>
BatteryStatusManagerLinux::CreateForTesting(
const BatteryStatusService::BatteryUpdateCallback& callback,
dbus::Bus* bus) {
auto manager = std::make_unique<BatteryStatusManagerLinux>(callback);
if (!manager->StartNotifierThreadIfNecessary())
return nullptr;
manager->notifier_thread_->SetDBusForTesting(bus);
return manager;
}
std::unique_ptr<BatteryStatusManager> BatteryStatusManager::Create(
const BatteryStatusService::BatteryUpdateCallback& callback) {
return std::make_unique<BatteryStatusManagerLinux>(callback);
}
}