Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(612)

Unified Diff: content/browser/battery_status/battery_status_manager_linux.cc

Issue 436683002: Battery Status API: implementation for Linux. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix BUILD.gn Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..06c60d17c8347df4b12382825ac26a3433e9a174
--- /dev/null
+++ b/content/browser/battery_status/battery_status_manager_linux.cc
@@ -0,0 +1,344 @@
+// 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";
+
+// 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,
+};
+
+// intended for uint32, int64, uint64, double.
hashimoto 2014/08/11 08:19:15 I see no uint64 in this file.
timvolodine 2014/08/11 16:03:00 Acknowledged.
+template<typename T>
+T GetProperty(const base::DictionaryValue& dictionary,
hashimoto 2014/08/11 08:19:15 I don't see any merit of having this template. It
timvolodine 2014/08/11 16:02:59 Done.
+ const std::string& property_name,
+ T default_value) {
+ double value;
hashimoto 2014/08/11 08:19:14 Please don't leave a variable uninitialized.
timvolodine 2014/08/11 16:02:58 Done.
+ return dictionary.GetDouble(property_name, &value) ? static_cast<T>(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(kDBusPropertiesInterface,
+ kDBusPropertiesGetAll);
hashimoto 2014/08/11 08:19:15 Why don't you use dbus/property.h?
timvolodine 2014/08/11 16:02:59 Done.
hashimoto 2014/08/12 10:01:42 Sorry for not being clear, why don't you use dbus:
timvolodine 2014/08/12 19:27:53 I'll look into that, not sure if this will simplif
hashimoto 2014/08/14 04:17:04 ping?
+ 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());
hashimoto 2014/08/11 08:19:14 Why not returning a null scoped_ptr?
timvolodine 2014/08/11 16:02:59 upon failure it returns an empty dictionary which
hashimoto 2014/08/13 11:02:20 Why should we perform totally redandunt allocation
timvolodine 2014/08/13 16:58:08 ok made it to return null. Done. it's actually mor
+}
+
+void GetPowerSourcesPaths(dbus::ObjectProxy* proxy,
+ std::vector<dbus::ObjectPath>& paths) {
hashimoto 2014/08/11 08:19:15 Please don't use non-const references.
timvolodine 2014/08/11 16:02:59 Done.
+ if (!proxy)
+ return;
+
+ dbus::MethodCall method_call(kUPowerServiceName, "EnumerateDevices");
hashimoto 2014/08/11 08:19:15 "EnumerateDevices" should be a constant.
timvolodine 2014/08/11 16:03:00 Done.
+ scoped_ptr<dbus::Response> response(proxy->CallMethodAndBlock(&method_call,
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
hashimoto 2014/08/11 08:19:14 nit: Arguments should be aligned. http://google-st
timvolodine 2014/08/11 16:03:00 Done.
+
+ if (response) {
+ dbus::MessageReader reader(response.get());
+ if (reader.PopArrayOfObjectPaths(&paths))
+ return;
+ }
+ paths.clear();
hashimoto 2014/08/11 08:19:15 This line is not needed.
timvolodine 2014/08/11 16:02:59 Done.
+}
+
hashimoto 2014/08/11 08:19:14 Please add comments for each class which describe
timvolodine 2014/08/11 16:03:00 Done.
+class BatteryStatusNotificationThread : public base::Thread {
hashimoto 2014/08/11 08:19:15 No need to inherit base::Thread.
timvolodine 2014/08/11 16:02:59 ? what do you mean, it's a thread.
hashimoto 2014/08/12 10:01:42 Composition is preferred to inheritance. http://go
timvolodine 2014/08/12 19:27:53 oh I see, in this case I don't think it'll make th
hashimoto 2014/08/13 11:02:20 Please don't violate the coding style and degrade
+ public:
+ BatteryStatusNotificationThread(
+ const BatteryStatusService::BatteryUpdateCallback& callback)
+ : base::Thread(kBatteryWatcherThreadName),
+ callback_(callback),
+ battery_proxy_(0) {}
hashimoto 2014/08/11 08:19:14 Use NULL.
timvolodine 2014/08/11 16:02:59 Done.
+
+ 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);
hashimoto 2014/08/11 08:19:14 ObjectProxy won't be destroyed until the bus gets
timvolodine 2014/08/11 16:02:59 Done.
+ scoped_ptr<base::DictionaryValue> dictionary =
+ GetPropertiesAsDictionary(device_proxy);
+
+ bool is_present = GetProperty<bool>(*dictionary, "IsPresent", false);
+ uint32 type = GetProperty<uint32>(*dictionary, "Type",
+ UPOWER_DEVICE_TYPE_UNKNOWN);
+
+ if (!is_present || type != UPOWER_DEVICE_TYPE_BATTERY)
+ 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).
+ // 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:
+
hashimoto 2014/08/11 08:19:14 nit: No need to have a blank line.
timvolodine 2014/08/11 16:03:00 Done.
+ bool OnWatcherThread() {
+ return std::string(base::PlatformThread::GetName()).compare(
hashimoto 2014/08/11 08:19:15 Use operator==.
timvolodine 2014/08/11 16:02:59 Acknowledged.
+ kBatteryWatcherThreadName) == 0;
hashimoto 2014/08/11 08:19:14 Why don't you directly compare the name of the thr
timvolodine 2014/08/11 16:03:00 PlatformThread::GetName() == kBatteryWatcherThread
hashimoto 2014/08/12 10:01:42 Sorry, not then name, the ID. Can't we do the same
timvolodine 2014/08/12 19:27:53 yes, RunsTasksOnCurrentThread appears to work. Don
+ }
+
+ 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,
hashimoto 2014/08/11 08:19:15 Doesn't this result in a case where BatteryChanged
timvolodine 2014/08/11 16:02:59 I've added a check in BatteryChanged to prevent th
hashimoto 2014/08/12 10:01:42 I'm not sure if that |system_bus_| check can be he
timvolodine 2014/08/12 19:27:53 I am not sure I understand your concern. The Batte
hashimoto 2014/08/13 11:02:20 I was concerned about a case where this object is
timvolodine 2014/08/13 16:58:08 Done.
+ system_bus_));
+ system_bus_ = 0;
hashimoto 2014/08/11 08:19:15 Use NULL for pointers.
timvolodine 2014/08/11 16:03:00 Done.
+ 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) {
hashimoto 2014/08/11 08:19:15 Use operator!=
timvolodine 2014/08/11 16:02:59 Done.
+ return;
+ }
+
+ if (!system_bus_)
+ return;
+
+ if (success) {
+ BatteryChanged(0);
hashimoto 2014/08/11 08:19:15 Use NULL for pointers.
timvolodine 2014/08/11 16:02:59 Done.
+ } else {
+ // Failed to register for "Changed" signal, execute callback with the
+ // default values.
hashimoto 2014/08/11 08:19:15 Why do we need to do this?
timvolodine 2014/08/11 16:03:00 this means we cannot get battery updates, so need
hashimoto 2014/08/12 10:01:42 It seems the Chrome OS implmentation is not doing
timvolodine 2014/08/12 19:27:53 I believe this is needed to cover the following (u
hashimoto 2014/08/13 11:02:20 When something goes wrong with D-Bus, it seems the
timvolodine 2014/08/13 16:58:08 I think ChromeOS actually forces one callback on s
hashimoto 2014/08/14 04:17:04 That RequestStatusUpdate() results in nothing if a
timvolodine 2014/08/14 22:02:45 What I meant is that callback SignalConnected() is
hashimoto 2014/08/15 02:07:00 You shouldn't expect the service to return respons
+ 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);
+ }
+
+ BatteryStatusService::BatteryUpdateCallback callback_;
+ scoped_refptr<dbus::Bus> system_bus_;
+ dbus::ObjectProxy* battery_proxy_; // owned by dbus
hashimoto 2014/08/11 08:19:14 "owned by dbus" is ambiguos. It's owned by the bus
timvolodine 2014/08/11 16:03:00 Done.
+
+ 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 (!NotifierThreadStarted())
+ return false;
+
+ notifier_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&BatteryStatusNotificationThread::StartListening,
+ base::Unretained(notifier_thread_.get())));
hashimoto 2014/08/11 08:19:14 Doesn't this result in a crash if StartListening()
timvolodine 2014/08/11 16:03:00 this cannot happen, currently the thread is destro
hashimoto 2014/08/12 10:01:42 Are you sure that StartListening() and StopListeni
timvolodine 2014/08/12 19:27:53 notifier_thead_ currently get destroyed at shutdow
+ 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())));
+ }
+
+ bool NotifierThreadStarted() {
hashimoto 2014/08/11 08:19:15 "NotifierThreadStarted" sounds like a getter which
timvolodine 2014/08/11 16:03:00 Done.
+ 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 " << kBatteryWatcherThreadName
+ << " thread";
+ return false;
+ }
+ return true;
+ }
+
+ BatteryStatusService::BatteryUpdateCallback callback_;
+ scoped_ptr<BatteryStatusNotificationThread> notifier_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux);
+};
+
+} // namespace
+
+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 &&
+ state != UPOWER_DEVICE_STATE_EMPTY;
+ 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;
hashimoto 2014/08/11 08:19:14 Why should we intentionally degrade precision?
timvolodine 2014/08/11 16:02:59 this is to avoid fingerprinting and not to trigger
hashimoto 2014/08/12 10:01:42 I'm not sure what "fingerprinting" means in this c
timvolodine 2014/08/12 19:27:52 Mac and Android do not need this rounding, don't k
hashimoto 2014/08/13 11:02:20 Chrome OS is much more important than Linux. Pleas
timvolodine 2014/08/13 16:58:08 I'll check with the chromeOS guys on this.
+
+ switch (state) {
+ case UPOWER_DEVICE_STATE_CHARGING : {
+ int64 time_to_full = GetProperty<int64>(dictionary, "TimeToFull", 0);
hashimoto 2014/08/11 08:19:15 Why do we need to convert a double value stored in
timvolodine 2014/08/11 16:02:59 Done.
+ 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

Powered by Google App Engine
This is Rietveld 408576698