Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / content / browser / battery_status / battery_status_manager_linux.cc
blobd09a3dfe220d4d3fe8e5651665e9209534361c68
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/battery_status/battery_status_manager_linux.h"
7 #include "base/macros.h"
8 #include "base/metrics/histogram.h"
9 #include "base/threading/thread.h"
10 #include "base/values.h"
11 #include "content/browser/battery_status/battery_status_manager.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "dbus/bus.h"
14 #include "dbus/message.h"
15 #include "dbus/object_path.h"
16 #include "dbus/object_proxy.h"
17 #include "dbus/property.h"
18 #include "dbus/values_util.h"
20 namespace content {
22 namespace {
24 const char kUPowerServiceName[] = "org.freedesktop.UPower";
25 const char kUPowerDeviceName[] = "org.freedesktop.UPower.Device";
26 const char kUPowerPath[] = "/org/freedesktop/UPower";
27 const char kUPowerDeviceSignalChanged[] = "Changed";
28 const char kUPowerEnumerateDevices[] = "EnumerateDevices";
29 const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier";
31 // UPowerDeviceType reflects the possible UPower.Device.Type values,
32 // see upower.freedesktop.org/docs/Device.html#Device:Type.
33 enum UPowerDeviceType {
34 UPOWER_DEVICE_TYPE_UNKNOWN = 0,
35 UPOWER_DEVICE_TYPE_LINE_POWER = 1,
36 UPOWER_DEVICE_TYPE_BATTERY = 2,
37 UPOWER_DEVICE_TYPE_UPS = 3,
38 UPOWER_DEVICE_TYPE_MONITOR = 4,
39 UPOWER_DEVICE_TYPE_MOUSE = 5,
40 UPOWER_DEVICE_TYPE_KEYBOARD = 6,
41 UPOWER_DEVICE_TYPE_PDA = 7,
42 UPOWER_DEVICE_TYPE_PHONE = 8,
45 typedef std::vector<dbus::ObjectPath> PathsVector;
47 double GetPropertyAsDouble(const base::DictionaryValue& dictionary,
48 const std::string& property_name,
49 double default_value) {
50 double value = default_value;
51 return dictionary.GetDouble(property_name, &value) ? value : default_value;
54 bool GetPropertyAsBoolean(const base::DictionaryValue& dictionary,
55 const std::string& property_name,
56 bool default_value) {
57 bool value = default_value;
58 return dictionary.GetBoolean(property_name, &value) ? value : default_value;
61 scoped_ptr<base::DictionaryValue> GetPropertiesAsDictionary(
62 dbus::ObjectProxy* proxy) {
63 dbus::MethodCall method_call(dbus::kPropertiesInterface,
64 dbus::kPropertiesGetAll);
65 dbus::MessageWriter builder(&method_call);
66 builder.AppendString(kUPowerDeviceName);
68 scoped_ptr<dbus::Response> response(
69 proxy->CallMethodAndBlock(&method_call,
70 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
71 if (response) {
72 dbus::MessageReader reader(response.get());
73 scoped_ptr<base::Value> value(dbus::PopDataAsValue(&reader));
74 base::DictionaryValue* dictionary_value = NULL;
75 if (value && value->GetAsDictionary(&dictionary_value)) {
76 ignore_result(value.release());
77 return scoped_ptr<base::DictionaryValue>(dictionary_value);
80 return scoped_ptr<base::DictionaryValue>();
83 scoped_ptr<PathsVector> GetPowerSourcesPaths(dbus::ObjectProxy* proxy) {
84 scoped_ptr<PathsVector> paths(new PathsVector());
85 if (!proxy)
86 return paths.Pass();
88 dbus::MethodCall method_call(kUPowerServiceName, kUPowerEnumerateDevices);
89 scoped_ptr<dbus::Response> response(
90 proxy->CallMethodAndBlock(&method_call,
91 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
93 if (response) {
94 dbus::MessageReader reader(response.get());
95 reader.PopArrayOfObjectPaths(paths.get());
97 return paths.Pass();;
100 void UpdateNumberBatteriesHistogram(int count) {
101 UMA_HISTOGRAM_CUSTOM_COUNTS(
102 "BatteryStatus.NumberBatteriesLinux", count, 1, 5, 6);
105 // Class that represents a dedicated thread which communicates with DBus to
106 // obtain battery information and receives battery change notifications.
107 class BatteryStatusNotificationThread : public base::Thread {
108 public:
109 BatteryStatusNotificationThread(
110 const BatteryStatusService::BatteryUpdateCallback& callback)
111 : base::Thread(kBatteryNotifierThreadName),
112 callback_(callback),
113 battery_proxy_(NULL) {}
115 virtual ~BatteryStatusNotificationThread() {
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
118 // Make sure to shutdown the dbus connection if it is still open in the very
119 // end. It needs to happen on the BatteryStatusNotificationThread.
120 message_loop()->PostTask(
121 FROM_HERE,
122 base::Bind(&BatteryStatusNotificationThread::ShutdownDBusConnection,
123 base::Unretained(this)));
125 // Drain the message queue of the BatteryStatusNotificationThread and stop.
126 Stop();
129 void StartListening() {
130 DCHECK(OnWatcherThread());
132 if (system_bus_.get())
133 return;
135 InitDBus();
136 dbus::ObjectProxy* power_proxy =
137 system_bus_->GetObjectProxy(kUPowerServiceName,
138 dbus::ObjectPath(kUPowerPath));
139 scoped_ptr<PathsVector> device_paths = GetPowerSourcesPaths(power_proxy);
140 int num_batteries = 0;
142 for (size_t i = 0; i < device_paths->size(); ++i) {
143 const dbus::ObjectPath& device_path = device_paths->at(i);
144 dbus::ObjectProxy* device_proxy = system_bus_->GetObjectProxy(
145 kUPowerServiceName, device_path);
146 scoped_ptr<base::DictionaryValue> dictionary =
147 GetPropertiesAsDictionary(device_proxy);
149 if (!dictionary)
150 continue;
152 bool is_present = GetPropertyAsBoolean(*dictionary, "IsPresent", false);
153 uint32 type = static_cast<uint32>(
154 GetPropertyAsDouble(*dictionary, "Type", UPOWER_DEVICE_TYPE_UNKNOWN));
156 if (!is_present || type != UPOWER_DEVICE_TYPE_BATTERY) {
157 system_bus_->RemoveObjectProxy(kUPowerServiceName,
158 device_path,
159 base::Bind(&base::DoNothing));
160 continue;
163 if (battery_proxy_) {
164 // TODO(timvolodine): add support for multiple batteries. Currently we
165 // only collect information from the first battery we encounter
166 // (crbug.com/400780).
167 LOG(WARNING) << "multiple batteries found, "
168 << "using status data of the first battery only.";
169 } else {
170 battery_proxy_ = device_proxy;
172 num_batteries++;
175 UpdateNumberBatteriesHistogram(num_batteries);
177 if (!battery_proxy_) {
178 callback_.Run(blink::WebBatteryStatus());
179 return;
182 battery_proxy_->ConnectToSignal(
183 kUPowerDeviceName,
184 kUPowerDeviceSignalChanged,
185 base::Bind(&BatteryStatusNotificationThread::BatteryChanged,
186 base::Unretained(this)),
187 base::Bind(&BatteryStatusNotificationThread::OnSignalConnected,
188 base::Unretained(this)));
191 void StopListening() {
192 DCHECK(OnWatcherThread());
193 ShutdownDBusConnection();
196 private:
197 bool OnWatcherThread() {
198 return task_runner()->BelongsToCurrentThread();
201 void InitDBus() {
202 DCHECK(OnWatcherThread());
204 dbus::Bus::Options options;
205 options.bus_type = dbus::Bus::SYSTEM;
206 options.connection_type = dbus::Bus::PRIVATE;
207 system_bus_ = new dbus::Bus(options);
210 void ShutdownDBusConnection() {
211 DCHECK(OnWatcherThread());
213 if (!system_bus_.get())
214 return;
216 // Shutdown DBus connection later because there may be pending tasks on
217 // this thread.
218 message_loop()->PostTask(FROM_HERE,
219 base::Bind(&dbus::Bus::ShutdownAndBlock,
220 system_bus_));
221 system_bus_ = NULL;
222 battery_proxy_ = NULL;
225 void OnSignalConnected(const std::string& interface_name,
226 const std::string& signal_name,
227 bool success) {
228 DCHECK(OnWatcherThread());
230 if (interface_name != kUPowerDeviceName ||
231 signal_name != kUPowerDeviceSignalChanged) {
232 return;
235 if (!system_bus_.get())
236 return;
238 if (success) {
239 BatteryChanged(NULL);
240 } else {
241 // Failed to register for "Changed" signal, execute callback with the
242 // default values.
243 callback_.Run(blink::WebBatteryStatus());
247 void BatteryChanged(dbus::Signal* signal /* unsused */) {
248 DCHECK(OnWatcherThread());
250 if (!system_bus_.get())
251 return;
253 scoped_ptr<base::DictionaryValue> dictionary =
254 GetPropertiesAsDictionary(battery_proxy_);
255 if (dictionary)
256 callback_.Run(ComputeWebBatteryStatus(*dictionary));
257 else
258 callback_.Run(blink::WebBatteryStatus());
261 BatteryStatusService::BatteryUpdateCallback callback_;
262 scoped_refptr<dbus::Bus> system_bus_;
263 dbus::ObjectProxy* battery_proxy_; // owned by the bus
265 DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread);
268 // Runs on IO thread and creates a notification thread and delegates Start/Stop
269 // calls to it.
270 class BatteryStatusManagerLinux : public BatteryStatusManager {
271 public:
272 explicit BatteryStatusManagerLinux(
273 const BatteryStatusService::BatteryUpdateCallback& callback)
274 : callback_(callback) {}
276 virtual ~BatteryStatusManagerLinux() {}
278 private:
279 // BatteryStatusManager:
280 virtual bool StartListeningBatteryChange() OVERRIDE {
281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
283 if (!StartNotifierThreadIfNecessary())
284 return false;
286 notifier_thread_->message_loop()->PostTask(
287 FROM_HERE,
288 base::Bind(&BatteryStatusNotificationThread::StartListening,
289 base::Unretained(notifier_thread_.get())));
290 return true;
293 virtual void StopListeningBatteryChange() OVERRIDE {
294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
296 if (!notifier_thread_)
297 return;
299 notifier_thread_->message_loop()->PostTask(
300 FROM_HERE,
301 base::Bind(&BatteryStatusNotificationThread::StopListening,
302 base::Unretained(notifier_thread_.get())));
305 // Starts the notifier thread if not already started and returns true on
306 // success.
307 bool StartNotifierThreadIfNecessary() {
308 if (notifier_thread_)
309 return true;
311 base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
312 notifier_thread_.reset(new BatteryStatusNotificationThread(callback_));
313 if (!notifier_thread_->StartWithOptions(thread_options)) {
314 notifier_thread_.reset();
315 LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName
316 << " thread";
317 return false;
319 return true;
322 BatteryStatusService::BatteryUpdateCallback callback_;
323 scoped_ptr<BatteryStatusNotificationThread> notifier_thread_;
325 DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux);
328 } // namespace
330 blink::WebBatteryStatus ComputeWebBatteryStatus(
331 const base::DictionaryValue& dictionary) {
332 blink::WebBatteryStatus status;
333 if (!dictionary.HasKey("State"))
334 return status;
336 uint32 state = static_cast<uint32>(
337 GetPropertyAsDouble(dictionary, "State", UPOWER_DEVICE_STATE_UNKNOWN));
338 status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING &&
339 state != UPOWER_DEVICE_STATE_EMPTY;
340 double percentage = GetPropertyAsDouble(dictionary, "Percentage", 100);
341 // Convert percentage to a value between 0 and 1 with 2 digits of precision.
342 // This is to bring it in line with other platforms like Mac and Android where
343 // we report level with 1% granularity. It also serves the purpose of reducing
344 // the possibility of fingerprinting and triggers less level change events on
345 // the blink side.
346 // TODO(timvolodine): consider moving this rounding to the blink side.
347 status.level = round(percentage) / 100.f;
349 switch (state) {
350 case UPOWER_DEVICE_STATE_CHARGING : {
351 double time_to_full = GetPropertyAsDouble(dictionary, "TimeToFull", 0);
352 status.chargingTime =
353 (time_to_full > 0) ? time_to_full
354 : std::numeric_limits<double>::infinity();
355 break;
357 case UPOWER_DEVICE_STATE_DISCHARGING : {
358 double time_to_empty = GetPropertyAsDouble(dictionary, "TimeToEmpty", 0);
359 // Set dischargingTime if it's available. Otherwise leave the default
360 // value which is +infinity.
361 if (time_to_empty > 0)
362 status.dischargingTime = time_to_empty;
363 status.chargingTime = std::numeric_limits<double>::infinity();
364 break;
366 case UPOWER_DEVICE_STATE_FULL : {
367 break;
369 default: {
370 status.chargingTime = std::numeric_limits<double>::infinity();
373 return status;
376 // static
377 scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
378 const BatteryStatusService::BatteryUpdateCallback& callback) {
379 return scoped_ptr<BatteryStatusManager>(
380 new BatteryStatusManagerLinux(callback));
383 } // namespace content