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