Home | History | Annotate | Download | only in battery_status
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/battery_status/battery_status_manager_linux.h"
      6 
      7 #include "base/macros.h"
      8 #include "base/metrics/histogram.h"
      9 #include "base/threading/thread.h"
     10 #include "base/values.h"
     11 #include "content/browser/battery_status/battery_status_manager.h"
     12 #include "content/public/browser/browser_thread.h"
     13 #include "dbus/bus.h"
     14 #include "dbus/message.h"
     15 #include "dbus/object_path.h"
     16 #include "dbus/object_proxy.h"
     17 #include "dbus/property.h"
     18 #include "dbus/values_util.h"
     19 
     20 namespace content {
     21 
     22 namespace {
     23 
     24 const char kUPowerServiceName[] = "org.freedesktop.UPower";
     25 const char kUPowerDeviceName[] = "org.freedesktop.UPower.Device";
     26 const char kUPowerPath[] = "/org/freedesktop/UPower";
     27 const char kUPowerDeviceSignalChanged[] = "Changed";
     28 const char kUPowerEnumerateDevices[] = "EnumerateDevices";
     29 const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier";
     30 
     31 // UPowerDeviceType reflects the possible UPower.Device.Type values,
     32 // see upower.freedesktop.org/docs/Device.html#Device:Type.
     33 enum UPowerDeviceType {
     34   UPOWER_DEVICE_TYPE_UNKNOWN = 0,
     35   UPOWER_DEVICE_TYPE_LINE_POWER = 1,
     36   UPOWER_DEVICE_TYPE_BATTERY = 2,
     37   UPOWER_DEVICE_TYPE_UPS = 3,
     38   UPOWER_DEVICE_TYPE_MONITOR = 4,
     39   UPOWER_DEVICE_TYPE_MOUSE = 5,
     40   UPOWER_DEVICE_TYPE_KEYBOARD = 6,
     41   UPOWER_DEVICE_TYPE_PDA = 7,
     42   UPOWER_DEVICE_TYPE_PHONE = 8,
     43 };
     44 
     45 typedef std::vector<dbus::ObjectPath> PathsVector;
     46 
     47 double GetPropertyAsDouble(const base::DictionaryValue& dictionary,
     48                            const std::string& property_name,
     49                            double default_value) {
     50   double value = default_value;
     51   return dictionary.GetDouble(property_name, &value) ? value : default_value;
     52 }
     53 
     54 bool GetPropertyAsBoolean(const base::DictionaryValue& dictionary,
     55                           const std::string& property_name,
     56                           bool default_value) {
     57   bool value = default_value;
     58   return dictionary.GetBoolean(property_name, &value) ? value : default_value;
     59 }
     60 
     61 scoped_ptr<base::DictionaryValue> GetPropertiesAsDictionary(
     62     dbus::ObjectProxy* proxy) {
     63   dbus::MethodCall method_call(dbus::kPropertiesInterface,
     64                                dbus::kPropertiesGetAll);
     65   dbus::MessageWriter builder(&method_call);
     66   builder.AppendString(kUPowerDeviceName);
     67 
     68   scoped_ptr<dbus::Response> response(
     69       proxy->CallMethodAndBlock(&method_call,
     70                                 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
     71   if (response) {
     72     dbus::MessageReader reader(response.get());
     73     scoped_ptr<base::Value> value(dbus::PopDataAsValue(&reader));
     74     base::DictionaryValue* dictionary_value = NULL;
     75     if (value && value->GetAsDictionary(&dictionary_value)) {
     76       ignore_result(value.release());
     77       return scoped_ptr<base::DictionaryValue>(dictionary_value);
     78     }
     79   }
     80   return scoped_ptr<base::DictionaryValue>();
     81 }
     82 
     83 scoped_ptr<PathsVector> GetPowerSourcesPaths(dbus::ObjectProxy* proxy) {
     84   scoped_ptr<PathsVector> paths(new PathsVector());
     85   if (!proxy)
     86     return paths.Pass();
     87 
     88   dbus::MethodCall method_call(kUPowerServiceName, kUPowerEnumerateDevices);
     89   scoped_ptr<dbus::Response> response(
     90       proxy->CallMethodAndBlock(&method_call,
     91                                 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
     92 
     93   if (response) {
     94     dbus::MessageReader reader(response.get());
     95     reader.PopArrayOfObjectPaths(paths.get());
     96   }
     97   return paths.Pass();;
     98 }
     99 
    100 void UpdateNumberBatteriesHistogram(int count) {
    101   UMA_HISTOGRAM_CUSTOM_COUNTS(
    102       "BatteryStatus.NumberBatteriesLinux", count, 1, 5, 6);
    103 }
    104 
    105 // Class that represents a dedicated thread which communicates with DBus to
    106 // obtain battery information and receives battery change notifications.
    107 class BatteryStatusNotificationThread : public base::Thread {
    108  public:
    109   BatteryStatusNotificationThread(
    110       const BatteryStatusService::BatteryUpdateCallback& callback)
    111       : base::Thread(kBatteryNotifierThreadName),
    112         callback_(callback),
    113         battery_proxy_(NULL) {}
    114 
    115   virtual ~BatteryStatusNotificationThread() {
    116     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    117 
    118     // Make sure to shutdown the dbus connection if it is still open in the very
    119     // end. It needs to happen on the BatteryStatusNotificationThread.
    120     message_loop()->PostTask(
    121         FROM_HERE,
    122         base::Bind(&BatteryStatusNotificationThread::ShutdownDBusConnection,
    123                    base::Unretained(this)));
    124 
    125     // Drain the message queue of the BatteryStatusNotificationThread and stop.
    126     Stop();
    127   }
    128 
    129   void StartListening() {
    130     DCHECK(OnWatcherThread());
    131 
    132     if (system_bus_.get())
    133       return;
    134 
    135     InitDBus();
    136     dbus::ObjectProxy* power_proxy =
    137         system_bus_->GetObjectProxy(kUPowerServiceName,
    138                                     dbus::ObjectPath(kUPowerPath));
    139     scoped_ptr<PathsVector> device_paths = GetPowerSourcesPaths(power_proxy);
    140     int num_batteries = 0;
    141 
    142     for (size_t i = 0; i < device_paths->size(); ++i) {
    143       const dbus::ObjectPath& device_path = device_paths->at(i);
    144       dbus::ObjectProxy* device_proxy = system_bus_->GetObjectProxy(
    145           kUPowerServiceName, device_path);
    146       scoped_ptr<base::DictionaryValue> dictionary =
    147           GetPropertiesAsDictionary(device_proxy);
    148 
    149       if (!dictionary)
    150         continue;
    151 
    152       bool is_present = GetPropertyAsBoolean(*dictionary, "IsPresent", false);
    153       uint32 type = static_cast<uint32>(
    154           GetPropertyAsDouble(*dictionary, "Type", UPOWER_DEVICE_TYPE_UNKNOWN));
    155 
    156       if (!is_present || type != UPOWER_DEVICE_TYPE_BATTERY) {
    157         system_bus_->RemoveObjectProxy(kUPowerServiceName,
    158                                        device_path,
    159                                        base::Bind(&base::DoNothing));
    160         continue;
    161       }
    162 
    163       if (battery_proxy_) {
    164         // TODO(timvolodine): add support for multiple batteries. Currently we
    165         // only collect information from the first battery we encounter
    166         // (crbug.com/400780).
    167         LOG(WARNING) << "multiple batteries found, "
    168                      << "using status data of the first battery only.";
    169       } else {
    170         battery_proxy_ = device_proxy;
    171       }
    172       num_batteries++;
    173     }
    174 
    175     UpdateNumberBatteriesHistogram(num_batteries);
    176 
    177     if (!battery_proxy_) {
    178       callback_.Run(blink::WebBatteryStatus());
    179       return;
    180     }
    181 
    182     battery_proxy_->ConnectToSignal(
    183         kUPowerDeviceName,
    184         kUPowerDeviceSignalChanged,
    185         base::Bind(&BatteryStatusNotificationThread::BatteryChanged,
    186                    base::Unretained(this)),
    187         base::Bind(&BatteryStatusNotificationThread::OnSignalConnected,
    188                    base::Unretained(this)));
    189   }
    190 
    191   void StopListening() {
    192     DCHECK(OnWatcherThread());
    193     ShutdownDBusConnection();
    194   }
    195 
    196  private:
    197   bool OnWatcherThread() {
    198     return task_runner()->BelongsToCurrentThread();
    199   }
    200 
    201   void InitDBus() {
    202     DCHECK(OnWatcherThread());
    203 
    204     dbus::Bus::Options options;
    205     options.bus_type = dbus::Bus::SYSTEM;
    206     options.connection_type = dbus::Bus::PRIVATE;
    207     system_bus_ = new dbus::Bus(options);
    208   }
    209 
    210   void ShutdownDBusConnection() {
    211     DCHECK(OnWatcherThread());
    212 
    213     if (!system_bus_.get())
    214       return;
    215 
    216     // Shutdown DBus connection later because there may be pending tasks on
    217     // this thread.
    218     message_loop()->PostTask(FROM_HERE,
    219                              base::Bind(&dbus::Bus::ShutdownAndBlock,
    220                                         system_bus_));
    221     system_bus_ = NULL;
    222     battery_proxy_ = NULL;
    223   }
    224 
    225   void OnSignalConnected(const std::string& interface_name,
    226                          const std::string& signal_name,
    227                          bool success) {
    228     DCHECK(OnWatcherThread());
    229 
    230     if (interface_name != kUPowerDeviceName ||
    231         signal_name != kUPowerDeviceSignalChanged) {
    232       return;
    233     }
    234 
    235     if (!system_bus_.get())
    236       return;
    237 
    238     if (success) {
    239       BatteryChanged(NULL);
    240     } else {
    241       // Failed to register for "Changed" signal, execute callback with the
    242       // default values.
    243       callback_.Run(blink::WebBatteryStatus());
    244     }
    245   }
    246 
    247   void BatteryChanged(dbus::Signal* signal /* unsused */) {
    248     DCHECK(OnWatcherThread());
    249 
    250     if (!system_bus_.get())
    251       return;
    252 
    253     scoped_ptr<base::DictionaryValue> dictionary =
    254         GetPropertiesAsDictionary(battery_proxy_);
    255     if (dictionary)
    256       callback_.Run(ComputeWebBatteryStatus(*dictionary));
    257     else
    258       callback_.Run(blink::WebBatteryStatus());
    259   }
    260 
    261   BatteryStatusService::BatteryUpdateCallback callback_;
    262   scoped_refptr<dbus::Bus> system_bus_;
    263   dbus::ObjectProxy* battery_proxy_;  // owned by the bus
    264 
    265   DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread);
    266 };
    267 
    268 // Runs on IO thread and creates a notification thread and delegates Start/Stop
    269 // calls to it.
    270 class BatteryStatusManagerLinux : public BatteryStatusManager {
    271  public:
    272   explicit BatteryStatusManagerLinux(
    273       const BatteryStatusService::BatteryUpdateCallback& callback)
    274       : callback_(callback) {}
    275 
    276   virtual ~BatteryStatusManagerLinux() {}
    277 
    278  private:
    279   // BatteryStatusManager:
    280   virtual bool StartListeningBatteryChange() OVERRIDE {
    281     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    282 
    283     if (!StartNotifierThreadIfNecessary())
    284       return false;
    285 
    286     notifier_thread_->message_loop()->PostTask(
    287         FROM_HERE,
    288         base::Bind(&BatteryStatusNotificationThread::StartListening,
    289                    base::Unretained(notifier_thread_.get())));
    290     return true;
    291   }
    292 
    293   virtual void StopListeningBatteryChange() OVERRIDE {
    294     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    295 
    296     if (!notifier_thread_)
    297       return;
    298 
    299     notifier_thread_->message_loop()->PostTask(
    300         FROM_HERE,
    301         base::Bind(&BatteryStatusNotificationThread::StopListening,
    302                    base::Unretained(notifier_thread_.get())));
    303   }
    304 
    305   // Starts the notifier thread if not already started and returns true on
    306   // success.
    307   bool StartNotifierThreadIfNecessary() {
    308     if (notifier_thread_)
    309       return true;
    310 
    311     base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
    312     notifier_thread_.reset(new BatteryStatusNotificationThread(callback_));
    313     if (!notifier_thread_->StartWithOptions(thread_options)) {
    314       notifier_thread_.reset();
    315       LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName
    316                  << " thread";
    317       return false;
    318     }
    319     return true;
    320   }
    321 
    322   BatteryStatusService::BatteryUpdateCallback callback_;
    323   scoped_ptr<BatteryStatusNotificationThread> notifier_thread_;
    324 
    325   DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux);
    326 };
    327 
    328 }  // namespace
    329 
    330 blink::WebBatteryStatus ComputeWebBatteryStatus(
    331     const base::DictionaryValue& dictionary) {
    332   blink::WebBatteryStatus status;
    333   if (!dictionary.HasKey("State"))
    334     return status;
    335 
    336   uint32 state = static_cast<uint32>(
    337       GetPropertyAsDouble(dictionary, "State", UPOWER_DEVICE_STATE_UNKNOWN));
    338   status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING &&
    339                     state != UPOWER_DEVICE_STATE_EMPTY;
    340   double percentage = GetPropertyAsDouble(dictionary, "Percentage", 100);
    341   // Convert percentage to a value between 0 and 1 with 2 digits of precision.
    342   // This is to bring it in line with other platforms like Mac and Android where
    343   // we report level with 1% granularity. It also serves the purpose of reducing
    344   // the possibility of fingerprinting and triggers less level change events on
    345   // the blink side.
    346   // TODO(timvolodine): consider moving this rounding to the blink side.
    347   status.level = round(percentage) / 100.f;
    348 
    349   switch (state) {
    350     case UPOWER_DEVICE_STATE_CHARGING : {
    351       double time_to_full = GetPropertyAsDouble(dictionary, "TimeToFull", 0);
    352       status.chargingTime =
    353           (time_to_full > 0) ? time_to_full
    354                              : std::numeric_limits<double>::infinity();
    355       break;
    356     }
    357     case UPOWER_DEVICE_STATE_DISCHARGING : {
    358       double time_to_empty = GetPropertyAsDouble(dictionary, "TimeToEmpty", 0);
    359       // Set dischargingTime if it's available. Otherwise leave the default
    360       // value which is +infinity.
    361       if (time_to_empty > 0)
    362         status.dischargingTime = time_to_empty;
    363       status.chargingTime = std::numeric_limits<double>::infinity();
    364       break;
    365     }
    366     case UPOWER_DEVICE_STATE_FULL : {
    367       break;
    368     }
    369     default: {
    370       status.chargingTime = std::numeric_limits<double>::infinity();
    371     }
    372   }
    373   return status;
    374 }
    375 
    376 // static
    377 scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
    378     const BatteryStatusService::BatteryUpdateCallback& callback) {
    379   return scoped_ptr<BatteryStatusManager>(
    380       new BatteryStatusManagerLinux(callback));
    381 }
    382 
    383 }  // namespace content
    384