LCOV - code coverage report
Current view: top level - hal/linux - UPowerClient.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 161 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 21 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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           0 : 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           0 : EnableBatteryNotifications()
     140             : {
     141           0 :   UPowerClient::GetInstance()->BeginListening();
     142           0 : }
     143             : 
     144             : void
     145           0 : DisableBatteryNotifications()
     146             : {
     147           0 :   UPowerClient::GetInstance()->StopListening();
     148           0 : }
     149             : 
     150             : void
     151           0 : GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
     152             : {
     153           0 :   UPowerClient* upowerClient = UPowerClient::GetInstance();
     154             : 
     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           0 : UPowerClient::GetInstance()
     168             : {
     169           0 :   if (!sInstance) {
     170           0 :     sInstance = new UPowerClient();
     171             :   }
     172             : 
     173           0 :   return sInstance;
     174             : }
     175             : 
     176           0 : UPowerClient::UPowerClient()
     177             :   : mDBusConnection(nullptr)
     178             :   , mUPowerProxy(nullptr)
     179             :   , mTrackedDevice(nullptr)
     180             :   , mTrackedDeviceProxy(nullptr)
     181             :   , mLevel(kDefaultLevel)
     182             :   , mCharging(kDefaultCharging)
     183           0 :   , mRemainingTime(kDefaultRemainingTime)
     184             : {
     185           0 : }
     186             : 
     187           0 : UPowerClient::~UPowerClient()
     188             : {
     189           0 :   NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy,
     190             :                "The observers have not been correctly removed! "
     191             :                "(StopListening should have been called)");
     192           0 : }
     193             : 
     194             : void
     195           0 : UPowerClient::BeginListening()
     196             : {
     197           0 :   GError* error = nullptr;
     198           0 :   mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
     199             : 
     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             :   }
     205             : 
     206             :   DBusConnection* dbusConnection =
     207           0 :     dbus_g_connection_get_connection(mDBusConnection);
     208             : 
     209             :   // 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             : 
     212             :   // Listening to signals the DBus connection is going to get so we will know
     213             :   // when it is lost and we will be able to disconnect cleanly.
     214             :   dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
     215           0 :                              nullptr);
     216             : 
     217           0 :   mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection,
     218             :                                            "org.freedesktop.UPower",
     219             :                                            "/org/freedesktop/UPower",
     220             :                                            "org.freedesktop.UPower");
     221             : 
     222           0 :   UpdateTrackedDeviceSync();
     223             : 
     224             :   /*
     225             :    * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
     226             :    * If we do that, we would have to disconnect from those in StopListening.
     227             :    * It's not yet implemented because it requires testing hot plugging and
     228             :    * removal of a battery.
     229             :    */
     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             : }
     235             : 
     236             : void
     237           0 : UPowerClient::StopListening()
     238             : {
     239             :   // If mDBusConnection isn't initialized, that means we are not really listening.
     240           0 :   if (!mDBusConnection) {
     241           0 :     return;
     242             :   }
     243             : 
     244           0 :   dbus_connection_remove_filter(
     245             :       dbus_g_connection_get_connection(mDBusConnection),
     246           0 :       ConnectionSignalFilter, this);
     247             : 
     248           0 :   dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
     249           0 :                                  G_CALLBACK (DeviceChanged), this);
     250             : 
     251           0 :   g_free(mTrackedDevice);
     252           0 :   mTrackedDevice = nullptr;
     253             : 
     254           0 :   if (mTrackedDeviceProxy) {
     255           0 :     dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged",
     256           0 :                                    G_CALLBACK (PropertiesChanged), this);
     257             : 
     258           0 :     g_object_unref(mTrackedDeviceProxy);
     259           0 :     mTrackedDeviceProxy = nullptr;
     260             :   }
     261             : 
     262           0 :   g_object_unref(mUPowerProxy);
     263           0 :   mUPowerProxy = nullptr;
     264             : 
     265           0 :   dbus_g_connection_unref(mDBusConnection);
     266           0 :   mDBusConnection = nullptr;
     267             : 
     268             :   // 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             : }
     273             : 
     274             : void
     275           0 : UPowerClient::UpdateTrackedDeviceSync()
     276             : {
     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             : 
     282             :   // Reset the current tracked device:
     283           0 :   g_free(mTrackedDevice);
     284           0 :   mTrackedDevice = nullptr;
     285             : 
     286             :   // 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             : 
     291           0 :     g_object_unref(mTrackedDeviceProxy);
     292           0 :     mTrackedDeviceProxy = nullptr;
     293             :   }
     294             : 
     295             :   // If that fails, that likely means upower isn't installed.
     296           0 :   if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID,
     297             :                          typeGPtrArray, &devices, G_TYPE_INVALID)) {
     298           0 :     HAL_LOG("Error: %s\n", error->message);
     299           0 :     g_error_free(error);
     300           0 :     return;
     301             :   }
     302             : 
     303             :   /*
     304             :    * We are looking for the first device that is a battery.
     305             :    * TODO: we could try to combine more than one battery.
     306             :    */
     307           0 :   for (guint i=0; i<devices->len; ++i) {
     308           0 :     gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i));
     309             : 
     310           0 :     DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy,
     311             :                                                     "org.freedesktop.DBus.Properties",
     312           0 :                                                     devicePath);
     313             : 
     314           0 :     nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy));
     315             : 
     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             :     }
     322             : 
     323           0 :     g_object_unref(proxy);
     324           0 :     g_free(devicePath);
     325             :   }
     326             : 
     327           0 :   if (mTrackedDeviceProxy) {
     328           0 :     dbus_g_proxy_add_signal(mTrackedDeviceProxy, "PropertiesChanged",
     329             :                             G_TYPE_STRING,
     330             :                             dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
     331             :                                                 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             :   }
     336             : 
     337           0 :   g_ptr_array_free(devices, true);
     338             : }
     339             : 
     340             : /* static */ void
     341           0 : UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
     342             :                             UPowerClient* aListener)
     343             : {
     344           0 :   if (!aListener->mTrackedDevice) {
     345           0 :     return;
     346             :   }
     347             : 
     348             : #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           0 :     return;
     354             :   }
     355             : 
     356           0 :   aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
     357             : }
     358             : 
     359             : /* static */ void
     360           0 : UPowerClient::PropertiesChanged(DBusGProxy* aProxy, const gchar*, GHashTable*,
     361             :                                 char**, UPowerClient* aListener)
     362             : {
     363           0 :   aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
     364           0 : }
     365             : 
     366             : /* static */ DBusHandlerResult
     367           0 : UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection,
     368             :                                      DBusMessage* aMessage, void* aData)
     369             : {
     370           0 :   if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
     371           0 :     static_cast<UPowerClient*>(aData)->StopListening();
     372             :     // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
     373             :     // might be shared and some other filters might want to do something.
     374             :   }
     375             : 
     376           0 :   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
     377             : }
     378             : 
     379             : GHashTable*
     380           0 : UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy)
     381             : {
     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             :                          "org.freedesktop.UPower.Device", G_TYPE_INVALID,
     388             :                          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             :   }
     393             : 
     394           0 :   return hashTable;
     395             : }
     396             : 
     397             : /* static */ void
     398           0 : UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy,
     399             :                                           DBusGProxyCall* aCall, void* aData)
     400             : {
     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             :                              &hashTable, G_TYPE_INVALID)) {
     407           0 :     HAL_LOG("Error: %s\n", error->message);
     408           0 :     g_error_free(error);
     409             :   } 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             :   }
     416           0 : }
     417             : 
     418             : void
     419           0 : UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy)
     420             : {
     421             :   dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr,
     422             :                           nullptr, G_TYPE_STRING,
     423           0 :                           "org.freedesktop.UPower.Device", G_TYPE_INVALID);
     424           0 : }
     425             : 
     426             : void
     427           0 : UPowerClient::UpdateSavedInfo(GHashTable* aHashTable)
     428             : {
     429           0 :   bool isFull = false;
     430             : 
     431             :   /*
     432             :    * State values are confusing...
     433             :    * First of all, after looking at upower sources (0.9.13), it seems that
     434             :    * PendingDischarge and PendingCharge are not used.
     435             :    * In addition, FullyCharged and Empty states are not clear because we do not
     436             :    * know if the battery is actually charging or not. Those values come directly
     437             :    * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
     438             :    * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
     439             :    * related to the level, not to the charging state.
     440             :    * In this code, we are going to assume that Full means charging and Empty
     441             :    * means discharging because if that is not the case, the state should not
     442             :    * last a long time (actually, it should disappear at the following update).
     443             :    * It might be even very hard to see real cases where the state is Empty and
     444             :    * the battery is charging or the state is Full and the battery is discharging
     445             :    * given that plugging/unplugging the battery should have an impact on the
     446             :    * level.
     447             :    */
     448           0 :   switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) {
     449             :     case eState_Unknown:
     450           0 :       mCharging = kDefaultCharging;
     451           0 :       break;
     452             :     case eState_FullyCharged:
     453           0 :       isFull = true;
     454             :       MOZ_FALLTHROUGH;
     455             :     case eState_Charging:
     456             :     case eState_PendingCharge:
     457           0 :       mCharging = true;
     458           0 :       break;
     459             :     case eState_Discharging:
     460             :     case eState_Empty:
     461             :     case eState_PendingDischarge:
     462           0 :       mCharging = false;
     463           0 :       break;
     464             :   }
     465             : 
     466             :   /*
     467             :    * The battery level might be very close to 100% (like 99%) without
     468             :    * increasing. It seems that upower sets the battery state as 'full' in that
     469             :    * case so we should trust it and not even try to get the value.
     470             :    */
     471           0 :   if (isFull) {
     472           0 :     mLevel = 1.0;
     473             :   } else {
     474           0 :     mLevel = round(g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage"))))*0.01;
     475             :   }
     476             : 
     477           0 :   if (isFull) {
     478           0 :     mRemainingTime = 0;
     479             :   } 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             : 
     483           0 :     if (mRemainingTime == kUPowerUnknownRemainingTime) {
     484           0 :       mRemainingTime = kUnknownRemainingTime;
     485             :     }
     486             :   }
     487           0 : }
     488             : 
     489             : double
     490           0 : UPowerClient::GetLevel()
     491             : {
     492           0 :   return mLevel;
     493             : }
     494             : 
     495             : bool
     496           0 : UPowerClient::IsCharging()
     497             : {
     498           0 :   return mCharging;
     499             : }
     500             : 
     501             : double
     502           0 : UPowerClient::GetRemainingTime()
     503             : {
     504           0 :   return mRemainingTime;
     505             : }
     506             : 
     507             : } // namespace hal_impl
     508             : } // namespace mozilla

Generated by: LCOV version 1.13