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