Chromium Code Reviews| Index: content/browser/battery_status/battery_status_manager_linux.cc |
| diff --git a/content/browser/battery_status/battery_status_manager_linux.cc b/content/browser/battery_status/battery_status_manager_linux.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..067c4355e8fb168783ea6d20fe8be344b0d3a21d |
| --- /dev/null |
| +++ b/content/browser/battery_status/battery_status_manager_linux.cc |
| @@ -0,0 +1,318 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/battery_status/battery_status_manager_linux.h" |
| + |
| +#include "base/macros.h" |
| +#include "base/threading/thread.h" |
| +#include "base/values.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "dbus/bus.h" |
| +#include "dbus/message.h" |
| +#include "dbus/object_path.h" |
| +#include "dbus/object_proxy.h" |
| +#include "dbus/values_util.h" |
| +#include "third_party/WebKit/public/platform/WebBatteryStatus.h" |
| + |
| +namespace content { |
| + |
| +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 kBatteryWatcherThreadName[] = "BatteryStatusWatcher"; |
| +const char kDBusPropertiesInterface[] = "org.freedesktop.DBus.Properties"; |
| +const char kDBusPropertiesGetAll[] = "GetAll"; |
| + |
| +// intended for unit32, int64, uint64, double. |
|
Michael van Ouwerkerk
2014/08/08 14:54:59
s/unit32/uint32/
timvolodine
2014/08/08 18:46:45
Done.
|
| +template<typename T> |
| +T GetProperty(const base::DictionaryValue& dictionary, |
| + const std::string& property_name, |
| + T default_value) { |
| + double value; |
| + return dictionary.GetDouble(property_name, &value) ? value : default_value; |
|
stevenjb
2014/08/08 16:18:16
static_cast<T>(value)
timvolodine
2014/08/08 18:46:45
Done.
|
| +} |
| + |
| +// bool |
| +template<> |
| +bool GetProperty(const base::DictionaryValue& dictionary, |
| + const std::string& property_name, |
| + bool default_value) { |
| + bool value; |
| + return dictionary.GetBoolean(property_name, &value) ? value : default_value; |
| +} |
| + |
| +scoped_ptr<base::DictionaryValue> GetPropertiesAsDictionary( |
| + dbus::ObjectProxy* proxy) { |
| + dbus::MethodCall method_call(kDBusPropertiesInterface, |
| + kDBusPropertiesGetAll); |
| + dbus::MessageWriter builder(&method_call); |
| + builder.AppendString(kUPowerDeviceName); |
| + |
| + scoped_ptr<dbus::Response> response( |
| + proxy->CallMethodAndBlock(&method_call, |
| + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); |
| + if (response) { |
|
stevenjb
2014/08/08 16:18:16
We should probably return NULL on failure to read
timvolodine
2014/08/08 18:46:45
the idea here is to return an empty dictionary, wh
|
| + dbus::MessageReader reader(response.get()); |
| + scoped_ptr<base::Value> value(dbus::PopDataAsValue(&reader)); |
| + base::DictionaryValue* dictionary_value = NULL; |
| + if (value && value->GetAsDictionary(&dictionary_value)) { |
| + ignore_result(value.release()); |
| + return scoped_ptr<base::DictionaryValue>(dictionary_value); |
| + } |
| + } |
| + return scoped_ptr<base::DictionaryValue>(new base::DictionaryValue()); |
| +} |
| + |
| +void GetPowerSourcesPaths(dbus::ObjectProxy* proxy, |
| + std::vector<dbus::ObjectPath>& paths) { |
| + if (!proxy) |
| + return; |
| + |
| + dbus::MethodCall method_call(kUPowerServiceName, "EnumerateDevices"); |
| + scoped_ptr<dbus::Response> response(proxy->CallMethodAndBlock(&method_call, |
| + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); |
| + |
| + if (response) { |
| + dbus::MessageReader reader(response.get()); |
| + if (reader.PopArrayOfObjectPaths(&paths)) |
| + return; |
| + } |
| + paths.clear(); |
| +} |
| + |
| +class BatteryStatusNotificationThread : public base::Thread { |
| + public: |
| + BatteryStatusNotificationThread( |
| + const BatteryStatusService::BatteryUpdateCallback& callback) |
| + : base::Thread(kBatteryWatcherThreadName), |
| + callback_(callback), |
| + battery_proxy_(0) {} |
| + |
| + virtual ~BatteryStatusNotificationThread() { |
| + if (system_bus_) |
| + ShutdownDBusConnection(); |
| + Stop(); |
| + } |
| + |
| + void StartListening() { |
| + DCHECK(OnWatcherThread()); |
| + |
| + if (system_bus_) |
| + return; |
| + |
| + InitDBus(); |
| + dbus::ObjectProxy* power_proxy = |
| + system_bus_->GetObjectProxy(kUPowerServiceName, |
| + dbus::ObjectPath(kUPowerPath)); |
| + std::vector<dbus::ObjectPath> device_paths; |
| + GetPowerSourcesPaths(power_proxy, device_paths); |
| + |
| + for (size_t i = 0; i < device_paths.size(); ++i) { |
| + const dbus::ObjectPath& device_path = device_paths[i]; |
| + dbus::ObjectProxy* device_proxy = system_bus_->GetObjectProxy( |
| + kUPowerServiceName, device_path); |
| + scoped_ptr<base::DictionaryValue> dictionary = |
| + GetPropertiesAsDictionary(device_proxy); |
| + |
| + bool is_present = GetProperty<bool>(*dictionary, "IsPresent", false); |
| + uint32 type = GetProperty<uint32>(*dictionary, "Type", 0); |
| + |
| + if (is_present && type == 2 /* battery */) { |
|
mlamouri (slow - plz ping)
2014/08/07 16:17:04
Could you add an enum for that too? Sorry for not
stevenjb
2014/08/08 16:18:16
nit: if (!is_present || type != TYPE_BATTERY) cont
timvolodine
2014/08/08 18:46:45
Done.
timvolodine
2014/08/08 18:46:45
Done.
|
| + if (battery_proxy_) { |
| + // TODO(timvolodine): add support for multiple batteries. Currently we |
| + // only collect information from the first battery we encounter |
| + // (crbug.com/400780). |
| + // TODO(timvolodine): add UMA logging for this case. |
| + LOG(WARNING) << "multiple batteries found, " |
| + << "using status data of the first battery only."; |
| + } else { |
| + battery_proxy_ = device_proxy; |
| + } |
| + } |
| + } |
| + |
| + if (!battery_proxy_) { |
| + callback_.Run(blink::WebBatteryStatus()); |
| + return; |
| + } |
| + |
| + battery_proxy_->ConnectToSignal( |
| + kUPowerDeviceName, |
| + kUPowerDeviceSignalChanged, |
| + base::Bind(&BatteryStatusNotificationThread::BatteryChanged, |
| + base::Unretained(this)), |
| + base::Bind(&BatteryStatusNotificationThread::OnSignalConnected, |
| + base::Unretained(this))); |
| + } |
| + |
| + void StopListening() { |
| + DCHECK(OnWatcherThread()); |
| + |
| + if (!system_bus_) |
| + return; |
| + |
| + ShutdownDBusConnection(); |
| + } |
| + |
| + private: |
| + |
| + bool OnWatcherThread() { |
| + return std::string(base::PlatformThread::GetName()).compare( |
| + kBatteryWatcherThreadName) == 0; |
| + } |
| + |
| + void InitDBus() { |
| + DCHECK(OnWatcherThread()); |
| + |
| + dbus::Bus::Options options; |
| + options.bus_type = dbus::Bus::SYSTEM; |
| + options.connection_type = dbus::Bus::PRIVATE; |
| + system_bus_ = new dbus::Bus(options); |
| + } |
| + |
| + void ShutdownDBusConnection() { |
| + // 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_ = 0; |
| + battery_proxy_ = 0; |
| + } |
| + |
| + void OnSignalConnected(const std::string& interface_name, |
| + const std::string& signal_name, |
| + bool success) { |
| + DCHECK(OnWatcherThread()); |
| + |
| + if (interface_name.compare(kUPowerDeviceName) != 0 || |
| + signal_name.compare(kUPowerDeviceSignalChanged) != 0) { |
| + return; |
| + } |
| + |
| + if (!system_bus_) |
| + return; |
| + |
| + if (success) { |
| + BatteryChanged(0); |
| + } else { |
| + // Failed to register for "Changed" signal, execute callback with the |
| + // default values. |
| + callback_.Run(blink::WebBatteryStatus()); |
| + } |
| + } |
| + |
| + void BatteryChanged(dbus::Signal* signal /* unsused */) { |
| + DCHECK(OnWatcherThread()); |
| + blink::WebBatteryStatus status; |
| + scoped_ptr<base::DictionaryValue> dictionary = |
| + GetPropertiesAsDictionary(battery_proxy_); |
| + ComputeWebBatteryStatus(*dictionary, status); |
| + callback_.Run(status); |
|
mlamouri (slow - plz ping)
2014/08/07 16:17:04
This block needs some empty lines. It's quite dens
timvolodine
2014/08/08 18:46:45
Done.
|
| + } |
| + |
| + BatteryStatusService::BatteryUpdateCallback callback_; |
| + scoped_refptr<dbus::Bus> system_bus_; |
| + dbus::ObjectProxy* battery_proxy_; // owned by dbus |
| + |
| + DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread); |
| +}; |
| + |
| +class BatteryStatusManagerLinux : public BatteryStatusManager { |
| + public: |
| + explicit BatteryStatusManagerLinux( |
| + const BatteryStatusService::BatteryUpdateCallback& callback) |
| + : callback_(callback) {} |
| + |
| + virtual ~BatteryStatusManagerLinux() {} |
| + |
| + private: |
| + // BatteryStatusManager: |
| + virtual bool StartListeningBatteryChange() OVERRIDE { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + if (!notifier_thread_) { |
| + 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 " << kBatteryWatcherThreadName |
| + << "thread"; |
| + return false; |
| + } |
| + } |
|
stevenjb
2014/08/08 16:18:16
This bit should probably be moved to a helper meth
timvolodine
2014/08/08 18:46:45
Done.
|
| + |
| + notifier_thread_->message_loop()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&BatteryStatusNotificationThread::StartListening, |
| + base::Unretained(notifier_thread_.get()))); |
| + return true; |
| + } |
| + |
| + virtual void StopListeningBatteryChange() OVERRIDE { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + |
| + notifier_thread_->message_loop()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&BatteryStatusNotificationThread::StopListening, |
| + base::Unretained(notifier_thread_.get()))); |
| + } |
| + |
| + BatteryStatusService::BatteryUpdateCallback callback_; |
| + scoped_ptr<BatteryStatusNotificationThread> notifier_thread_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux); |
| +}; |
| + |
| +} // namespace |
| + |
| +// Computes and sets the WebBatteryStatus values where possible based on the |
| +// passed dictionary. |
| +void ComputeWebBatteryStatus(const base::DictionaryValue& dictionary, |
| + blink::WebBatteryStatus& status) { |
| + if (!dictionary.HasKey("State")) |
| + return; |
| + uint32 state = GetProperty<uint32>(dictionary, "State", |
| + UPOWER_DEVICE_STATE_UNKNOWN); |
| + status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING; |
| + double percentage = GetProperty<double>(dictionary, "Percentage", 100); |
| + // Convert percentage to a value between 0 and 1 with 3 digits of precision. |
| + status.level = round(percentage * 10) / 1000.f; |
|
mlamouri (slow - plz ping)
2014/08/07 16:17:04
Could you add a test with "Percentage" = 13.37 and
timvolodine
2014/08/08 18:46:45
Done.
|
| + |
| + switch (state) { |
| + case UPOWER_DEVICE_STATE_CHARGING : { |
| + int64 time_to_full = GetProperty<int64>(dictionary, "TimeToFull", 0); |
| + status.chargingTime = |
| + (time_to_full > 0) ? time_to_full |
| + : std::numeric_limits<double>::infinity(); |
| + break; |
| + } |
| + case UPOWER_DEVICE_STATE_DISCHARGING : { |
| + int64 time_to_empty = GetProperty<int64>(dictionary, "TimeToEmpty", 0); |
| + // Set dischargingTime if it's available. Otherwise leave the default |
| + // value which is +infinity. |
| + if (time_to_empty > 0) |
| + status.dischargingTime = time_to_empty; |
| + status.chargingTime = std::numeric_limits<double>::infinity(); |
| + break; |
| + } |
| + case UPOWER_DEVICE_STATE_FULL : { |
| + break; |
| + } |
| + default: { |
| + status.chargingTime = std::numeric_limits<double>::infinity(); |
| + } |
| + } |
| +} |
| + |
| +// static |
| +scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create( |
| + const BatteryStatusService::BatteryUpdateCallback& callback) { |
| + return scoped_ptr<BatteryStatusManager>( |
| + new BatteryStatusManagerLinux(callback)); |
| +} |
| + |
| +} // namespace content |