Bug 1226301. Remove Shumway from b2gdroid nightly builds. r=fabrice
[gecko.git] / hal / linux / UPowerClient.cpp
blob3b769099d953ebd7da5486675b7e66f07e359ca6
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/dom/battery/Constants.h>
11 #include "nsAutoRef.h"
12 #include <cmath>
15 * Helper that manages the destruction of glib objects as soon as they leave
16 * the current scope.
18 * We are specializing nsAutoRef class.
21 template <>
22 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
40 public:
41 static UPowerClient* GetInstance();
43 void BeginListening();
44 void StopListening();
46 double GetLevel();
47 bool IsCharging();
48 double GetRemainingTime();
50 ~UPowerClient();
52 private:
53 UPowerClient();
55 enum States {
56 eState_Unknown = 0,
57 eState_Charging,
58 eState_Discharging,
59 eState_Empty,
60 eState_FullyCharged,
61 eState_PendingCharge,
62 eState_PendingDischarge
65 /**
66 * Update the currently tracked device.
67 * @return whether everything went ok.
69 void UpdateTrackedDeviceSync();
71 /**
72 * Returns a hash table with the properties of aDevice.
73 * Note: the caller has to unref the hash table.
75 GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy);
76 void GetDevicePropertiesAsync(DBusGProxy* aProxy);
77 static void GetDevicePropertiesCallback(DBusGProxy* aProxy,
78 DBusGProxyCall* aCall,
79 void* aData);
81 /**
82 * Using the device properties (aHashTable), this method updates the member
83 * variable storing the values we care about.
85 void UpdateSavedInfo(GHashTable* aHashTable);
87 /**
88 * Callback used by 'DeviceChanged' signal.
90 static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
91 UPowerClient* aListener);
93 /**
94 * Callback called when mDBusConnection gets a signal.
96 static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
97 DBusMessage* aMessage,
98 void* aData);
100 // The DBus connection object.
101 DBusGConnection* mDBusConnection;
103 // The DBus proxy object to upower.
104 DBusGProxy* mUPowerProxy;
106 // The path of the tracked device.
107 gchar* mTrackedDevice;
109 // The DBusGProxy for the tracked device.
110 DBusGProxy* mTrackedDeviceProxy;
112 double mLevel;
113 bool mCharging;
114 double mRemainingTime;
116 static UPowerClient* sInstance;
118 static const guint sDeviceTypeBattery = 2;
119 static const guint64 kUPowerUnknownRemainingTime = 0;
123 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
124 * mozilla::hal_impl::DisableBatteryNotifications,
125 * and mozilla::hal_impl::GetCurrentBatteryInformation.
128 void
129 EnableBatteryNotifications()
131 UPowerClient::GetInstance()->BeginListening();
134 void
135 DisableBatteryNotifications()
137 UPowerClient::GetInstance()->StopListening();
140 void
141 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 */ UPowerClient*
157 UPowerClient::GetInstance()
159 if (!sInstance) {
160 sInstance = new UPowerClient();
163 return sInstance;
166 UPowerClient::UPowerClient()
167 : mDBusConnection(nullptr)
168 , mUPowerProxy(nullptr)
169 , mTrackedDevice(nullptr)
170 , mTrackedDeviceProxy(nullptr)
171 , mLevel(kDefaultLevel)
172 , mCharging(kDefaultCharging)
173 , mRemainingTime(kDefaultRemainingTime)
177 UPowerClient::~UPowerClient()
179 NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy,
180 "The observers have not been correctly removed! "
181 "(StopListening should have been called)");
184 void
185 UPowerClient::BeginListening()
187 GError* error = nullptr;
188 mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
190 if (!mDBusConnection) {
191 HAL_LOG("Failed to open connection to bus: %s\n", error->message);
192 g_error_free(error);
193 return;
196 DBusConnection* dbusConnection =
197 dbus_g_connection_get_connection(mDBusConnection);
199 // Make sure we do not exit the entire program if DBus connection get lost.
200 dbus_connection_set_exit_on_disconnect(dbusConnection, false);
202 // Listening to signals the DBus connection is going to get so we will know
203 // when it is lost and we will be able to disconnect cleanly.
204 dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
205 nullptr);
207 mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection,
208 "org.freedesktop.UPower",
209 "/org/freedesktop/UPower",
210 "org.freedesktop.UPower");
212 UpdateTrackedDeviceSync();
215 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
216 * If we do that, we would have to disconnect from those in StopListening.
217 * It's not yet implemented because it requires testing hot plugging and
218 * removal of a battery.
220 dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING,
221 G_TYPE_INVALID);
222 dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged",
223 G_CALLBACK (DeviceChanged), this, nullptr);
226 void
227 UPowerClient::StopListening()
229 // If mDBusConnection isn't initialized, that means we are not really listening.
230 if (!mDBusConnection) {
231 return;
234 dbus_connection_remove_filter(
235 dbus_g_connection_get_connection(mDBusConnection),
236 ConnectionSignalFilter, this);
238 dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
239 G_CALLBACK (DeviceChanged), this);
241 g_free(mTrackedDevice);
242 mTrackedDevice = nullptr;
244 if (mTrackedDeviceProxy) {
245 g_object_unref(mTrackedDeviceProxy);
246 mTrackedDeviceProxy = nullptr;
249 g_object_unref(mUPowerProxy);
250 mUPowerProxy = nullptr;
252 dbus_g_connection_unref(mDBusConnection);
253 mDBusConnection = nullptr;
255 // We should now show the default values, not the latest we got.
256 mLevel = kDefaultLevel;
257 mCharging = kDefaultCharging;
258 mRemainingTime = kDefaultRemainingTime;
261 void
262 UPowerClient::UpdateTrackedDeviceSync()
264 GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray",
265 DBUS_TYPE_G_OBJECT_PATH);
266 GPtrArray* devices = nullptr;
267 GError* error = nullptr;
269 // Reset the current tracked device:
270 g_free(mTrackedDevice);
271 mTrackedDevice = nullptr;
273 // Reset the current tracked device proxy:
274 if (mTrackedDeviceProxy) {
275 g_object_unref(mTrackedDeviceProxy);
276 mTrackedDeviceProxy = nullptr;
279 // If that fails, that likely means upower isn't installed.
280 if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID,
281 typeGPtrArray, &devices, G_TYPE_INVALID)) {
282 HAL_LOG("Error: %s\n", error->message);
283 g_error_free(error);
284 return;
288 * We are looking for the first device that is a battery.
289 * TODO: we could try to combine more than one battery.
291 for (guint i=0; i<devices->len; ++i) {
292 gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i));
294 DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy,
295 "org.freedesktop.DBus.Properties",
296 devicePath);
298 nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy));
300 if (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) {
301 UpdateSavedInfo(hashTable);
302 mTrackedDevice = devicePath;
303 mTrackedDeviceProxy = proxy;
304 break;
307 g_object_unref(proxy);
308 g_free(devicePath);
311 g_ptr_array_free(devices, true);
314 /* static */ void
315 UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener)
317 if (!aListener->mTrackedDevice) {
318 return;
321 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
322 if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) {
323 #else
324 if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) {
325 #endif
326 return;
329 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
332 /* static */ DBusHandlerResult
333 UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection,
334 DBusMessage* aMessage, void* aData)
336 if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
337 static_cast<UPowerClient*>(aData)->StopListening();
338 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
339 // might be shared and some other filters might want to do something.
342 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
345 GHashTable*
346 UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy)
348 GError* error = nullptr;
349 GHashTable* hashTable = nullptr;
350 GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
351 G_TYPE_VALUE);
352 if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING,
353 "org.freedesktop.UPower.Device", G_TYPE_INVALID,
354 typeGHashTable, &hashTable, G_TYPE_INVALID)) {
355 HAL_LOG("Error: %s\n", error->message);
356 g_error_free(error);
357 return nullptr;
360 return hashTable;
363 /* static */ void
364 UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy,
365 DBusGProxyCall* aCall, void* aData)
367 GError* error = nullptr;
368 GHashTable* hashTable = nullptr;
369 GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
370 G_TYPE_VALUE);
371 if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable,
372 &hashTable, G_TYPE_INVALID)) {
373 HAL_LOG("Error: %s\n", error->message);
374 g_error_free(error);
375 } else {
376 sInstance->UpdateSavedInfo(hashTable);
377 hal::NotifyBatteryChange(hal::BatteryInformation(sInstance->mLevel,
378 sInstance->mCharging,
379 sInstance->mRemainingTime));
380 g_hash_table_unref(hashTable);
384 void
385 UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy)
387 dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr,
388 nullptr, G_TYPE_STRING,
389 "org.freedesktop.UPower.Device", G_TYPE_INVALID);
392 void
393 UPowerClient::UpdateSavedInfo(GHashTable* aHashTable)
395 bool isFull = false;
398 * State values are confusing...
399 * First of all, after looking at upower sources (0.9.13), it seems that
400 * PendingDischarge and PendingCharge are not used.
401 * In addition, FullyCharged and Empty states are not clear because we do not
402 * know if the battery is actually charging or not. Those values come directly
403 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
404 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
405 * related to the level, not to the charging state.
406 * In this code, we are going to assume that Full means charging and Empty
407 * means discharging because if that is not the case, the state should not
408 * last a long time (actually, it should disappear at the following update).
409 * It might be even very hard to see real cases where the state is Empty and
410 * the battery is charging or the state is Full and the battery is discharging
411 * given that plugging/unplugging the battery should have an impact on the
412 * level.
414 switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) {
415 case eState_Unknown:
416 mCharging = kDefaultCharging;
417 break;
418 case eState_FullyCharged:
419 isFull = true;
420 case eState_Charging:
421 case eState_PendingCharge:
422 mCharging = true;
423 break;
424 case eState_Discharging:
425 case eState_Empty:
426 case eState_PendingDischarge:
427 mCharging = false;
428 break;
432 * The battery level might be very close to 100% (like 99%) without
433 * increasing. It seems that upower sets the battery state as 'full' in that
434 * case so we should trust it and not even try to get the value.
436 if (isFull) {
437 mLevel = 1.0;
438 } else {
439 mLevel = round(g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage"))))*0.01;
442 if (isFull) {
443 mRemainingTime = 0;
444 } else {
445 mRemainingTime = mCharging ? g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToFull")))
446 : g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToEmpty")));
448 if (mRemainingTime == kUPowerUnknownRemainingTime) {
449 mRemainingTime = kUnknownRemainingTime;
454 double
455 UPowerClient::GetLevel()
457 return mLevel;
460 bool
461 UPowerClient::IsCharging()
463 return mCharging;
466 double
467 UPowerClient::GetRemainingTime()
469 return mRemainingTime;
472 } // namespace hal_impl
473 } // namespace mozilla