Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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 <mozilla/Hal.h>
7 #include <dbus/dbus-glib.h>
8 #include <dbus/dbus-glib-lowlevel.h>
9 #include <mozilla/dom/battery/Constants.h>
10 #include "nsAutoRef.h"
12 /*
13 * Helper that manages the destruction of glib objects as soon as they leave
14 * the current scope.
15 *
16 * We are specializing nsAutoRef class.
17 */
19 template <>
20 class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable>
21 {
22 public:
23 static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); }
24 };
26 using namespace mozilla::dom::battery;
28 namespace mozilla {
29 namespace hal_impl {
31 /**
32 * This is the declaration of UPowerClient class. This class is listening and
33 * communicating to upower daemon through DBus.
34 * There is no header file because this class shouldn't be public.
35 */
36 class UPowerClient
37 {
38 public:
39 static UPowerClient* GetInstance();
41 void BeginListening();
42 void StopListening();
44 double GetLevel();
45 bool IsCharging();
46 double GetRemainingTime();
48 ~UPowerClient();
50 private:
51 UPowerClient();
53 enum States {
54 eState_Unknown = 0,
55 eState_Charging,
56 eState_Discharging,
57 eState_Empty,
58 eState_FullyCharged,
59 eState_PendingCharge,
60 eState_PendingDischarge
61 };
63 /**
64 * Update the currently tracked device.
65 * @return whether everything went ok.
66 */
67 void UpdateTrackedDeviceSync();
69 /**
70 * Returns a hash table with the properties of aDevice.
71 * Note: the caller has to unref the hash table.
72 */
73 GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy);
74 void GetDevicePropertiesAsync(DBusGProxy* aProxy);
75 static void GetDevicePropertiesCallback(DBusGProxy* aProxy,
76 DBusGProxyCall* aCall,
77 void* aData);
79 /**
80 * Using the device properties (aHashTable), this method updates the member
81 * variable storing the values we care about.
82 */
83 void UpdateSavedInfo(GHashTable* aHashTable);
85 /**
86 * Callback used by 'DeviceChanged' signal.
87 */
88 static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
89 UPowerClient* aListener);
91 /**
92 * Callback called when mDBusConnection gets a signal.
93 */
94 static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
95 DBusMessage* aMessage,
96 void* aData);
98 // The DBus connection object.
99 DBusGConnection* mDBusConnection;
101 // The DBus proxy object to upower.
102 DBusGProxy* mUPowerProxy;
104 // The path of the tracked device.
105 gchar* mTrackedDevice;
107 // The DBusGProxy for the tracked device.
108 DBusGProxy* mTrackedDeviceProxy;
110 double mLevel;
111 bool mCharging;
112 double mRemainingTime;
114 static UPowerClient* sInstance;
116 static const guint sDeviceTypeBattery = 2;
117 static const guint64 kUPowerUnknownRemainingTime = 0;
118 };
120 /*
121 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
122 * mozilla::hal_impl::DisableBatteryNotifications,
123 * and mozilla::hal_impl::GetCurrentBatteryInformation.
124 */
126 void
127 EnableBatteryNotifications()
128 {
129 UPowerClient::GetInstance()->BeginListening();
130 }
132 void
133 DisableBatteryNotifications()
134 {
135 UPowerClient::GetInstance()->StopListening();
136 }
138 void
139 GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
140 {
141 UPowerClient* upowerClient = UPowerClient::GetInstance();
143 aBatteryInfo->level() = upowerClient->GetLevel();
144 aBatteryInfo->charging() = upowerClient->IsCharging();
145 aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime();
146 }
148 /*
149 * Following is the implementation of UPowerClient.
150 */
152 UPowerClient* UPowerClient::sInstance = nullptr;
154 /* static */ UPowerClient*
155 UPowerClient::GetInstance()
156 {
157 if (!sInstance) {
158 sInstance = new UPowerClient();
159 }
161 return sInstance;
162 }
164 UPowerClient::UPowerClient()
165 : mDBusConnection(nullptr)
166 , mUPowerProxy(nullptr)
167 , mTrackedDevice(nullptr)
168 , mTrackedDeviceProxy(nullptr)
169 , mLevel(kDefaultLevel)
170 , mCharging(kDefaultCharging)
171 , mRemainingTime(kDefaultRemainingTime)
172 {
173 }
175 UPowerClient::~UPowerClient()
176 {
177 NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy,
178 "The observers have not been correctly removed! "
179 "(StopListening should have been called)");
180 }
182 void
183 UPowerClient::BeginListening()
184 {
185 GError* error = nullptr;
186 mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
188 if (!mDBusConnection) {
189 g_printerr("Failed to open connection to bus: %s\n", error->message);
190 g_error_free(error);
191 return;
192 }
194 DBusConnection* dbusConnection =
195 dbus_g_connection_get_connection(mDBusConnection);
197 // Make sure we do not exit the entire program if DBus connection get lost.
198 dbus_connection_set_exit_on_disconnect(dbusConnection, false);
200 // Listening to signals the DBus connection is going to get so we will know
201 // when it is lost and we will be able to disconnect cleanly.
202 dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
203 nullptr);
205 mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection,
206 "org.freedesktop.UPower",
207 "/org/freedesktop/UPower",
208 "org.freedesktop.UPower");
210 UpdateTrackedDeviceSync();
212 /*
213 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
214 * If we do that, we would have to disconnect from those in StopListening.
215 * It's not yet implemented because it requires testing hot plugging and
216 * removal of a battery.
217 */
218 dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING,
219 G_TYPE_INVALID);
220 dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged",
221 G_CALLBACK (DeviceChanged), this, nullptr);
222 }
224 void
225 UPowerClient::StopListening()
226 {
227 // If mDBusConnection isn't initialized, that means we are not really listening.
228 if (!mDBusConnection) {
229 return;
230 }
232 dbus_connection_remove_filter(
233 dbus_g_connection_get_connection(mDBusConnection),
234 ConnectionSignalFilter, this);
236 dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
237 G_CALLBACK (DeviceChanged), this);
239 g_free(mTrackedDevice);
240 mTrackedDevice = nullptr;
242 if (mTrackedDeviceProxy) {
243 g_object_unref(mTrackedDeviceProxy);
244 mTrackedDeviceProxy = nullptr;
245 }
247 g_object_unref(mUPowerProxy);
248 mUPowerProxy = nullptr;
250 dbus_g_connection_unref(mDBusConnection);
251 mDBusConnection = nullptr;
253 // We should now show the default values, not the latest we got.
254 mLevel = kDefaultLevel;
255 mCharging = kDefaultCharging;
256 mRemainingTime = kDefaultRemainingTime;
257 }
259 void
260 UPowerClient::UpdateTrackedDeviceSync()
261 {
262 GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray",
263 DBUS_TYPE_G_OBJECT_PATH);
264 GPtrArray* devices = nullptr;
265 GError* error = nullptr;
267 // Reset the current tracked device:
268 g_free(mTrackedDevice);
269 mTrackedDevice = nullptr;
271 // Reset the current tracked device proxy:
272 if (mTrackedDeviceProxy) {
273 g_object_unref(mTrackedDeviceProxy);
274 mTrackedDeviceProxy = nullptr;
275 }
277 // If that fails, that likely means upower isn't installed.
278 if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID,
279 typeGPtrArray, &devices, G_TYPE_INVALID)) {
280 g_printerr ("Error: %s\n", error->message);
281 g_error_free(error);
282 return;
283 }
285 /*
286 * We are looking for the first device that is a battery.
287 * TODO: we could try to combine more than one battery.
288 */
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(mUPowerProxy,
293 "org.freedesktop.DBus.Properties",
294 devicePath);
296 nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy));
298 if (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) {
299 UpdateSavedInfo(hashTable);
300 mTrackedDevice = devicePath;
301 mTrackedDeviceProxy = proxy;
302 break;
303 }
305 g_object_unref(proxy);
306 g_free(devicePath);
307 }
309 g_ptr_array_free(devices, true);
310 }
312 /* static */ void
313 UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener)
314 {
315 if (!aListener->mTrackedDevice) {
316 return;
317 }
319 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
320 if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) {
321 #else
322 if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) {
323 #endif
324 return;
325 }
327 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
328 }
330 /* static */ DBusHandlerResult
331 UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection,
332 DBusMessage* aMessage, void* aData)
333 {
334 if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
335 static_cast<UPowerClient*>(aData)->StopListening();
336 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
337 // might be shared and some other filters might want to do something.
338 }
340 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
341 }
343 GHashTable*
344 UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy)
345 {
346 GError* error = nullptr;
347 GHashTable* hashTable = nullptr;
348 GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
349 G_TYPE_VALUE);
350 if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING,
351 "org.freedesktop.UPower.Device", G_TYPE_INVALID,
352 typeGHashTable, &hashTable, G_TYPE_INVALID)) {
353 g_printerr("Error: %s\n", error->message);
354 g_error_free(error);
355 return nullptr;
356 }
358 return hashTable;
359 }
361 /* static */ void
362 UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy,
363 DBusGProxyCall* aCall, void* aData)
364 {
365 GError* error = nullptr;
366 GHashTable* hashTable = nullptr;
367 GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
368 G_TYPE_VALUE);
369 if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable,
370 &hashTable, G_TYPE_INVALID)) {
371 g_printerr("Error: %s\n", error->message);
372 g_error_free(error);
373 } else {
374 sInstance->UpdateSavedInfo(hashTable);
375 hal::NotifyBatteryChange(hal::BatteryInformation(sInstance->mLevel,
376 sInstance->mCharging,
377 sInstance->mRemainingTime));
378 g_hash_table_unref(hashTable);
379 }
380 }
382 void
383 UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy)
384 {
385 dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr,
386 nullptr, G_TYPE_STRING,
387 "org.freedesktop.UPower.Device", G_TYPE_INVALID);
388 }
390 void
391 UPowerClient::UpdateSavedInfo(GHashTable* aHashTable)
392 {
393 bool isFull = false;
395 /*
396 * State values are confusing...
397 * First of all, after looking at upower sources (0.9.13), it seems that
398 * PendingDischarge and PendingCharge are not used.
399 * In addition, FullyCharged and Empty states are not clear because we do not
400 * know if the battery is actually charging or not. Those values come directly
401 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
402 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
403 * related to the level, not to the charging state.
404 * In this code, we are going to assume that Full means charging and Empty
405 * means discharging because if that is not the case, the state should not
406 * last a long time (actually, it should disappear at the following update).
407 * It might be even very hard to see real cases where the state is Empty and
408 * the battery is charging or the state is Full and the battery is discharging
409 * given that plugging/unplugging the battery should have an impact on the
410 * level.
411 */
412 switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) {
413 case eState_Unknown:
414 mCharging = kDefaultCharging;
415 break;
416 case eState_FullyCharged:
417 isFull = true;
418 case eState_Charging:
419 case eState_PendingCharge:
420 mCharging = true;
421 break;
422 case eState_Discharging:
423 case eState_Empty:
424 case eState_PendingDischarge:
425 mCharging = false;
426 break;
427 }
429 /*
430 * The battery level might be very close to 100% (like 99.xxxx%) without
431 * increasing. It seems that upower sets the battery state as 'full' in that
432 * case so we should trust it and not even try to get the value.
433 */
434 if (isFull) {
435 mLevel = 1.0;
436 } else {
437 mLevel = g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage")))*0.01;
438 }
440 if (isFull) {
441 mRemainingTime = 0;
442 } else {
443 mRemainingTime = mCharging ? g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToFull")))
444 : g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToEmpty")));
446 if (mRemainingTime == kUPowerUnknownRemainingTime) {
447 mRemainingTime = kUnknownRemainingTime;
448 }
449 }
450 }
452 double
453 UPowerClient::GetLevel()
454 {
455 return mLevel;
456 }
458 bool
459 UPowerClient::IsCharging()
460 {
461 return mCharging;
462 }
464 double
465 UPowerClient::GetRemainingTime()
466 {
467 return mRemainingTime;
468 }
470 } // namespace hal_impl
471 } // namespace mozilla