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..23756133383182987024c2ff954dbac78b4b42d9 |
| --- /dev/null |
| +++ b/content/browser/battery_status/battery_status_manager_linux.cc |
| @@ -0,0 +1,306 @@ |
| +// 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.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" |
| +#include "third_party/cros_system_api/dbus/service_constants.h" |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:14
I think you should remove that #include. Isn't it
timvolodine
2014/08/06 16:37:42
Done.
|
| + |
| +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"; |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:13
I think you can use "DeviceChanged" instead of "Ch
timvolodine
2014/08/06 16:37:42
UPower.Device only has the Changed() signal, in li
mlamouri (slow - plz ping)
2014/08/07 16:17:04
My bad, I misread. I didn't see that you were regi
|
| +const char kBatteryWatcherThreadName[] = "BatteryStatusWatcher"; |
| + |
| +// intended for unit32, int64, uint64, double. |
| +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; |
| +} |
| + |
| +// 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(dbus::kDBusPropertiesInterface, |
| + dbus::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) { |
| + 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_) |
| + ShutdownDBus(); |
| + 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 */) { |
| + if (battery_proxy_) { |
| + // TODO(timvolodine): add support for multiple batteries. Currently we |
|
Michael van Ouwerkerk
2014/08/01 15:24:39
Please file a bug for this. It would be nice to ha
timvolodine
2014/08/06 16:37:42
Done.
|
| + // only collect information from the first battery we encounter. |
| + 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; |
| + |
| + ShutdownDBus(); |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:13
nit: could you rename this ShutdownDBusConnection(
timvolodine
2014/08/06 16:37:42
Done.
|
| + } |
| + |
| + 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 ShutdownDBus() { |
| + // 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) { |
|
Michael van Ouwerkerk
2014/08/01 15:24:39
Maybe a unit test would be appropriate here. I thi
Michael van Ouwerkerk
2014/08/01 15:24:39
The signal param initially confused me. How about
timvolodine
2014/08/06 16:37:42
Done.
timvolodine
2014/08/06 16:37:42
I've extracted a method for testing (ComputeWebBat
|
| + DCHECK(OnWatcherThread()); |
| + |
| + blink::WebBatteryStatus status; |
| + scoped_ptr<base::DictionaryValue> dictionary = |
| + GetPropertiesAsDictionary(battery_proxy_); |
| + |
| + uint32 state = GetProperty<uint32>(*dictionary, "State", -1); |
| + status.charging = state != 2; // true if not discharging |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:14
Could you have an enum that mirror the possible va
timvolodine
2014/08/06 16:37:42
Done.
|
| + double percentage = GetProperty<double>(*dictionary, "Percentage", 1); |
| + // 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/01 17:14:13
Why not *0.01.f or /10.f?
timvolodine
2014/08/06 16:37:42
dbus reports the value with lost of fractional dig
|
| + |
| + switch (state) { |
| + case 1 : { // charging |
| + int64 time_to_full = GetProperty<int64>(*dictionary, "TimeToFull", 0); |
| + status.chargingTime = |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:13
Are we using the right default value in Blink if t
timvolodine
2014/08/06 16:37:42
here we explicitly set it to either the time or +i
|
| + (time_to_full > 0) ? time_to_full |
| + : std::numeric_limits<double>::infinity(); |
| + break; |
| + } |
| + case 2 : { // 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; |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:13
ditto
timvolodine
2014/08/06 16:37:43
dischargingTime is +inf by default so we are fine
|
| + status.chargingTime = std::numeric_limits<double>::infinity(); |
| + break; |
| + } |
| + case 4 : { // fully charged |
| + break; |
| + } |
| + default: { |
| + status.chargingTime = std::numeric_limits<double>::infinity(); |
| + } |
| + } |
| + |
| + callback_.Run(status); |
| + } |
| + |
| + 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(); |
| + return false; |
|
mlamouri (slow - plz ping)
2014/08/01 17:14:14
That's not really something we expect to happen, r
timvolodine
2014/08/06 16:37:42
right, added LOG(ERROR).
|
| + } |
| + } |
| + |
| + 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 |
| + |
| +// static |
| +scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create( |
| + const BatteryStatusService::BatteryUpdateCallback& callback) { |
| + return scoped_ptr<BatteryStatusManager>( |
| + new BatteryStatusManagerLinux(callback)); |
| +} |
| + |
| +} // namespace content |