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.
4
5#include "content/browser/battery_status/battery_status_manager_linux.h"
6
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"
19
20namespace content {
21
22namespace {
23
24const char kUPowerServiceName[] = "org.freedesktop.UPower";
25const char kUPowerDeviceName[] = "org.freedesktop.UPower.Device";
26const char kUPowerPath[] = "/org/freedesktop/UPower";
27const char kUPowerDeviceSignalChanged[] = "Changed";
28const char kUPowerEnumerateDevices[] = "EnumerateDevices";
29const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier";
30
31// UPowerDeviceType reflects the possible UPower.Device.Type values,
32// see upower.freedesktop.org/docs/Device.html#Device:Type.
33enum 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,
43};
44
45typedef std::vector<dbus::ObjectPath> PathsVector;
46
47double 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;
52}
53
54bool 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;
59}
60
61scoped_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);
67
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);
78    }
79  }
80  return scoped_ptr<base::DictionaryValue>();
81}
82
83scoped_ptr<PathsVector> GetPowerSourcesPaths(dbus::ObjectProxy* proxy) {
84  scoped_ptr<PathsVector> paths(new PathsVector());
85  if (!proxy)
86    return paths.Pass();
87
88  dbus::MethodCall method_call(kUPowerServiceName, kUPowerEnumerateDevices);
89  scoped_ptr<dbus::Response> response(
90      proxy->CallMethodAndBlock(&method_call,
91                                dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
92
93  if (response) {
94    dbus::MessageReader reader(response.get());
95    reader.PopArrayOfObjectPaths(paths.get());
96  }
97  return paths.Pass();;
98}
99
100void UpdateNumberBatteriesHistogram(int count) {
101  UMA_HISTOGRAM_CUSTOM_COUNTS(
102      "BatteryStatus.NumberBatteriesLinux", count, 1, 5, 6);
103}
104
105// Class that represents a dedicated thread which communicates with DBus to
106// obtain battery information and receives battery change notifications.
107class BatteryStatusNotificationThread : public base::Thread {
108 public:
109  BatteryStatusNotificationThread(
110      const BatteryStatusService::BatteryUpdateCallback& callback)
111      : base::Thread(kBatteryNotifierThreadName),
112        callback_(callback),
113        battery_proxy_(NULL) {}
114
115  virtual ~BatteryStatusNotificationThread() {
116    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
117
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)));
124
125    // Drain the message queue of the BatteryStatusNotificationThread and stop.
126    Stop();
127  }
128
129  void StartListening() {
130    DCHECK(OnWatcherThread());
131
132    if (system_bus_.get())
133      return;
134
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;
141
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);
148
149      if (!dictionary)
150        continue;
151
152      bool is_present = GetPropertyAsBoolean(*dictionary, "IsPresent", false);
153      uint32 type = static_cast<uint32>(
154          GetPropertyAsDouble(*dictionary, "Type", UPOWER_DEVICE_TYPE_UNKNOWN));
155
156      if (!is_present || type != UPOWER_DEVICE_TYPE_BATTERY) {
157        system_bus_->RemoveObjectProxy(kUPowerServiceName,
158                                       device_path,
159                                       base::Bind(&base::DoNothing));
160        continue;
161      }
162
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;
171      }
172      num_batteries++;
173    }
174
175    UpdateNumberBatteriesHistogram(num_batteries);
176
177    if (!battery_proxy_) {
178      callback_.Run(blink::WebBatteryStatus());
179      return;
180    }
181
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)));
189  }
190
191  void StopListening() {
192    DCHECK(OnWatcherThread());
193    ShutdownDBusConnection();
194  }
195
196 private:
197  bool OnWatcherThread() {
198    return task_runner()->BelongsToCurrentThread();
199  }
200
201  void InitDBus() {
202    DCHECK(OnWatcherThread());
203
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);
208  }
209
210  void ShutdownDBusConnection() {
211    DCHECK(OnWatcherThread());
212
213    if (!system_bus_.get())
214      return;
215
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;
223  }
224
225  void OnSignalConnected(const std::string& interface_name,
226                         const std::string& signal_name,
227                         bool success) {
228    DCHECK(OnWatcherThread());
229
230    if (interface_name != kUPowerDeviceName ||
231        signal_name != kUPowerDeviceSignalChanged) {
232      return;
233    }
234
235    if (!system_bus_.get())
236      return;
237
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());
244    }
245  }
246
247  void BatteryChanged(dbus::Signal* signal /* unsused */) {
248    DCHECK(OnWatcherThread());
249
250    if (!system_bus_.get())
251      return;
252
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());
259  }
260
261  BatteryStatusService::BatteryUpdateCallback callback_;
262  scoped_refptr<dbus::Bus> system_bus_;
263  dbus::ObjectProxy* battery_proxy_;  // owned by the bus
264
265  DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread);
266};
267
268// Runs on IO thread and creates a notification thread and delegates Start/Stop
269// calls to it.
270class BatteryStatusManagerLinux : public BatteryStatusManager {
271 public:
272  explicit BatteryStatusManagerLinux(
273      const BatteryStatusService::BatteryUpdateCallback& callback)
274      : callback_(callback) {}
275
276  virtual ~BatteryStatusManagerLinux() {}
277
278 private:
279  // BatteryStatusManager:
280  virtual bool StartListeningBatteryChange() OVERRIDE {
281    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
282
283    if (!StartNotifierThreadIfNecessary())
284      return false;
285
286    notifier_thread_->message_loop()->PostTask(
287        FROM_HERE,
288        base::Bind(&BatteryStatusNotificationThread::StartListening,
289                   base::Unretained(notifier_thread_.get())));
290    return true;
291  }
292
293  virtual void StopListeningBatteryChange() OVERRIDE {
294    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
295
296    if (!notifier_thread_)
297      return;
298
299    notifier_thread_->message_loop()->PostTask(
300        FROM_HERE,
301        base::Bind(&BatteryStatusNotificationThread::StopListening,
302                   base::Unretained(notifier_thread_.get())));
303  }
304
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;
310
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;
318    }
319    return true;
320  }
321
322  BatteryStatusService::BatteryUpdateCallback callback_;
323  scoped_ptr<BatteryStatusNotificationThread> notifier_thread_;
324
325  DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux);
326};
327
328}  // namespace
329
330blink::WebBatteryStatus ComputeWebBatteryStatus(
331    const base::DictionaryValue& dictionary) {
332  blink::WebBatteryStatus status;
333  if (!dictionary.HasKey("State"))
334    return status;
335
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;
348
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;
356    }
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;
365    }
366    case UPOWER_DEVICE_STATE_FULL : {
367      break;
368    }
369    default: {
370      status.chargingTime = std::numeric_limits<double>::infinity();
371    }
372  }
373  return status;
374}
375
376// static
377scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
378    const BatteryStatusService::BatteryUpdateCallback& callback) {
379  return scoped_ptr<BatteryStatusManager>(
380      new BatteryStatusManagerLinux(callback));
381}
382
383}  // namespace content
384