OLD | NEW |
| (Empty) |
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 | |
OLD | NEW |