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.h"
      6 
      7 #include <CoreFoundation/CoreFoundation.h>
      8 #include <IOKit/ps/IOPowerSources.h>
      9 #include <IOKit/ps/IOPSKeys.h>
     10 #include <vector>
     11 
     12 #include "base/mac/foundation_util.h"
     13 #include "base/mac/scoped_cftyperef.h"
     14 #include "base/memory/ref_counted.h"
     15 #include "base/metrics/histogram.h"
     16 #include "base/time/time.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "third_party/WebKit/public/platform/WebBatteryStatus.h"
     19 
     20 namespace content {
     21 
     22 namespace {
     23 
     24 typedef BatteryStatusService::BatteryUpdateCallback BatteryCallback;
     25 
     26 // Returns the value corresponding to |key| in the dictionary |description|.
     27 // Returns |default_value| if the dictionary does not contain |key|, the
     28 // corresponding value is NULL or it could not be converted to SInt64.
     29 SInt64 GetValueAsSInt64(CFDictionaryRef description,
     30                         CFStringRef key,
     31                         SInt64 default_value) {
     32   CFNumberRef number =
     33       base::mac::GetValueFromDictionary<CFNumberRef>(description, key);
     34   SInt64 value;
     35 
     36   if (number && CFNumberGetValue(number, kCFNumberSInt64Type, &value))
     37     return value;
     38 
     39   return default_value;
     40 }
     41 
     42 bool GetValueAsBoolean(CFDictionaryRef description,
     43                        CFStringRef key,
     44                        bool default_value) {
     45   CFBooleanRef boolean =
     46       base::mac::GetValueFromDictionary<CFBooleanRef>(description, key);
     47 
     48   return boolean ? CFBooleanGetValue(boolean) : default_value;
     49 }
     50 
     51 bool CFStringsAreEqual(CFStringRef string1, CFStringRef string2) {
     52   if (!string1 || !string2)
     53     return false;
     54   return CFStringCompare(string1, string2, 0) == kCFCompareEqualTo;
     55 }
     56 
     57 void UpdateNumberBatteriesHistogram(int count) {
     58   UMA_HISTOGRAM_CUSTOM_COUNTS(
     59       "BatteryStatus.NumberBatteriesMac", count, 1, 5, 6);
     60 }
     61 
     62 void FetchBatteryStatus(CFDictionaryRef description,
     63                         blink::WebBatteryStatus& status) {
     64   CFStringRef current_state =
     65       base::mac::GetValueFromDictionary<CFStringRef>(description,
     66           CFSTR(kIOPSPowerSourceStateKey));
     67 
     68   bool on_battery_power =
     69       CFStringsAreEqual(current_state, CFSTR(kIOPSBatteryPowerValue));
     70   bool is_charging =
     71       GetValueAsBoolean(description, CFSTR(kIOPSIsChargingKey), true);
     72   bool is_charged =
     73       GetValueAsBoolean(description, CFSTR(kIOPSIsChargedKey), false);
     74 
     75   status.charging = !on_battery_power || is_charging;
     76 
     77   SInt64 current_capacity =
     78       GetValueAsSInt64(description, CFSTR(kIOPSCurrentCapacityKey), -1);
     79   SInt64 max_capacity =
     80       GetValueAsSInt64(description, CFSTR(kIOPSMaxCapacityKey), -1);
     81 
     82   // Set level if it is available and valid. Otherwise leave the default value,
     83   // which is 1.
     84   if (current_capacity != -1 && max_capacity != -1 &&
     85       current_capacity <= max_capacity && max_capacity != 0) {
     86     status.level = current_capacity / static_cast<double>(max_capacity);
     87   }
     88 
     89   if (is_charging) {
     90     SInt64 charging_time =
     91         GetValueAsSInt64(description, CFSTR(kIOPSTimeToFullChargeKey), -1);
     92 
     93     // Battery is charging: set the charging time if it's available, otherwise
     94     // set to +infinity.
     95     status.chargingTime = charging_time != -1
     96         ? base::TimeDelta::FromMinutes(charging_time).InSeconds()
     97         : std::numeric_limits<double>::infinity();
     98   } else {
     99     // Battery is not charging.
    100     // Set chargingTime to +infinity if the battery is not charged. Otherwise
    101     // leave the default value, which is 0.
    102     if (!is_charged)
    103       status.chargingTime = std::numeric_limits<double>::infinity();
    104 
    105     // Set dischargingTime if it's available and valid, i.e. when on battery
    106     // power. Otherwise leave the default value, which is +infinity.
    107     if (on_battery_power) {
    108       SInt64 discharging_time =
    109           GetValueAsSInt64(description, CFSTR(kIOPSTimeToEmptyKey), -1);
    110       if (discharging_time != -1) {
    111         status.dischargingTime =
    112             base::TimeDelta::FromMinutes(discharging_time).InSeconds();
    113       }
    114     }
    115   }
    116 }
    117 
    118 std::vector<blink::WebBatteryStatus> GetInternalBatteriesStates() {
    119   std::vector<blink::WebBatteryStatus> internal_sources;
    120 
    121   base::ScopedCFTypeRef<CFTypeRef> info(IOPSCopyPowerSourcesInfo());
    122   base::ScopedCFTypeRef<CFArrayRef> power_sources_list(
    123       IOPSCopyPowerSourcesList(info));
    124   CFIndex count = CFArrayGetCount(power_sources_list);
    125 
    126   for (CFIndex i = 0; i < count; ++i) {
    127     CFDictionaryRef description = IOPSGetPowerSourceDescription(info,
    128         CFArrayGetValueAtIndex(power_sources_list, i));
    129 
    130     if (!description)
    131       continue;
    132 
    133     CFStringRef transport_type =
    134         base::mac::GetValueFromDictionary<CFStringRef>(description,
    135             CFSTR(kIOPSTransportTypeKey));
    136 
    137     bool internal_source =
    138         CFStringsAreEqual(transport_type, CFSTR(kIOPSInternalType));
    139     bool source_present =
    140         GetValueAsBoolean(description, CFSTR(kIOPSIsPresentKey), false);
    141 
    142     if (internal_source && source_present) {
    143       blink::WebBatteryStatus status;
    144       FetchBatteryStatus(description, status);
    145       internal_sources.push_back(status);
    146     }
    147   }
    148 
    149   return internal_sources;
    150 }
    151 
    152 void OnBatteryStatusChanged(const BatteryCallback& callback) {
    153   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    154 
    155   std::vector<blink::WebBatteryStatus> batteries(GetInternalBatteriesStates());
    156 
    157   if (batteries.empty()) {
    158     callback.Run(blink::WebBatteryStatus());
    159     return;
    160   }
    161 
    162   // TODO(timvolodine): implement the case when there are multiple internal
    163   // sources, e.g. when multiple batteries are present. Currently this will
    164   // fail a DCHECK.
    165   DCHECK(batteries.size() == 1);
    166   callback.Run(batteries.front());
    167 }
    168 
    169 class BatteryStatusObserver
    170     : public base::RefCountedThreadSafe<BatteryStatusObserver> {
    171  public:
    172   explicit BatteryStatusObserver(const BatteryCallback& callback)
    173       : callback_(callback) {}
    174 
    175   void Start() {
    176     // Need to start on a thread with UI-type message loop for
    177     // |notifier_run_loop_| to receive callbacks.
    178     if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    179       StartOnUI();
    180     } else {
    181       BrowserThread::PostTask(
    182           BrowserThread::UI,
    183           FROM_HERE,
    184           base::Bind(&BatteryStatusObserver::StartOnUI, this));
    185     }
    186   }
    187 
    188   void Stop() {
    189     if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    190       StopOnUI();
    191     } else {
    192       BrowserThread::PostTask(
    193           BrowserThread::UI,
    194           FROM_HERE,
    195           base::Bind(&BatteryStatusObserver::StopOnUI, this));
    196     }
    197   }
    198 
    199  private:
    200   friend class base::RefCountedThreadSafe<BatteryStatusObserver>;
    201   virtual ~BatteryStatusObserver() { DCHECK(!notifier_run_loop_source_); }
    202 
    203   void StartOnUI() {
    204     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    205 
    206     if (notifier_run_loop_source_)
    207       return;
    208 
    209     notifier_run_loop_source_.reset(
    210         IOPSNotificationCreateRunLoopSource(OnBatteryStatusChangedUI,
    211                                             static_cast<void*>(&callback_)));
    212     if (!notifier_run_loop_source_) {
    213       LOG(ERROR) << "Failed to create battery status notification run loop";
    214       // Make sure to execute to callback with the default values.
    215       callback_.Run(blink::WebBatteryStatus());
    216       return;
    217     }
    218 
    219     OnBatteryStatusChangedUI(static_cast<void*>(&callback_));
    220     CFRunLoopAddSource(CFRunLoopGetCurrent(), notifier_run_loop_source_,
    221                        kCFRunLoopDefaultMode);
    222     UpdateNumberBatteriesHistogram(GetInternalBatteriesStates().size());
    223   }
    224 
    225   void StopOnUI() {
    226     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    227 
    228     if (!notifier_run_loop_source_)
    229       return;
    230 
    231     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), notifier_run_loop_source_,
    232                           kCFRunLoopDefaultMode);
    233     notifier_run_loop_source_.reset();
    234   }
    235 
    236   static void OnBatteryStatusChangedUI(void* callback) {
    237     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    238     // Offload fetching of values and callback execution to the IO thread.
    239     BrowserThread::PostTask(
    240         BrowserThread::IO,
    241         FROM_HERE,
    242         base::Bind(&OnBatteryStatusChanged,
    243                    *static_cast<BatteryCallback*>(callback)));
    244   }
    245 
    246   BatteryCallback callback_;
    247   base::ScopedCFTypeRef<CFRunLoopSourceRef> notifier_run_loop_source_;
    248 
    249   DISALLOW_COPY_AND_ASSIGN(BatteryStatusObserver);
    250 };
    251 
    252 class BatteryStatusManagerMac : public BatteryStatusManager {
    253  public:
    254   explicit BatteryStatusManagerMac(const BatteryCallback& callback)
    255       : notifier_(new BatteryStatusObserver(callback)) {}
    256 
    257   virtual ~BatteryStatusManagerMac() {
    258     notifier_->Stop();
    259   }
    260 
    261   // BatteryStatusManager:
    262   virtual bool StartListeningBatteryChange() OVERRIDE {
    263     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    264     notifier_->Start();
    265     return true;
    266   }
    267 
    268   virtual void StopListeningBatteryChange() OVERRIDE {
    269     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    270     notifier_->Stop();
    271   }
    272 
    273  private:
    274   scoped_refptr<BatteryStatusObserver> notifier_;
    275 
    276   DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerMac);
    277 };
    278 
    279 } // end namespace
    280 
    281 // static
    282 scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
    283     const BatteryStatusService::BatteryUpdateCallback& callback) {
    284   return scoped_ptr<BatteryStatusManager>(
    285       new BatteryStatusManagerMac(callback));
    286 }
    287 
    288 }  // namespace content
    289