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 |