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..e6abf88ed4eb11b4f53ee7b1e9d771e87caf98d5 |
--- /dev/null |
+++ b/content/browser/battery_status/battery_status_manager_linux.cc |
@@ -0,0 +1,374 @@ |
+// 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/browser/battery_status/battery_status_manager.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/property.h" |
+#include "dbus/values_util.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 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, |
+}; |
+ |
+typedef std::vector<dbus::ObjectPath> PathsVector; |
+ |
+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; |
+} |
+ |
+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; |
+} |
+ |
+scoped_ptr<base::DictionaryValue> GetPropertiesAsDictionary( |
+ dbus::ObjectProxy* proxy) { |
+ dbus::MethodCall method_call(dbus::kPropertiesInterface, |
+ dbus::kPropertiesGetAll); |
+ 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>(); |
+} |
+ |
+scoped_ptr<PathsVector> GetPowerSourcesPaths(dbus::ObjectProxy* proxy) { |
+ scoped_ptr<PathsVector> paths(new PathsVector()); |
+ if (!proxy) |
+ return paths.Pass(); |
+ |
+ dbus::MethodCall method_call(kUPowerServiceName, kUPowerEnumerateDevices); |
+ scoped_ptr<dbus::Response> response( |
+ proxy->CallMethodAndBlock(&method_call, |
+ dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); |
+ |
+ if (response) { |
+ dbus::MessageReader reader(response.get()); |
+ reader.PopArrayOfObjectPaths(paths.get()); |
+ } |
+ return paths.Pass();; |
+} |
+ |
+// Class that represents a dedicated thread which communicates with DBus to |
+// obtain battery information and receives battery change notifications. |
+class BatteryStatusNotificationThread : public base::Thread { |
+ public: |
+ BatteryStatusNotificationThread( |
+ const BatteryStatusService::BatteryUpdateCallback& callback) |
+ : base::Thread(kBatteryNotifierThreadName), |
+ callback_(callback), |
+ battery_proxy_(NULL) {} |
+ |
+ virtual ~BatteryStatusNotificationThread() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ // Make sure to shutdown the dbus connection if it is still open in the very |
+ // end. It needs to happen on the BatteryStatusNotificationThread. |
+ message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&BatteryStatusNotificationThread::ShutdownDBusConnection, |
+ base::Unretained(this))); |
+ |
+ // Drain the message queue of the BatteryStatusNotificationThread and stop. |
+ Stop(); |
+ } |
+ |
+ void StartListening() { |
+ DCHECK(OnWatcherThread()); |
+ |
+ if (system_bus_) |
+ return; |
+ |
+ InitDBus(); |
+ dbus::ObjectProxy* power_proxy = |
+ system_bus_->GetObjectProxy(kUPowerServiceName, |
+ dbus::ObjectPath(kUPowerPath)); |
+ scoped_ptr<PathsVector> device_paths = GetPowerSourcesPaths(power_proxy); |
+ |
+ 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); |
+ scoped_ptr<base::DictionaryValue> dictionary = |
+ GetPropertiesAsDictionary(device_proxy); |
+ |
+ if (!dictionary) |
+ continue; |
+ |
+ bool is_present = GetPropertyAsBoolean(*dictionary, "IsPresent", false); |
+ uint32 type = static_cast<uint32>( |
+ 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). |
+ // 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()); |
+ ShutdownDBusConnection(); |
+ } |
+ |
+ private: |
+ bool OnWatcherThread() { |
+ return task_runner()->BelongsToCurrentThread(); |
+ } |
+ |
+ 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() { |
+ DCHECK(OnWatcherThread()); |
+ |
+ if (!system_bus_) |
+ return; |
+ |
+ // 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()); |
+ |
+ if (interface_name != kUPowerDeviceName || |
+ signal_name != kUPowerDeviceSignalChanged) { |
+ return; |
+ } |
+ |
+ if (!system_bus_) |
+ return; |
+ |
+ if (success) { |
+ BatteryChanged(NULL); |
+ } 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()); |
+ |
+ if (!system_bus_) |
+ return; |
+ |
+ scoped_ptr<base::DictionaryValue> dictionary = |
+ GetPropertiesAsDictionary(battery_proxy_); |
+ if (dictionary) |
+ callback_.Run(ComputeWebBatteryStatus(*dictionary)); |
+ else |
+ callback_.Run(blink::WebBatteryStatus()); |
+ } |
+ |
+ BatteryStatusService::BatteryUpdateCallback callback_; |
+ scoped_refptr<dbus::Bus> system_bus_; |
+ dbus::ObjectProxy* battery_proxy_; // owned by the bus |
+ |
+ DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread); |
+}; |
+ |
+// Runs on IO thread and creates a notification thread and delegates Start/Stop |
+// calls to it. |
+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 (!StartNotifierThreadIfNecessary()) |
+ return false; |
+ |
+ 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)); |
+ |
+ if (!notifier_thread_) |
+ return; |
+ |
+ 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; |
+ } |
+ return true; |
+ } |
+ |
+ BatteryStatusService::BatteryUpdateCallback callback_; |
+ scoped_ptr<BatteryStatusNotificationThread> notifier_thread_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux); |
+}; |
+ |
+} // namespace |
+ |
+blink::WebBatteryStatus ComputeWebBatteryStatus( |
+ const base::DictionaryValue& dictionary) { |
+ blink::WebBatteryStatus status; |
+ if (!dictionary.HasKey("State")) |
+ return status; |
+ |
+ uint32 state = static_cast<uint32>( |
+ 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; |
+ |
+ switch (state) { |
+ case UPOWER_DEVICE_STATE_CHARGING : { |
+ double time_to_full = GetPropertyAsDouble(dictionary, "TimeToFull", 0); |
+ status.chargingTime = |
+ (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.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(); |
+ } |
+ } |
+ return status; |
+} |
+ |
+// static |
+scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create( |
+ const BatteryStatusService::BatteryUpdateCallback& callback) { |
+ return scoped_ptr<BatteryStatusManager>( |
+ new BatteryStatusManagerLinux(callback)); |
+} |
+ |
+} // namespace content |