Bug 1649121: part 9) Simplify `GetMostAncestorListOrTableElement`. r=masayuki
[gecko.git] / hal / linux / UPowerClient.cpp
blob8bf732e233f99719116462fba43643332c3a9035
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "Hal.h"
7 #include "HalLog.h"
8 #include <dbus/dbus-glib.h>
9 #include <dbus/dbus-glib-lowlevel.h>
10 #include <mozilla/Attributes.h>
11 #include <mozilla/dom/battery/Constants.h>
12 #include "nsAutoRef.h"
13 #include <cmath>
16 * Helper that manages the destruction of glib objects as soon as they leave
17 * the current scope.
19 * We are specializing nsAutoRef class.
22 template <>
23 class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable> {
24 public:
25 static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); }
28 using namespace mozilla::dom::battery;
30 namespace mozilla {
31 namespace hal_impl {
33 /**
34 * This is the declaration of UPowerClient class. This class is listening and
35 * communicating to upower daemon through DBus.
36 * There is no header file because this class shouldn't be public.
38 class UPowerClient {
39 public:
40 static UPowerClient* GetInstance();
42 void BeginListening();
43 void StopListening();
45 double GetLevel();
46 bool IsCharging();
47 double GetRemainingTime();
49 ~UPowerClient();
51 private:
52 UPowerClient();
54 enum States {
55 eState_Unknown = 0,
56 eState_Charging,
57 eState_Discharging,
58 eState_Empty,
59 eState_FullyCharged,
60 eState_PendingCharge,
61 eState_PendingDischarge
64 /**
65 * Update the currently tracked device.
66 * @return whether everything went ok.
68 void UpdateTrackedDeviceSync();
70 /**
71 * Returns a hash table with the properties of aDevice.
72 * Note: the caller has to unref the hash table.
74 GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy);
75 void GetDevicePropertiesAsync(DBusGProxy* aProxy);
76 static void GetDevicePropertiesCallback(DBusGProxy* aProxy,
77 DBusGProxyCall* aCall, void* aData);
79 /**
80 * Using the device properties (aHashTable), this method updates the member
81 * variable storing the values we care about.
83 void UpdateSavedInfo(GHashTable* aHashTable);
85 /**
86 * Callback used by 'DeviceChanged' signal.
88 static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
89 UPowerClient* aListener);
91 /**
92 * Callback used by 'PropertiesChanged' signal.
93 * This method is called when the the battery level changes.
94 * (Only with upower >= 0.99)
96 static void PropertiesChanged(DBusGProxy* aProxy, const gchar*, GHashTable*,
97 char**, UPowerClient* aListener);
99 /**
100 * Callback called when mDBusConnection gets a signal.
102 static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
103 DBusMessage* aMessage,
104 void* aData);
106 // The DBus connection object.
107 DBusGConnection* mDBusConnection;
109 // The DBus proxy object to upower.
110 DBusGProxy* mUPowerProxy;
112 // The path of the tracked device.
113 gchar* mTrackedDevice;
115 // The DBusGProxy for the tracked device.
116 DBusGProxy* mTrackedDeviceProxy;
118 double mLevel;
119 bool mCharging;
120 double mRemainingTime;
122 static UPowerClient* sInstance;
124 static const guint sDeviceTypeBattery = 2;
125 static const guint64 kUPowerUnknownRemainingTime = 0;
129 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
130 * mozilla::hal_impl::DisableBatteryNotifications,
131 * and mozilla::hal_impl::GetCurrentBatteryInformation.
134 void EnableBatteryNotifications() {
135 UPowerClient::GetInstance()->BeginListening();
138 void DisableBatteryNotifications() {
139 UPowerClient::GetInstance()->StopListening();
142 void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) {
143 UPowerClient* upowerClient = UPowerClient::GetInstance();
145 aBatteryInfo->level() = upowerClient->GetLevel();
146 aBatteryInfo->charging() = upowerClient->IsCharging();
147 aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime();
151 * Following is the implementation of UPowerClient.
154 UPowerClient* UPowerClient::sInstance = nullptr;
156 /* static */
157 UPowerClient* UPowerClient::GetInstance() {
158 if (!sInstance) {
159 sInstance = new UPowerClient();
162 return sInstance;
165 UPowerClient::UPowerClient()
166 : mDBusConnection(nullptr),
167 mUPowerProxy(nullptr),
168 mTrackedDevice(nullptr),
169 mTrackedDeviceProxy(nullptr),
170 mLevel(kDefaultLevel),
171 mCharging(kDefaultCharging),
172 mRemainingTime(kDefaultRemainingTime) {}
174 UPowerClient::~UPowerClient() {
175 NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice &&
176 !mTrackedDeviceProxy,
177 "The observers have not been correctly removed! "
178 "(StopListening should have been called)");
181 void UPowerClient::BeginListening() {
182 GError* error = nullptr;
183 mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
185 if (!mDBusConnection) {
186 HAL_LOG("Failed to open connection to bus: %s\n", error->message);
187 g_error_free(error);
188 return;
191 DBusConnection* dbusConnection =
192 dbus_g_connection_get_connection(mDBusConnection);
194 // Make sure we do not exit the entire program if DBus connection get lost.
195 dbus_connection_set_exit_on_disconnect(dbusConnection, false);
197 // Listening to signals the DBus connection is going to get so we will know
198 // when it is lost and we will be able to disconnect cleanly.
199 dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
200 nullptr);
202 mUPowerProxy = dbus_g_proxy_new_for_name(
203 mDBusConnection, "org.freedesktop.UPower", "/org/freedesktop/UPower",
204 "org.freedesktop.UPower");
206 UpdateTrackedDeviceSync();
209 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
210 * If we do that, we would have to disconnect from those in StopListening.
211 * It's not yet implemented because it requires testing hot plugging and
212 * removal of a battery.
214 dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING,
215 G_TYPE_INVALID);
216 dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged",
217 G_CALLBACK(DeviceChanged), this, nullptr);
220 void UPowerClient::StopListening() {
221 // If mDBusConnection isn't initialized, that means we are not really
222 // listening.
223 if (!mDBusConnection) {
224 return;
227 dbus_connection_remove_filter(
228 dbus_g_connection_get_connection(mDBusConnection), ConnectionSignalFilter,
229 this);
231 dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
232 G_CALLBACK(DeviceChanged), this);
234 g_free(mTrackedDevice);
235 mTrackedDevice = nullptr;
237 if (mTrackedDeviceProxy) {
238 dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged",
239 G_CALLBACK(PropertiesChanged), this);
241 g_object_unref(mTrackedDeviceProxy);
242 mTrackedDeviceProxy = nullptr;
245 g_object_unref(mUPowerProxy);
246 mUPowerProxy = nullptr;
248 dbus_g_connection_unref(mDBusConnection);
249 mDBusConnection = nullptr;
251 // We should now show the default values, not the latest we got.
252 mLevel = kDefaultLevel;
253 mCharging = kDefaultCharging;
254 mRemainingTime = kDefaultRemainingTime;
257 void UPowerClient::UpdateTrackedDeviceSync() {
258 GType typeGPtrArray =
259 dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH);
260 GPtrArray* devices = nullptr;
261 GError* error = nullptr;
263 // Reset the current tracked device:
264 g_free(mTrackedDevice);
265 mTrackedDevice = nullptr;
267 // Reset the current tracked device proxy:
268 if (mTrackedDeviceProxy) {
269 dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged",
270 G_CALLBACK(PropertiesChanged), this);
272 g_object_unref(mTrackedDeviceProxy);
273 mTrackedDeviceProxy = nullptr;
276 // If that fails, that likely means upower isn't installed.
277 if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error,
278 G_TYPE_INVALID, typeGPtrArray, &devices,
279 G_TYPE_INVALID)) {
280 HAL_LOG("Error: %s\n", error->message);
281 g_error_free(error);
282 return;
286 * We are looking for the first device that is a battery.
287 * TODO: we could try to combine more than one battery.
289 for (guint i = 0; i < devices->len; ++i) {
290 gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i));
292 DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(
293 mUPowerProxy, "org.freedesktop.DBus.Properties", devicePath);
295 nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy));
297 if (g_value_get_uint(static_cast<const GValue*>(
298 g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) {
299 UpdateSavedInfo(hashTable);
300 mTrackedDevice = devicePath;
301 mTrackedDeviceProxy = proxy;
302 break;
305 g_object_unref(proxy);
306 g_free(devicePath);
309 if (mTrackedDeviceProxy) {
310 dbus_g_proxy_add_signal(
311 mTrackedDeviceProxy, "PropertiesChanged", G_TYPE_STRING,
312 dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
313 G_TYPE_STRV, G_TYPE_INVALID);
314 dbus_g_proxy_connect_signal(mTrackedDeviceProxy, "PropertiesChanged",
315 G_CALLBACK(PropertiesChanged), this, nullptr);
318 g_ptr_array_free(devices, true);
321 /* static */
322 void UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
323 UPowerClient* aListener) {
324 if (!aListener->mTrackedDevice) {
325 return;
328 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
329 if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) {
330 #else
331 if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) {
332 #endif
333 return;
336 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
339 /* static */
340 void UPowerClient::PropertiesChanged(DBusGProxy* aProxy, const gchar*,
341 GHashTable*, char**,
342 UPowerClient* aListener) {
343 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
346 /* static */
347 DBusHandlerResult UPowerClient::ConnectionSignalFilter(
348 DBusConnection* aConnection, DBusMessage* aMessage, void* aData) {
349 if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
350 static_cast<UPowerClient*>(aData)->StopListening();
351 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
352 // might be shared and some other filters might want to do something.
355 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
358 GHashTable* UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy) {
359 GError* error = nullptr;
360 GHashTable* hashTable = nullptr;
361 GType typeGHashTable =
362 dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
363 if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING,
364 "org.freedesktop.UPower.Device", G_TYPE_INVALID,
365 typeGHashTable, &hashTable, G_TYPE_INVALID)) {
366 HAL_LOG("Error: %s\n", error->message);
367 g_error_free(error);
368 return nullptr;
371 return hashTable;
374 /* static */
375 void UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy,
376 DBusGProxyCall* aCall,
377 void* aData) {
378 GError* error = nullptr;
379 GHashTable* hashTable = nullptr;
380 GType typeGHashTable =
381 dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
382 if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable, &hashTable,
383 G_TYPE_INVALID)) {
384 HAL_LOG("Error: %s\n", error->message);
385 g_error_free(error);
386 } else {
387 sInstance->UpdateSavedInfo(hashTable);
388 hal::NotifyBatteryChange(hal::BatteryInformation(
389 sInstance->mLevel, sInstance->mCharging, sInstance->mRemainingTime));
390 g_hash_table_unref(hashTable);
394 void UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy) {
395 dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback,
396 nullptr, nullptr, G_TYPE_STRING,
397 "org.freedesktop.UPower.Device", G_TYPE_INVALID);
400 void UPowerClient::UpdateSavedInfo(GHashTable* aHashTable) {
401 bool isFull = false;
404 * State values are confusing...
405 * First of all, after looking at upower sources (0.9.13), it seems that
406 * PendingDischarge and PendingCharge are not used.
407 * In addition, FullyCharged and Empty states are not clear because we do not
408 * know if the battery is actually charging or not. Those values come directly
409 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
410 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
411 * related to the level, not to the charging state.
412 * In this code, we are going to assume that Full means charging and Empty
413 * means discharging because if that is not the case, the state should not
414 * last a long time (actually, it should disappear at the following update).
415 * It might be even very hard to see real cases where the state is Empty and
416 * the battery is charging or the state is Full and the battery is discharging
417 * given that plugging/unplugging the battery should have an impact on the
418 * level.
420 switch (g_value_get_uint(
421 static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) {
422 case eState_Unknown:
423 mCharging = kDefaultCharging;
424 break;
425 case eState_FullyCharged:
426 isFull = true;
427 [[fallthrough]];
428 case eState_Charging:
429 case eState_PendingCharge:
430 mCharging = true;
431 break;
432 case eState_Discharging:
433 case eState_Empty:
434 case eState_PendingDischarge:
435 mCharging = false;
436 break;
440 * The battery level might be very close to 100% (like 99%) without
441 * increasing. It seems that upower sets the battery state as 'full' in that
442 * case so we should trust it and not even try to get the value.
444 if (isFull) {
445 mLevel = 1.0;
446 } else {
447 mLevel = round(g_value_get_double(static_cast<const GValue*>(
448 g_hash_table_lookup(aHashTable, "Percentage")))) *
449 0.01;
452 if (isFull) {
453 mRemainingTime = 0;
454 } else {
455 mRemainingTime = mCharging
456 ? g_value_get_int64(static_cast<const GValue*>(
457 g_hash_table_lookup(aHashTable, "TimeToFull")))
458 : g_value_get_int64(static_cast<const GValue*>(
459 g_hash_table_lookup(aHashTable, "TimeToEmpty")));
461 if (mRemainingTime == kUPowerUnknownRemainingTime) {
462 mRemainingTime = kUnknownRemainingTime;
467 double UPowerClient::GetLevel() { return mLevel; }
469 bool UPowerClient::IsCharging() { return mCharging; }
471 double UPowerClient::GetRemainingTime() { return mRemainingTime; }
473 } // namespace hal_impl
474 } // namespace mozilla