Coverage Report

Created: 2018-09-25 14:53

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