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.
5 #include "content/browser/battery_status/battery_status_manager.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <IOKit/ps/IOPowerSources.h>
9 #include <IOKit/ps/IOPSKeys.h>
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"
24 typedef BatteryStatusService::BatteryUpdateCallback BatteryCallback;
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,
31 SInt64 default_value) {
33 base::mac::GetValueFromDictionary<CFNumberRef>(description, key);
36 if (number && CFNumberGetValue(number, kCFNumberSInt64Type, &value))
42 bool GetValueAsBoolean(CFDictionaryRef description,
45 CFBooleanRef boolean =
46 base::mac::GetValueFromDictionary<CFBooleanRef>(description, key);
48 return boolean ? CFBooleanGetValue(boolean) : default_value;
51 bool CFStringsAreEqual(CFStringRef string1, CFStringRef string2) {
52 if (!string1 || !string2)
54 return CFStringCompare(string1, string2, 0) == kCFCompareEqualTo;
57 void UpdateNumberBatteriesHistogram(int count) {
58 UMA_HISTOGRAM_CUSTOM_COUNTS(
59 "BatteryStatus.NumberBatteriesMac", count, 1, 5, 6);
62 void FetchBatteryStatus(CFDictionaryRef description,
63 blink::WebBatteryStatus& status) {
64 CFStringRef current_state =
65 base::mac::GetValueFromDictionary<CFStringRef>(description,
66 CFSTR(kIOPSPowerSourceStateKey));
68 bool on_battery_power =
69 CFStringsAreEqual(current_state, CFSTR(kIOPSBatteryPowerValue));
71 GetValueAsBoolean(description, CFSTR(kIOPSIsChargingKey), true);
73 GetValueAsBoolean(description, CFSTR(kIOPSIsChargedKey), false);
75 status.charging = !on_battery_power || is_charging;
77 SInt64 current_capacity =
78 GetValueAsSInt64(description, CFSTR(kIOPSCurrentCapacityKey), -1);
80 GetValueAsSInt64(description, CFSTR(kIOPSMaxCapacityKey), -1);
82 // Set level if it is available and valid. Otherwise leave the default value,
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);
90 SInt64 charging_time =
91 GetValueAsSInt64(description, CFSTR(kIOPSTimeToFullChargeKey), -1);
93 // Battery is charging: set the charging time if it's available, otherwise
95 status.chargingTime = charging_time != -1
96 ? base::TimeDelta::FromMinutes(charging_time).InSeconds()
97 : std::numeric_limits<double>::infinity();
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.
103 status.chargingTime = std::numeric_limits<double>::infinity();
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();
118 std::vector<blink::WebBatteryStatus> GetInternalBatteriesStates() {
119 std::vector<blink::WebBatteryStatus> internal_sources;
121 base::ScopedCFTypeRef<CFTypeRef> info(IOPSCopyPowerSourcesInfo());
122 base::ScopedCFTypeRef<CFArrayRef> power_sources_list(
123 IOPSCopyPowerSourcesList(info));
124 CFIndex count = CFArrayGetCount(power_sources_list);
126 for (CFIndex i = 0; i < count; ++i) {
127 CFDictionaryRef description = IOPSGetPowerSourceDescription(info,
128 CFArrayGetValueAtIndex(power_sources_list, i));
133 CFStringRef transport_type =
134 base::mac::GetValueFromDictionary<CFStringRef>(description,
135 CFSTR(kIOPSTransportTypeKey));
137 bool internal_source =
138 CFStringsAreEqual(transport_type, CFSTR(kIOPSInternalType));
139 bool source_present =
140 GetValueAsBoolean(description, CFSTR(kIOPSIsPresentKey), false);
142 if (internal_source && source_present) {
143 blink::WebBatteryStatus status;
144 FetchBatteryStatus(description, status);
145 internal_sources.push_back(status);
149 return internal_sources;
152 void OnBatteryStatusChanged(const BatteryCallback& callback) {
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
155 std::vector<blink::WebBatteryStatus> batteries(GetInternalBatteriesStates());
157 if (batteries.empty()) {
158 callback.Run(blink::WebBatteryStatus());
162 // TODO(timvolodine): implement the case when there are multiple internal
163 // sources, e.g. when multiple batteries are present. Currently this will
165 DCHECK(batteries.size() == 1);
166 callback.Run(batteries.front());
169 class BatteryStatusObserver
170 : public base::RefCountedThreadSafe<BatteryStatusObserver> {
172 explicit BatteryStatusObserver(const BatteryCallback& callback)
173 : callback_(callback) {}
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)) {
181 BrowserThread::PostTask(
184 base::Bind(&BatteryStatusObserver::StartOnUI, this));
189 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
192 BrowserThread::PostTask(
195 base::Bind(&BatteryStatusObserver::StopOnUI, this));
200 friend class base::RefCountedThreadSafe<BatteryStatusObserver>;
201 virtual ~BatteryStatusObserver() { DCHECK(!notifier_run_loop_source_); }
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 if (notifier_run_loop_source_)
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());
219 OnBatteryStatusChangedUI(static_cast<void*>(&callback_));
220 CFRunLoopAddSource(CFRunLoopGetCurrent(), notifier_run_loop_source_,
221 kCFRunLoopDefaultMode);
222 UpdateNumberBatteriesHistogram(GetInternalBatteriesStates().size());
226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
228 if (!notifier_run_loop_source_)
231 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), notifier_run_loop_source_,
232 kCFRunLoopDefaultMode);
233 notifier_run_loop_source_.reset();
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(
242 base::Bind(&OnBatteryStatusChanged,
243 *static_cast<BatteryCallback*>(callback)));
246 BatteryCallback callback_;
247 base::ScopedCFTypeRef<CFRunLoopSourceRef> notifier_run_loop_source_;
249 DISALLOW_COPY_AND_ASSIGN(BatteryStatusObserver);
252 class BatteryStatusManagerMac : public BatteryStatusManager {
254 explicit BatteryStatusManagerMac(const BatteryCallback& callback)
255 : notifier_(new BatteryStatusObserver(callback)) {}
257 virtual ~BatteryStatusManagerMac() {
261 // BatteryStatusManager:
262 virtual bool StartListeningBatteryChange() OVERRIDE {
263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
268 virtual void StopListeningBatteryChange() OVERRIDE {
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
274 scoped_refptr<BatteryStatusObserver> notifier_;
276 DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerMac);
282 scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
283 const BatteryStatusService::BatteryUpdateCallback& callback) {
284 return scoped_ptr<BatteryStatusManager>(
285 new BatteryStatusManagerMac(callback));
288 } // namespace content