Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(332)

Side by Side Diff: device/battery/battery_status_manager_linux.cc

Issue 2818673003: [DeviceService] Expose battery monitoring solely via the Device Service (Closed)
Patch Set: Java file format change Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 "device/battery/battery_status_manager_linux.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <limits>
11 #include <memory>
12 #include <string>
13 #include <utility>
14 #include <vector>
15
16 #include "base/macros.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/threading/thread.h"
20 #include "base/values.h"
21 #include "base/version.h"
22 #include "dbus/bus.h"
23 #include "dbus/message.h"
24 #include "dbus/object_path.h"
25 #include "dbus/object_proxy.h"
26 #include "dbus/property.h"
27 #include "dbus/values_util.h"
28 #include "device/battery/battery_status_manager_linux-inl.h"
29
30 namespace device {
31 namespace {
32 const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier";
33
34 class UPowerProperties : public dbus::PropertySet {
35 public:
36 UPowerProperties(dbus::ObjectProxy* object_proxy,
37 const PropertyChangedCallback callback);
38 ~UPowerProperties() override;
39
40 base::Version daemon_version();
41
42 private:
43 dbus::Property<std::string> daemon_version_;
44
45 DISALLOW_COPY_AND_ASSIGN(UPowerProperties);
46 };
47
48 UPowerProperties::UPowerProperties(dbus::ObjectProxy* object_proxy,
49 const PropertyChangedCallback callback)
50 : dbus::PropertySet(object_proxy, kUPowerInterfaceName, callback) {
51 RegisterProperty(kUPowerPropertyDaemonVersion, &daemon_version_);
52 }
53
54 UPowerProperties::~UPowerProperties() {}
55
56 base::Version UPowerProperties::daemon_version() {
57 return (daemon_version_.is_valid() || daemon_version_.GetAndBlock())
58 ? base::Version(daemon_version_.value())
59 : base::Version();
60 }
61
62 class UPowerObject {
63 public:
64 using PropertyChangedCallback = dbus::PropertySet::PropertyChangedCallback;
65
66 UPowerObject(dbus::Bus* dbus,
67 const PropertyChangedCallback property_changed_callback);
68 ~UPowerObject();
69
70 std::vector<dbus::ObjectPath> EnumerateDevices();
71 dbus::ObjectPath GetDisplayDevice();
72
73 dbus::ObjectProxy* proxy() { return proxy_; }
74 UPowerProperties* properties() { return properties_.get(); }
75
76 private:
77 dbus::Bus* dbus_; // Owned by the BatteryStatusNotificationThread.
78 dbus::ObjectProxy* proxy_; // Owned by the dbus.
79 std::unique_ptr<UPowerProperties> properties_;
80
81 DISALLOW_COPY_AND_ASSIGN(UPowerObject);
82 };
83
84 UPowerObject::UPowerObject(
85 dbus::Bus* dbus,
86 const PropertyChangedCallback property_changed_callback)
87 : dbus_(dbus),
88 proxy_(dbus_->GetObjectProxy(kUPowerServiceName,
89 dbus::ObjectPath(kUPowerPath))),
90 properties_(new UPowerProperties(proxy_, property_changed_callback)) {}
91
92 UPowerObject::~UPowerObject() {
93 properties_.reset(); // before the proxy is deleted.
94 dbus_->RemoveObjectProxy(kUPowerServiceName, proxy_->object_path(),
95 base::Bind(&base::DoNothing));
96 }
97
98 std::vector<dbus::ObjectPath> UPowerObject::EnumerateDevices() {
99 std::vector<dbus::ObjectPath> paths;
100 dbus::MethodCall method_call(kUPowerServiceName,
101 kUPowerMethodEnumerateDevices);
102 std::unique_ptr<dbus::Response> response(proxy_->CallMethodAndBlock(
103 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
104
105 if (response) {
106 dbus::MessageReader reader(response.get());
107 reader.PopArrayOfObjectPaths(&paths);
108 }
109 return paths;
110 }
111
112 dbus::ObjectPath UPowerObject::GetDisplayDevice() {
113 dbus::ObjectPath display_device_path;
114 if (!proxy_)
115 return display_device_path;
116
117 dbus::MethodCall method_call(kUPowerServiceName,
118 kUPowerMethodGetDisplayDevice);
119 std::unique_ptr<dbus::Response> response(proxy_->CallMethodAndBlock(
120 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
121
122 if (response) {
123 dbus::MessageReader reader(response.get());
124 reader.PopObjectPath(&display_device_path);
125 }
126 return display_device_path;
127 }
128
129 void UpdateNumberBatteriesHistogram(int count) {
130 UMA_HISTOGRAM_CUSTOM_COUNTS(
131 "BatteryStatus.NumberBatteriesLinux", count, 1, 5, 6);
132 }
133
134 class BatteryProperties : public dbus::PropertySet {
135 public:
136 BatteryProperties(dbus::ObjectProxy* object_proxy,
137 const PropertyChangedCallback callback);
138 ~BatteryProperties() override;
139
140 void ConnectSignals() override;
141
142 void Invalidate();
143
144 bool is_present(bool default_value = false);
145 double percentage(double default_value = 100);
146 uint32_t state(uint32_t default_value = UPOWER_DEVICE_STATE_UNKNOWN);
147 int64_t time_to_empty(int64_t default_value = 0);
148 int64_t time_to_full(int64_t default_value = 0);
149 uint32_t type(uint32_t default_value = UPOWER_DEVICE_TYPE_UNKNOWN);
150
151 private:
152 bool connected_ = false;
153 dbus::Property<bool> is_present_;
154 dbus::Property<double> percentage_;
155 dbus::Property<uint32_t> state_;
156 dbus::Property<int64_t> time_to_empty_;
157 dbus::Property<int64_t> time_to_full_;
158 dbus::Property<uint32_t> type_;
159
160 DISALLOW_COPY_AND_ASSIGN(BatteryProperties);
161 };
162
163 BatteryProperties::BatteryProperties(dbus::ObjectProxy* object_proxy,
164 const PropertyChangedCallback callback)
165 : dbus::PropertySet(object_proxy, kUPowerDeviceInterfaceName, callback) {
166 RegisterProperty(kUPowerDevicePropertyIsPresent, &is_present_);
167 RegisterProperty(kUPowerDevicePropertyPercentage, &percentage_);
168 RegisterProperty(kUPowerDevicePropertyState, &state_);
169 RegisterProperty(kUPowerDevicePropertyTimeToEmpty, &time_to_empty_);
170 RegisterProperty(kUPowerDevicePropertyTimeToFull, &time_to_full_);
171 RegisterProperty(kUPowerDevicePropertyType, &type_);
172 }
173
174 BatteryProperties::~BatteryProperties() {}
175
176 void BatteryProperties::ConnectSignals() {
177 if (!connected_) {
178 connected_ = true;
179 dbus::PropertySet::ConnectSignals();
180 }
181 }
182
183 void BatteryProperties::Invalidate() {
184 is_present_.set_valid(false);
185 percentage_.set_valid(false);
186 state_.set_valid(false);
187 time_to_empty_.set_valid(false);
188 time_to_full_.set_valid(false);
189 type_.set_valid(false);
190 }
191
192 bool BatteryProperties::is_present(bool default_value) {
193 return (is_present_.is_valid() || is_present_.GetAndBlock())
194 ? is_present_.value()
195 : default_value;
196 }
197
198 double BatteryProperties::percentage(double default_value) {
199 return (percentage_.is_valid() || percentage_.GetAndBlock())
200 ? percentage_.value()
201 : default_value;
202 }
203
204 uint32_t BatteryProperties::state(uint32_t default_value) {
205 return (state_.is_valid() || state_.GetAndBlock()) ? state_.value()
206 : default_value;
207 }
208
209 int64_t BatteryProperties::time_to_empty(int64_t default_value) {
210 return (time_to_empty_.is_valid() || time_to_empty_.GetAndBlock())
211 ? time_to_empty_.value()
212 : default_value;
213 }
214
215 int64_t BatteryProperties::time_to_full(int64_t default_value) {
216 return (time_to_full_.is_valid() || time_to_full_.GetAndBlock())
217 ? time_to_full_.value()
218 : default_value;
219 }
220
221 uint32_t BatteryProperties::type(uint32_t default_value) {
222 return (type_.is_valid() || type_.GetAndBlock()) ? type_.value()
223 : default_value;
224 }
225
226 class BatteryObject {
227 public:
228 using PropertyChangedCallback = dbus::PropertySet::PropertyChangedCallback;
229
230 BatteryObject(dbus::Bus* dbus,
231 const dbus::ObjectPath& device_path,
232 const PropertyChangedCallback& property_changed_callback);
233 ~BatteryObject();
234
235 bool IsValid();
236
237 dbus::ObjectProxy* proxy() { return proxy_; }
238 BatteryProperties* properties() { return properties_.get(); }
239
240 private:
241 dbus::Bus* dbus_; // Owned by the BatteryStatusNotificationThread,
242 dbus::ObjectProxy* proxy_; // Owned by the dbus.
243 std::unique_ptr<BatteryProperties> properties_;
244
245 DISALLOW_COPY_AND_ASSIGN(BatteryObject);
246 };
247
248 BatteryObject::BatteryObject(
249 dbus::Bus* dbus,
250 const dbus::ObjectPath& device_path,
251 const PropertyChangedCallback& property_changed_callback)
252 : dbus_(dbus),
253 proxy_(dbus_->GetObjectProxy(kUPowerServiceName, device_path)),
254 properties_(new BatteryProperties(proxy_, property_changed_callback)) {}
255
256 BatteryObject::~BatteryObject() {
257 properties_.reset(); // before the proxy is deleted.
258 dbus_->RemoveObjectProxy(kUPowerServiceName, proxy_->object_path(),
259 base::Bind(&base::DoNothing));
260 }
261
262 bool BatteryObject::IsValid() {
263 return properties_->is_present() &&
264 properties_->type() == UPOWER_DEVICE_TYPE_BATTERY;
265 }
266
267 mojom::BatteryStatus ComputeWebBatteryStatus(BatteryProperties* properties) {
268 mojom::BatteryStatus status;
269 uint32_t state = properties->state();
270 status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING &&
271 state != UPOWER_DEVICE_STATE_EMPTY;
272 // Convert percentage to a value between 0 and 1 with 2 digits of precision.
273 // This is to bring it in line with other platforms like Mac and Android where
274 // we report level with 1% granularity. It also serves the purpose of reducing
275 // the possibility of fingerprinting and triggers less level change events on
276 // the blink side.
277 // TODO(timvolodine): consider moving this rounding to the blink side.
278 status.level = round(properties->percentage()) / 100.f;
279
280 switch (state) {
281 case UPOWER_DEVICE_STATE_CHARGING: {
282 int64_t time_to_full = properties->time_to_full();
283 status.charging_time = (time_to_full > 0)
284 ? time_to_full
285 : std::numeric_limits<double>::infinity();
286 break;
287 }
288 case UPOWER_DEVICE_STATE_DISCHARGING: {
289 int64_t time_to_empty = properties->time_to_empty();
290 // Set dischargingTime if it's available. Otherwise leave the default
291 // value which is +infinity.
292 if (time_to_empty > 0)
293 status.discharging_time = time_to_empty;
294 status.charging_time = std::numeric_limits<double>::infinity();
295 break;
296 }
297 case UPOWER_DEVICE_STATE_FULL: {
298 break;
299 }
300 default: { status.charging_time = std::numeric_limits<double>::infinity(); }
301 }
302 return status;
303 }
304
305 } // namespace
306
307 // Class that represents a dedicated thread which communicates with DBus to
308 // obtain battery information and receives battery change notifications.
309 class BatteryStatusManagerLinux::BatteryStatusNotificationThread
310 : public base::Thread {
311 public:
312 BatteryStatusNotificationThread(
313 const BatteryStatusService::BatteryUpdateCallback& callback)
314 : base::Thread(kBatteryNotifierThreadName), callback_(callback) {}
315
316 ~BatteryStatusNotificationThread() override {
317 // Make sure to shutdown the dbus connection if it is still open in the very
318 // end. It needs to happen on the BatteryStatusNotificationThread.
319 message_loop()->task_runner()->PostTask(
320 FROM_HERE,
321 base::Bind(&BatteryStatusNotificationThread::ShutdownDBusConnection,
322 base::Unretained(this)));
323
324 // Drain the message queue of the BatteryStatusNotificationThread and stop.
325 Stop();
326 }
327
328 void StartListening() {
329 DCHECK(OnWatcherThread());
330
331 if (upower_)
332 return;
333
334 if (!system_bus_)
335 InitDBus();
336
337 upower_ = base::MakeUnique<UPowerObject>(
338 system_bus_.get(), UPowerObject::PropertyChangedCallback());
339 upower_->proxy()->ConnectToSignal(
340 kUPowerServiceName, kUPowerSignalDeviceAdded,
341 base::Bind(&BatteryStatusNotificationThread::DeviceAdded,
342 base::Unretained(this)),
343 base::Bind(&BatteryStatusNotificationThread::OnSignalConnected,
344 base::Unretained(this)));
345 upower_->proxy()->ConnectToSignal(
346 kUPowerServiceName, kUPowerSignalDeviceRemoved,
347 base::Bind(&BatteryStatusNotificationThread::DeviceRemoved,
348 base::Unretained(this)),
349 base::Bind(&BatteryStatusNotificationThread::OnSignalConnected,
350 base::Unretained(this)));
351
352 FindBatteryDevice();
353 }
354
355 void StopListening() {
356 DCHECK(OnWatcherThread());
357 ShutdownDBusConnection();
358 }
359
360 void SetDBusForTesting(dbus::Bus* bus) { system_bus_ = bus; }
361
362 private:
363 bool OnWatcherThread() {
364 return task_runner()->BelongsToCurrentThread();
365 }
366
367 void InitDBus() {
368 DCHECK(OnWatcherThread());
369
370 dbus::Bus::Options options;
371 options.bus_type = dbus::Bus::SYSTEM;
372 options.connection_type = dbus::Bus::PRIVATE;
373 system_bus_ = new dbus::Bus(options);
374 }
375
376 bool IsDaemonVersionBelow_0_99() {
377 base::Version daemon_version = upower_->properties()->daemon_version();
378 return daemon_version.IsValid() &&
379 daemon_version.CompareTo(base::Version("0.99")) < 0;
380 }
381
382 void FindBatteryDevice() {
383 // Move the currently watched battery_ device to a stack-local variable such
384 // that we can enumerate all devices (once more):
385 // first testing the display device, then testing all devices from
386 // EnumerateDevices. We will monitor the first battery device we find.
387 // - That may be the same device we did monitor on entering this method;
388 // then we'll use the same BatteryObject instance, that was moved to
389 // current - see UseCurrentOrCreateBattery().
390 // - Or it may be a new device; then the previously monitored BatteryObject
391 // instance (if any) is released on leaving this function.
392 // - Or we may not find a battery device; then on leaving this function
393 // battery_ will be nullptr and the previously monitored BatteryObject
394 // instance (if any) is no longer a battery and will be released.
395 std::unique_ptr<BatteryObject> current = std::move(battery_);
396 auto UseCurrentOrCreateBattery =
397 [&current, this](const dbus::ObjectPath& device_path) {
398 if (current && current->proxy()->object_path() == device_path)
399 return std::move(current);
400 return CreateBattery(device_path);
401 };
402
403 dbus::ObjectPath display_device_path;
404 if (!IsDaemonVersionBelow_0_99())
405 display_device_path = upower_->GetDisplayDevice();
406 if (display_device_path.IsValid()) {
407 auto battery = UseCurrentOrCreateBattery(display_device_path);
408 if (battery->IsValid())
409 battery_ = std::move(battery);
410 }
411
412 if (!battery_) {
413 int num_batteries = 0;
414 for (const auto& device_path : upower_->EnumerateDevices()) {
415 auto battery = UseCurrentOrCreateBattery(device_path);
416 if (!battery->IsValid())
417 continue;
418
419 if (battery_) {
420 // TODO(timvolodine): add support for multiple batteries. Currently we
421 // only collect information from the first battery we encounter
422 // (crbug.com/400780).
423 LOG(WARNING) << "multiple batteries found, "
424 << "using status data of the first battery only.";
425 } else {
426 battery_ = std::move(battery);
427 }
428 num_batteries++;
429 }
430
431 UpdateNumberBatteriesHistogram(num_batteries);
432 }
433
434 if (!battery_) {
435 callback_.Run(mojom::BatteryStatus());
436 return;
437 }
438
439 battery_->properties()->ConnectSignals();
440 NotifyBatteryStatus();
441
442 if (IsDaemonVersionBelow_0_99()) {
443 // UPower Version 0.99 replaced the Changed signal with the
444 // PropertyChanged signal. For older versions we need to listen
445 // to the Changed signal.
446 battery_->proxy()->ConnectToSignal(
447 kUPowerDeviceInterfaceName, kUPowerDeviceSignalChanged,
448 base::Bind(&BatteryStatusNotificationThread::BatteryChanged,
449 base::Unretained(this)),
450 base::Bind(&BatteryStatusNotificationThread::OnSignalConnected,
451 base::Unretained(this)));
452 }
453 }
454
455 void ShutdownDBusConnection() {
456 DCHECK(OnWatcherThread());
457
458 if (!system_bus_.get())
459 return;
460
461 battery_.reset(); // before the system_bus_ is shut down.
462 upower_.reset();
463
464 // Shutdown DBus connection later because there may be pending tasks on
465 // this thread.
466 message_loop()->task_runner()->PostTask(
467 FROM_HERE, base::Bind(&dbus::Bus::ShutdownAndBlock, system_bus_));
468 system_bus_ = NULL;
469 }
470
471 void OnSignalConnected(const std::string& interface_name,
472 const std::string& signal_name,
473 bool success) {}
474
475 std::unique_ptr<BatteryObject> CreateBattery(
476 const dbus::ObjectPath& device_path) {
477 return base::MakeUnique<BatteryObject>(
478 system_bus_.get(), device_path,
479 base::Bind(&BatteryStatusNotificationThread::BatteryPropertyChanged,
480 base::Unretained(this)));
481 }
482
483 void DeviceAdded(dbus::Signal* signal /* unused */) {
484 // Re-iterate all devices to see if we need to monitor the added battery
485 // instead of the currently monitored battery.
486 FindBatteryDevice();
487 }
488
489 void DeviceRemoved(dbus::Signal* signal) {
490 if (!battery_)
491 return;
492
493 // UPower specifies that the DeviceRemoved signal has an object-path as
494 // argument, however IRL that signal was observed with a string argument,
495 // so cover both cases (argument as string, as object-path and neither of
496 // these) and call FindBatteryDevice() if either we couldn't get the
497 // argument or the removed device-path is the battery_.
498 dbus::MessageReader reader(signal);
499 dbus::ObjectPath removed_device_path;
500 switch (reader.GetDataType()) {
501 case dbus::Message::DataType::STRING: {
502 std::string removed_device_path_string;
503 if (reader.PopString(&removed_device_path_string))
504 removed_device_path = dbus::ObjectPath(removed_device_path_string);
505 break;
506 }
507
508 case dbus::Message::DataType::OBJECT_PATH:
509 reader.PopObjectPath(&removed_device_path);
510 break;
511
512 default:
513 break;
514 }
515
516 if (!removed_device_path.IsValid() ||
517 battery_->proxy()->object_path() == removed_device_path)
518 FindBatteryDevice();
519 }
520
521 void BatteryPropertyChanged(const std::string& property_name) {
522 NotifyBatteryStatus();
523 }
524
525 void BatteryChanged(dbus::Signal* signal /* unsused */) {
526 DCHECK(battery_);
527 battery_->properties()->Invalidate();
528 NotifyBatteryStatus();
529 }
530
531 void NotifyBatteryStatus() {
532 DCHECK(OnWatcherThread());
533
534 if (!system_bus_.get() || !battery_ || notifying_battery_status_)
535 return;
536
537 // If the system uses a UPower daemon older than version 0.99
538 // (see IsDaemonVersionBelow_0_99), then we are notified about changed
539 // battery_ properties through the 'Changed' signal of the battery_
540 // device (see BatteryChanged()). That is implemented to invalidate all
541 // battery_ properties (so they are re-fetched from the dbus). Getting
542 // the new property-value triggers a callback to BatteryPropertyChanged().
543 // notifying_battery_status_ is set to avoid recursion and computing the
544 // status too often.
545 notifying_battery_status_ = true;
546 callback_.Run(ComputeWebBatteryStatus(battery_->properties()));
547 notifying_battery_status_ = false;
548 }
549
550 BatteryStatusService::BatteryUpdateCallback callback_;
551 scoped_refptr<dbus::Bus> system_bus_;
552 std::unique_ptr<UPowerObject> upower_;
553 std::unique_ptr<BatteryObject> battery_;
554 bool notifying_battery_status_ = false;
555
556 DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread);
557 };
558
559 BatteryStatusManagerLinux::BatteryStatusManagerLinux(
560 const BatteryStatusService::BatteryUpdateCallback& callback)
561 : callback_(callback) {}
562
563 BatteryStatusManagerLinux::~BatteryStatusManagerLinux() {}
564
565 bool BatteryStatusManagerLinux::StartListeningBatteryChange() {
566 if (!StartNotifierThreadIfNecessary())
567 return false;
568
569 notifier_thread_->task_runner()->PostTask(
570 FROM_HERE, base::Bind(&BatteryStatusNotificationThread::StartListening,
571 base::Unretained(notifier_thread_.get())));
572 return true;
573 }
574
575 void BatteryStatusManagerLinux::StopListeningBatteryChange() {
576 if (!notifier_thread_)
577 return;
578
579 notifier_thread_->task_runner()->PostTask(
580 FROM_HERE, base::Bind(&BatteryStatusNotificationThread::StopListening,
581 base::Unretained(notifier_thread_.get())));
582 }
583
584 bool BatteryStatusManagerLinux::StartNotifierThreadIfNecessary() {
585 if (notifier_thread_)
586 return true;
587
588 base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
589 auto notifier_thread =
590 base::MakeUnique<BatteryStatusNotificationThread>(callback_);
591 if (!notifier_thread->StartWithOptions(thread_options)) {
592 LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName
593 << " thread";
594 return false;
595 }
596 notifier_thread_ = std::move(notifier_thread);
597 return true;
598 }
599
600 base::Thread* BatteryStatusManagerLinux::GetNotifierThreadForTesting() {
601 return notifier_thread_.get();
602 }
603
604 // static
605 std::unique_ptr<BatteryStatusManagerLinux>
606 BatteryStatusManagerLinux::CreateForTesting(
607 const BatteryStatusService::BatteryUpdateCallback& callback,
608 dbus::Bus* bus) {
609 auto manager = base::MakeUnique<BatteryStatusManagerLinux>(callback);
610 if (!manager->StartNotifierThreadIfNecessary())
611 return nullptr;
612 manager->notifier_thread_->SetDBusForTesting(bus);
613 return manager;
614 }
615
616 // static
617 std::unique_ptr<BatteryStatusManager> BatteryStatusManager::Create(
618 const BatteryStatusService::BatteryUpdateCallback& callback) {
619 return base::MakeUnique<BatteryStatusManagerLinux>(callback);
620 }
621
622 } // namespace device
OLDNEW
« no previous file with comments | « device/battery/battery_status_manager_linux.h ('k') | device/battery/battery_status_manager_linux-inl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698