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"
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"
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
,
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
));
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());
88 dbus::MethodCall
method_call(kUPowerServiceName
, kUPowerEnumerateDevices
);
89 scoped_ptr
<dbus::Response
> response(
90 proxy
->CallMethodAndBlock(&method_call
,
91 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
94 dbus::MessageReader
reader(response
.get());
95 reader
.PopArrayOfObjectPaths(paths
.get());
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
{
109 BatteryStatusNotificationThread(
110 const BatteryStatusService::BatteryUpdateCallback
& callback
)
111 : base::Thread(kBatteryNotifierThreadName
),
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(
122 base::Bind(&BatteryStatusNotificationThread::ShutdownDBusConnection
,
123 base::Unretained(this)));
125 // Drain the message queue of the BatteryStatusNotificationThread and stop.
129 void StartListening() {
130 DCHECK(OnWatcherThread());
132 if (system_bus_
.get())
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
);
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
,
159 base::Bind(&base::DoNothing
));
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.";
170 battery_proxy_
= device_proxy
;
175 UpdateNumberBatteriesHistogram(num_batteries
);
177 if (!battery_proxy_
) {
178 callback_
.Run(blink::WebBatteryStatus());
182 battery_proxy_
->ConnectToSignal(
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();
197 bool OnWatcherThread() {
198 return task_runner()->BelongsToCurrentThread();
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())
216 // Shutdown DBus connection later because there may be pending tasks on
218 message_loop()->PostTask(FROM_HERE
,
219 base::Bind(&dbus::Bus::ShutdownAndBlock
,
222 battery_proxy_
= NULL
;
225 void OnSignalConnected(const std::string
& interface_name
,
226 const std::string
& signal_name
,
228 DCHECK(OnWatcherThread());
230 if (interface_name
!= kUPowerDeviceName
||
231 signal_name
!= kUPowerDeviceSignalChanged
) {
235 if (!system_bus_
.get())
239 BatteryChanged(NULL
);
241 // Failed to register for "Changed" signal, execute callback with the
243 callback_
.Run(blink::WebBatteryStatus());
247 void BatteryChanged(dbus::Signal
* signal
/* unsused */) {
248 DCHECK(OnWatcherThread());
250 if (!system_bus_
.get())
253 scoped_ptr
<base::DictionaryValue
> dictionary
=
254 GetPropertiesAsDictionary(battery_proxy_
);
256 callback_
.Run(ComputeWebBatteryStatus(*dictionary
));
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
270 class BatteryStatusManagerLinux
: public BatteryStatusManager
{
272 explicit BatteryStatusManagerLinux(
273 const BatteryStatusService::BatteryUpdateCallback
& callback
)
274 : callback_(callback
) {}
276 virtual ~BatteryStatusManagerLinux() {}
279 // BatteryStatusManager:
280 virtual bool StartListeningBatteryChange() OVERRIDE
{
281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
283 if (!StartNotifierThreadIfNecessary())
286 notifier_thread_
->message_loop()->PostTask(
288 base::Bind(&BatteryStatusNotificationThread::StartListening
,
289 base::Unretained(notifier_thread_
.get())));
293 virtual void StopListeningBatteryChange() OVERRIDE
{
294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
296 if (!notifier_thread_
)
299 notifier_thread_
->message_loop()->PostTask(
301 base::Bind(&BatteryStatusNotificationThread::StopListening
,
302 base::Unretained(notifier_thread_
.get())));
305 // Starts the notifier thread if not already started and returns true on
307 bool StartNotifierThreadIfNecessary() {
308 if (notifier_thread_
)
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
322 BatteryStatusService::BatteryUpdateCallback callback_
;
323 scoped_ptr
<BatteryStatusNotificationThread
> notifier_thread_
;
325 DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux
);
330 blink::WebBatteryStatus
ComputeWebBatteryStatus(
331 const base::DictionaryValue
& dictionary
) {
332 blink::WebBatteryStatus status
;
333 if (!dictionary
.HasKey("State"))
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
346 // TODO(timvolodine): consider moving this rounding to the blink side.
347 status
.level
= round(percentage
) / 100.f
;
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();
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();
366 case UPOWER_DEVICE_STATE_FULL
: {
370 status
.chargingTime
= std::numeric_limits
<double>::infinity();
377 scoped_ptr
<BatteryStatusManager
> BatteryStatusManager::Create(
378 const BatteryStatusService::BatteryUpdateCallback
& callback
) {
379 return scoped_ptr
<BatteryStatusManager
>(
380 new BatteryStatusManagerLinux(callback
));
383 } // namespace content