Index: device/battery/battery_status_manager_linux.cc |
diff --git a/device/battery/battery_status_manager_linux.cc b/device/battery/battery_status_manager_linux.cc |
index f708b8c49f23227a474567b8ec9ad2d9d8d10581..ec96544d775387d66dc1750c5ccb59bb3187c6b0 100644 |
--- a/device/battery/battery_status_manager_linux.cc |
+++ b/device/battery/battery_status_manager_linux.cc |
@@ -7,96 +7,122 @@ |
#include <stddef.h> |
#include <stdint.h> |
+#include <limits> |
#include <memory> |
+#include <string> |
+#include <utility> |
+#include <vector> |
#include "base/macros.h" |
#include "base/metrics/histogram.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 "device/battery/battery_status_manager.h" |
+#include "device/battery/battery_status_manager_linux-inl.h" |
namespace device { |
- |
namespace { |
- |
-const char kUPowerServiceName[] = "org.freedesktop.UPower"; |
-const char kUPowerDeviceName[] = "org.freedesktop.UPower.Device"; |
-const char kUPowerPath[] = "/org/freedesktop/UPower"; |
-const char kUPowerDeviceSignalChanged[] = "Changed"; |
-const char kUPowerEnumerateDevices[] = "EnumerateDevices"; |
const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier"; |
-// UPowerDeviceType reflects the possible UPower.Device.Type values, |
-// see upower.freedesktop.org/docs/Device.html#Device:Type. |
-enum UPowerDeviceType { |
- UPOWER_DEVICE_TYPE_UNKNOWN = 0, |
- UPOWER_DEVICE_TYPE_LINE_POWER = 1, |
- UPOWER_DEVICE_TYPE_BATTERY = 2, |
- UPOWER_DEVICE_TYPE_UPS = 3, |
- UPOWER_DEVICE_TYPE_MONITOR = 4, |
- UPOWER_DEVICE_TYPE_MOUSE = 5, |
- UPOWER_DEVICE_TYPE_KEYBOARD = 6, |
- UPOWER_DEVICE_TYPE_PDA = 7, |
- UPOWER_DEVICE_TYPE_PHONE = 8, |
-}; |
+class UPowerProperties : public dbus::PropertySet { |
+ public: |
+ UPowerProperties(dbus::ObjectProxy* object_proxy, |
+ const PropertyChangedCallback callback); |
+ ~UPowerProperties() override; |
+ |
+ base::Version daemon_version(); |
+ |
+ private: |
+ dbus::Property<std::string> daemon_version_; |
-typedef std::vector<dbus::ObjectPath> PathsVector; |
+ DISALLOW_COPY_AND_ASSIGN(UPowerProperties); |
+}; |
-double GetPropertyAsDouble(const base::DictionaryValue& dictionary, |
- const std::string& property_name, |
- double default_value) { |
- double value = default_value; |
- return dictionary.GetDouble(property_name, &value) ? value : default_value; |
+UPowerProperties::UPowerProperties(dbus::ObjectProxy* object_proxy, |
+ const PropertyChangedCallback callback) |
+ : dbus::PropertySet(object_proxy, kUPowerInterfaceName, callback) { |
+ RegisterProperty(kUPowerPropertyDaemonVersion, &daemon_version_); |
} |
-bool GetPropertyAsBoolean(const base::DictionaryValue& dictionary, |
- const std::string& property_name, |
- bool default_value) { |
- bool value = default_value; |
- return dictionary.GetBoolean(property_name, &value) ? value : default_value; |
+UPowerProperties::~UPowerProperties() {} |
+ |
+base::Version UPowerProperties::daemon_version() { |
+ return (daemon_version_.is_valid() || daemon_version_.GetAndBlock()) |
+ ? base::Version(daemon_version_.value()) |
+ : base::Version(); |
} |
-std::unique_ptr<base::DictionaryValue> GetPropertiesAsDictionary( |
- dbus::ObjectProxy* proxy) { |
- dbus::MethodCall method_call(dbus::kPropertiesInterface, |
- dbus::kPropertiesGetAll); |
- dbus::MessageWriter builder(&method_call); |
- builder.AppendString(kUPowerDeviceName); |
+class UPowerObject { |
+ public: |
+ typedef dbus::PropertySet::PropertyChangedCallback PropertyChangedCallback; |
+ |
+ UPowerObject(dbus::Bus* dbus, |
+ const PropertyChangedCallback property_changed_callback); |
+ ~UPowerObject(); |
+ |
+ std::vector<dbus::ObjectPath> EnumerateDevices(); |
+ dbus::ObjectPath GetDisplayDevice(); |
- std::unique_ptr<dbus::Response> response(proxy->CallMethodAndBlock( |
+ dbus::ObjectProxy* proxy() { return proxy_; } |
+ UPowerProperties* properties() { return properties_.get(); } |
+ |
+ private: |
+ dbus::Bus* dbus_; // Owned by the BatteryStatusNotificationThread. |
+ dbus::ObjectProxy* proxy_; // Owned by the dbus. |
+ std::unique_ptr<UPowerProperties> properties_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(UPowerObject); |
+}; |
+ |
+UPowerObject::UPowerObject( |
+ dbus::Bus* dbus, |
+ const PropertyChangedCallback property_changed_callback) |
+ : dbus_(dbus), |
+ proxy_(dbus_->GetObjectProxy(kUPowerServiceName, |
+ dbus::ObjectPath(kUPowerPath))), |
+ properties_(new UPowerProperties(proxy_, property_changed_callback)) {} |
+ |
+UPowerObject::~UPowerObject() { |
+ properties_.reset(); // before the proxy is deleted. |
+ dbus_->RemoveObjectProxy(kUPowerServiceName, proxy_->object_path(), |
+ base::Bind(&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()); |
- std::unique_ptr<base::Value> value(dbus::PopDataAsValue(&reader)); |
- base::DictionaryValue* dictionary_value = NULL; |
- if (value && value->GetAsDictionary(&dictionary_value)) { |
- ignore_result(value.release()); |
- return std::unique_ptr<base::DictionaryValue>(dictionary_value); |
- } |
+ reader.PopArrayOfObjectPaths(&paths); |
} |
- return std::unique_ptr<base::DictionaryValue>(); |
+ return paths; |
} |
-std::unique_ptr<PathsVector> GetPowerSourcesPaths(dbus::ObjectProxy* proxy) { |
- std::unique_ptr<PathsVector> paths(new PathsVector()); |
- if (!proxy) |
- return paths; |
+dbus::ObjectPath UPowerObject::GetDisplayDevice() { |
+ dbus::ObjectPath display_device_path; |
+ if (!proxy_) |
+ return display_device_path; |
- dbus::MethodCall method_call(kUPowerServiceName, kUPowerEnumerateDevices); |
- std::unique_ptr<dbus::Response> response(proxy->CallMethodAndBlock( |
+ 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.PopArrayOfObjectPaths(paths.get()); |
+ reader.PopObjectPath(&display_device_path); |
} |
- return paths; |
+ return display_device_path; |
} |
void UpdateNumberBatteriesHistogram(int count) { |
@@ -104,15 +130,187 @@ void UpdateNumberBatteriesHistogram(int count) { |
"BatteryStatus.NumberBatteriesLinux", count, 1, 5, 6); |
} |
+class BatteryProperties : public dbus::PropertySet { |
+ public: |
+ BatteryProperties(dbus::ObjectProxy* object_proxy, |
+ const PropertyChangedCallback callback); |
+ ~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_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(BatteryProperties); |
+}; |
+ |
+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: |
+ typedef dbus::PropertySet::PropertyChangedCallback PropertyChangedCallback; |
+ |
+ BatteryObject(dbus::Bus* dbus, |
+ const dbus::ObjectPath& device_path, |
+ const PropertyChangedCallback& property_changed_callback); |
+ ~BatteryObject(); |
+ |
+ bool IsValid(); |
+ |
+ dbus::ObjectProxy* proxy() { return proxy_; } |
+ BatteryProperties* properties() { return properties_.get(); } |
+ |
+ private: |
+ dbus::Bus* dbus_; // Owned by the BatteryStatusNotificationThread, |
+ dbus::ObjectProxy* proxy_; // Owned by the dbus. |
+ std::unique_ptr<BatteryProperties> properties_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(BatteryObject); |
+}; |
+ |
+BatteryObject::BatteryObject( |
+ dbus::Bus* dbus, |
+ const dbus::ObjectPath& device_path, |
+ const PropertyChangedCallback& property_changed_callback) |
+ : dbus_(dbus), |
+ proxy_(dbus_->GetObjectProxy(kUPowerServiceName, device_path)), |
+ properties_(new BatteryProperties(proxy_, property_changed_callback)) {} |
+ |
+BatteryObject::~BatteryObject() { |
+ properties_.reset(); // before the proxy is deleted. |
+ dbus_->RemoveObjectProxy(kUPowerServiceName, proxy_->object_path(), |
+ base::Bind(&base::DoNothing)); |
+} |
+ |
+bool BatteryObject::IsValid() { |
+ return properties_->is_present() && |
+ properties_->type() == UPOWER_DEVICE_TYPE_BATTERY; |
+} |
+ |
+BatteryStatus ComputeWebBatteryStatus(BatteryProperties* properties) { |
+ BatteryStatus status; |
+ uint32_t state = properties->state(); |
+ status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING && |
+ state != UPOWER_DEVICE_STATE_EMPTY; |
+ // Convert percentage to a value between 0 and 1 with 2 digits of precision. |
+ // This is to bring it in line with other platforms like Mac and Android where |
+ // we report level with 1% granularity. It also serves the purpose of reducing |
+ // the possibility of fingerprinting and triggers less level change events on |
+ // the blink side. |
+ // TODO(timvolodine): consider moving this rounding to the blink side. |
+ 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(); |
+ // Set dischargingTime if it's available. Otherwise leave the default |
+ // value which is +infinity. |
+ 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; |
+} |
+ |
+} // namespace |
+ |
// Class that represents a dedicated thread which communicates with DBus to |
// obtain battery information and receives battery change notifications. |
-class BatteryStatusNotificationThread : public base::Thread { |
+class BatteryStatusManagerLinux::BatteryStatusNotificationThread |
+ : public base::Thread { |
public: |
BatteryStatusNotificationThread( |
const BatteryStatusService::BatteryUpdateCallback& callback) |
- : base::Thread(kBatteryNotifierThreadName), |
- callback_(callback), |
- battery_proxy_(NULL) {} |
+ : base::Thread(kBatteryNotifierThreadName), callback_(callback) {} |
~BatteryStatusNotificationThread() override { |
// Make sure to shutdown the dbus connection if it is still open in the very |
@@ -129,64 +327,28 @@ class BatteryStatusNotificationThread : public base::Thread { |
void StartListening() { |
DCHECK(OnWatcherThread()); |
- if (system_bus_.get()) |
+ if (upower_) |
return; |
- InitDBus(); |
- dbus::ObjectProxy* power_proxy = |
- system_bus_->GetObjectProxy(kUPowerServiceName, |
- dbus::ObjectPath(kUPowerPath)); |
- std::unique_ptr<PathsVector> device_paths = |
- GetPowerSourcesPaths(power_proxy); |
- int num_batteries = 0; |
- |
- for (size_t i = 0; i < device_paths->size(); ++i) { |
- const dbus::ObjectPath& device_path = device_paths->at(i); |
- dbus::ObjectProxy* device_proxy = system_bus_->GetObjectProxy( |
- kUPowerServiceName, device_path); |
- std::unique_ptr<base::DictionaryValue> dictionary = |
- GetPropertiesAsDictionary(device_proxy); |
- |
- if (!dictionary) |
- continue; |
- |
- bool is_present = GetPropertyAsBoolean(*dictionary, "IsPresent", false); |
- uint32_t type = static_cast<uint32_t>( |
- GetPropertyAsDouble(*dictionary, "Type", UPOWER_DEVICE_TYPE_UNKNOWN)); |
- |
- if (!is_present || type != UPOWER_DEVICE_TYPE_BATTERY) { |
- system_bus_->RemoveObjectProxy(kUPowerServiceName, |
- device_path, |
- base::Bind(&base::DoNothing)); |
- continue; |
- } |
- |
- if (battery_proxy_) { |
- // TODO(timvolodine): add support for multiple batteries. Currently we |
- // only collect information from the first battery we encounter |
- // (crbug.com/400780). |
- LOG(WARNING) << "multiple batteries found, " |
- << "using status data of the first battery only."; |
- } else { |
- battery_proxy_ = device_proxy; |
- } |
- num_batteries++; |
- } |
- |
- UpdateNumberBatteriesHistogram(num_batteries); |
- |
- if (!battery_proxy_) { |
- callback_.Run(BatteryStatus()); |
- return; |
- } |
+ if (!system_bus_) |
+ InitDBus(); |
- battery_proxy_->ConnectToSignal( |
- kUPowerDeviceName, |
- kUPowerDeviceSignalChanged, |
- base::Bind(&BatteryStatusNotificationThread::BatteryChanged, |
+ upower_.reset(new UPowerObject(system_bus_.get(), |
+ UPowerObject::PropertyChangedCallback())); |
+ upower_->proxy()->ConnectToSignal( |
+ kUPowerServiceName, kUPowerSignalDeviceAdded, |
+ base::Bind(&BatteryStatusNotificationThread::DeviceAdded, |
base::Unretained(this)), |
base::Bind(&BatteryStatusNotificationThread::OnSignalConnected, |
base::Unretained(this))); |
+ upower_->proxy()->ConnectToSignal( |
+ kUPowerServiceName, kUPowerSignalDeviceRemoved, |
+ base::Bind(&BatteryStatusNotificationThread::DeviceRemoved, |
+ base::Unretained(this)), |
+ base::Bind(&BatteryStatusNotificationThread::OnSignalConnected, |
+ base::Unretained(this))); |
+ |
+ FindBatteryDevice(); |
} |
void StopListening() { |
@@ -194,6 +356,8 @@ class BatteryStatusNotificationThread : public base::Thread { |
ShutdownDBusConnection(); |
} |
+ void SetDBusForTesting(dbus::Bus* bus) { system_bus_ = bus; } |
+ |
private: |
bool OnWatcherThread() { |
return task_runner()->BelongsToCurrentThread(); |
@@ -208,164 +372,249 @@ class BatteryStatusNotificationThread : public base::Thread { |
system_bus_ = new dbus::Bus(options); |
} |
+ bool IsDaemonVersionBelow_0_99() { |
+ base::Version daemon_version = upower_->properties()->daemon_version(); |
+ return daemon_version.IsValid() && |
+ daemon_version.CompareTo(base::Version("0.99")) < 0; |
+ } |
+ |
+ void FindBatteryDevice() { |
+ // Move the currently watched battery_ device to a stack-local variable such |
+ // that we can enumerate all devices (once more): |
+ // first testing the display device, then testing all devices from |
+ // EnumerateDevices. We will monitor the first battery device we find. |
+ // - That may be the same device we did monitor on entering this method; |
+ // then we'll use the same BatteryObject instance, that was moved to |
+ // current - see UseCurrentOrCreateBattery(). |
+ // - Or it may be a new device; then the previously monitored BatteryObject |
+ // instance (if any) is released on leaving this function. |
+ // - Or we may not find a battery device; then on leaving this function |
+ // battery_ will be nullptr and the previously monitored BatteryObject |
+ // instance (if any) is no longer a battery and will be released. |
+ 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); |
+ else |
+ return CreateBattery(device_path); |
+ }; |
+ |
+ dbus::ObjectPath display_device_path = upower_->GetDisplayDevice(); |
+ if (display_device_path.IsValid()) { |
+ std::unique_ptr<BatteryObject> battery = |
+ UseCurrentOrCreateBattery(display_device_path); |
+ if (battery->IsValid()) |
+ battery_ = std::move(battery); |
+ } |
+ |
+ if (!battery_) { |
+ int num_batteries = 0; |
+ for (const auto& device_path : upower_->EnumerateDevices()) { |
+ std::unique_ptr<BatteryObject> battery = |
+ UseCurrentOrCreateBattery(device_path); |
+ |
+ if (!battery->IsValid()) |
+ continue; |
+ |
+ if (battery_) { |
+ // TODO(timvolodine): add support for multiple batteries. Currently we |
+ // only collect information from the first battery we encounter |
+ // (crbug.com/400780). |
+ LOG(WARNING) << "multiple batteries found, " |
+ << "using status data of the first battery only."; |
+ } else { |
+ battery_ = std::move(battery); |
+ } |
+ num_batteries++; |
+ } |
+ |
+ UpdateNumberBatteriesHistogram(num_batteries); |
+ } |
+ |
+ if (battery_) { |
+ battery_->properties()->ConnectSignals(); |
+ NotifyBatteryStatus(); |
+ } else { |
+ callback_.Run(BatteryStatus()); |
+ return; |
+ } |
+ |
+ if (IsDaemonVersionBelow_0_99()) { |
+ // UPower Version 0.99 replaced the Changed signal with the |
+ // PropertyChanged signal. For older versions we need to listen |
+ // to the Changed signal. |
+ battery_->proxy()->ConnectToSignal( |
+ kUPowerDeviceInterfaceName, kUPowerDeviceSignalChanged, |
+ base::Bind(&BatteryStatusNotificationThread::BatteryChanged, |
+ base::Unretained(this)), |
+ base::Bind(&BatteryStatusNotificationThread::OnSignalConnected, |
+ base::Unretained(this))); |
+ } |
+ } |
+ |
void ShutdownDBusConnection() { |
DCHECK(OnWatcherThread()); |
if (!system_bus_.get()) |
return; |
+ battery_.reset(); // before the system_bus_ is shut down. |
+ upower_.reset(); |
+ |
// Shutdown DBus connection later because there may be pending tasks on |
// this thread. |
message_loop()->PostTask(FROM_HERE, |
base::Bind(&dbus::Bus::ShutdownAndBlock, |
system_bus_)); |
system_bus_ = NULL; |
- battery_proxy_ = NULL; |
} |
void OnSignalConnected(const std::string& interface_name, |
const std::string& signal_name, |
- bool success) { |
- DCHECK(OnWatcherThread()); |
+ bool success) {} |
+ |
+ std::unique_ptr<BatteryObject> CreateBattery( |
+ const dbus::ObjectPath& device_path) { |
+ std::unique_ptr<BatteryObject> battery(new BatteryObject( |
+ system_bus_.get(), device_path, |
+ base::Bind(&BatteryStatusNotificationThread::BatteryPropertyChanged, |
+ base::Unretained(this)))); |
+ return battery; |
+ } |
- if (interface_name != kUPowerDeviceName || |
- signal_name != kUPowerDeviceSignalChanged) { |
- return; |
- } |
+ void DeviceAdded(dbus::Signal* signal /* unused */) { |
+ // Re-iterate all devices to see if we need to monitor the added battery |
+ // instead of the currently monitored battery. |
+ FindBatteryDevice(); |
+ } |
- if (!system_bus_.get()) |
+ void DeviceRemoved(dbus::Signal* signal) { |
+ if (!battery_) |
return; |
- if (success) { |
- BatteryChanged(NULL); |
- } else { |
- // Failed to register for "Changed" signal, execute callback with the |
- // default values. |
- callback_.Run(BatteryStatus()); |
+ // UPower specifies that the DeviceRemoved signal has an object-path as |
+ // argument, however IRL that signal was observed with a string argument, |
+ // so cover both cases (argument as string, as object-path and neither of |
+ // these) and call FindBatteryDevice() if either we couldn't get the |
+ // argument or the removed device-path is the battery_. |
+ 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& property_name) { |
+ NotifyBatteryStatus(); |
} |
void BatteryChanged(dbus::Signal* signal /* unsused */) { |
+ DCHECK(battery_); |
+ battery_->properties()->Invalidate(); |
+ NotifyBatteryStatus(); |
+ } |
+ |
+ void NotifyBatteryStatus() { |
DCHECK(OnWatcherThread()); |
- if (!system_bus_.get()) |
+ if (!system_bus_.get() || !battery_ || notifying_battery_status_) |
return; |
- std::unique_ptr<base::DictionaryValue> dictionary = |
- GetPropertiesAsDictionary(battery_proxy_); |
- if (dictionary) |
- callback_.Run(ComputeWebBatteryStatus(*dictionary)); |
- else |
- callback_.Run(BatteryStatus()); |
+ // If the system uses a UPower daemon older than version 0.99 |
+ // (see IsDaemonVersionBelow_0_99), then we are notified about changed |
+ // battery_ properties through the 'Changed' signal of the battery_ |
+ // device (see BatteryChanged()). That is implemented to invalidate all |
+ // battery_ properties (so they are re-fetched from the dbus). Getting |
+ // the new property-value triggers a callback to BatteryPropertyChanged(). |
+ // notifying_battery_status_ is set to avoid recursion and computing the |
+ // status too often. |
+ notifying_battery_status_ = true; |
+ callback_.Run(ComputeWebBatteryStatus(battery_->properties())); |
+ notifying_battery_status_ = false; |
} |
BatteryStatusService::BatteryUpdateCallback callback_; |
scoped_refptr<dbus::Bus> system_bus_; |
- dbus::ObjectProxy* battery_proxy_; // owned by the bus |
+ std::unique_ptr<UPowerObject> upower_; |
+ std::unique_ptr<BatteryObject> battery_; |
+ bool notifying_battery_status_ = false; |
DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread); |
}; |
-// Creates a notification thread and delegates Start/Stop calls to it. |
-class BatteryStatusManagerLinux : public BatteryStatusManager { |
- public: |
- explicit BatteryStatusManagerLinux( |
- const BatteryStatusService::BatteryUpdateCallback& callback) |
- : callback_(callback) {} |
+BatteryStatusManagerLinux::BatteryStatusManagerLinux( |
+ const BatteryStatusService::BatteryUpdateCallback& callback) |
+ : callback_(callback) {} |
- ~BatteryStatusManagerLinux() override {} |
+BatteryStatusManagerLinux::~BatteryStatusManagerLinux() {} |
- private: |
- // BatteryStatusManager: |
- bool StartListeningBatteryChange() override { |
- if (!StartNotifierThreadIfNecessary()) |
- return false; |
+bool BatteryStatusManagerLinux::StartListeningBatteryChange() { |
+ if (!StartNotifierThreadIfNecessary()) |
+ return false; |
- notifier_thread_->message_loop()->PostTask( |
- FROM_HERE, |
- base::Bind(&BatteryStatusNotificationThread::StartListening, |
- base::Unretained(notifier_thread_.get()))); |
- return true; |
- } |
+ notifier_thread_->message_loop()->PostTask( |
+ FROM_HERE, base::Bind(&BatteryStatusNotificationThread::StartListening, |
+ base::Unretained(notifier_thread_.get()))); |
+ return true; |
+} |
- void StopListeningBatteryChange() override { |
- if (!notifier_thread_) |
- return; |
+void BatteryStatusManagerLinux::StopListeningBatteryChange() { |
+ if (!notifier_thread_) |
+ return; |
- notifier_thread_->message_loop()->PostTask( |
- FROM_HERE, |
- base::Bind(&BatteryStatusNotificationThread::StopListening, |
- base::Unretained(notifier_thread_.get()))); |
- } |
+ notifier_thread_->message_loop()->PostTask( |
+ FROM_HERE, base::Bind(&BatteryStatusNotificationThread::StopListening, |
+ base::Unretained(notifier_thread_.get()))); |
+} |
- // Starts the notifier thread if not already started and returns true on |
- // success. |
- bool StartNotifierThreadIfNecessary() { |
- if (notifier_thread_) |
- return true; |
- |
- base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); |
- notifier_thread_.reset(new BatteryStatusNotificationThread(callback_)); |
- if (!notifier_thread_->StartWithOptions(thread_options)) { |
- notifier_thread_.reset(); |
- LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName |
- << " thread"; |
- return false; |
- } |
+bool BatteryStatusManagerLinux::StartNotifierThreadIfNecessary() { |
+ if (notifier_thread_) |
return true; |
- } |
- |
- BatteryStatusService::BatteryUpdateCallback callback_; |
- std::unique_ptr<BatteryStatusNotificationThread> notifier_thread_; |
- |
- DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux); |
-}; |
-} // namespace |
- |
-BatteryStatus ComputeWebBatteryStatus(const base::DictionaryValue& dictionary) { |
- BatteryStatus status; |
- if (!dictionary.HasKey("State")) |
- return status; |
+ base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); |
+ notifier_thread_.reset(new BatteryStatusNotificationThread(callback_)); |
+ if (!notifier_thread_->StartWithOptions(thread_options)) { |
+ notifier_thread_.reset(); |
+ LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName |
+ << " thread"; |
+ return false; |
+ } |
+ return true; |
+} |
- uint32_t state = static_cast<uint32_t>( |
- GetPropertyAsDouble(dictionary, "State", UPOWER_DEVICE_STATE_UNKNOWN)); |
- status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING && |
- state != UPOWER_DEVICE_STATE_EMPTY; |
- double percentage = GetPropertyAsDouble(dictionary, "Percentage", 100); |
- // Convert percentage to a value between 0 and 1 with 2 digits of precision. |
- // This is to bring it in line with other platforms like Mac and Android where |
- // we report level with 1% granularity. It also serves the purpose of reducing |
- // the possibility of fingerprinting and triggers less level change events on |
- // the blink side. |
- // TODO(timvolodine): consider moving this rounding to the blink side. |
- status.level = round(percentage) / 100.f; |
+base::Thread* BatteryStatusManagerLinux::GetNotifierThreadForTesting() { |
+ return notifier_thread_.get(); |
+} |
- switch (state) { |
- case UPOWER_DEVICE_STATE_CHARGING : { |
- double time_to_full = GetPropertyAsDouble(dictionary, "TimeToFull", 0); |
- status.charging_time = |
- (time_to_full > 0) ? time_to_full |
- : std::numeric_limits<double>::infinity(); |
- break; |
- } |
- case UPOWER_DEVICE_STATE_DISCHARGING : { |
- double time_to_empty = GetPropertyAsDouble(dictionary, "TimeToEmpty", 0); |
- // Set dischargingTime if it's available. Otherwise leave the default |
- // value which is +infinity. |
- 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; |
+// static |
+std::unique_ptr<BatteryStatusManagerLinux> |
+BatteryStatusManagerLinux::CreateForTesting( |
+ const BatteryStatusService::BatteryUpdateCallback& callback, |
+ dbus::Bus* bus) { |
+ std::unique_ptr<BatteryStatusManagerLinux> manager( |
+ new BatteryStatusManagerLinux(callback)); |
+ if (manager->StartNotifierThreadIfNecessary()) |
+ manager->notifier_thread_->SetDBusForTesting(bus); |
+ else |
+ manager.reset(); |
+ return manager; |
} |
// static |