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 "device/battery/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/scoped_ptr.h"
15 #include "base/metrics/histogram.h"
16 #include "base/time/time.h"
22 typedef BatteryStatusService::BatteryUpdateCallback BatteryCallback
;
24 // Returns the value corresponding to |key| in the dictionary |description|.
25 // Returns |default_value| if the dictionary does not contain |key|, the
26 // corresponding value is NULL or it could not be converted to SInt64.
27 SInt64
GetValueAsSInt64(CFDictionaryRef description
,
29 SInt64 default_value
) {
31 base::mac::GetValueFromDictionary
<CFNumberRef
>(description
, key
);
34 if (number
&& CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
))
40 bool GetValueAsBoolean(CFDictionaryRef description
,
43 CFBooleanRef boolean
=
44 base::mac::GetValueFromDictionary
<CFBooleanRef
>(description
, key
);
46 return boolean
? CFBooleanGetValue(boolean
) : default_value
;
49 bool CFStringsAreEqual(CFStringRef string1
, CFStringRef string2
) {
50 if (!string1
|| !string2
)
52 return CFStringCompare(string1
, string2
, 0) == kCFCompareEqualTo
;
55 void UpdateNumberBatteriesHistogram(int count
) {
56 UMA_HISTOGRAM_CUSTOM_COUNTS(
57 "BatteryStatus.NumberBatteriesMac", count
, 1, 5, 6);
60 void FetchBatteryStatus(CFDictionaryRef description
, BatteryStatus
* status
) {
61 CFStringRef current_state
=
62 base::mac::GetValueFromDictionary
<CFStringRef
>(description
,
63 CFSTR(kIOPSPowerSourceStateKey
));
65 bool on_battery_power
=
66 CFStringsAreEqual(current_state
, CFSTR(kIOPSBatteryPowerValue
));
68 GetValueAsBoolean(description
, CFSTR(kIOPSIsChargingKey
), true);
70 GetValueAsBoolean(description
, CFSTR(kIOPSIsChargedKey
), false);
72 status
->charging
= !on_battery_power
|| is_charging
;
74 SInt64 current_capacity
=
75 GetValueAsSInt64(description
, CFSTR(kIOPSCurrentCapacityKey
), -1);
77 GetValueAsSInt64(description
, CFSTR(kIOPSMaxCapacityKey
), -1);
79 // Set level if it is available and valid. Otherwise leave the default value,
81 if (current_capacity
!= -1 && max_capacity
!= -1 &&
82 current_capacity
<= max_capacity
&& max_capacity
!= 0) {
83 status
->level
= current_capacity
/ static_cast<double>(max_capacity
);
87 SInt64 charging_time
=
88 GetValueAsSInt64(description
, CFSTR(kIOPSTimeToFullChargeKey
), -1);
90 // Battery is charging: set the charging time if it's available, otherwise
92 status
->charging_time
= charging_time
!= -1
93 ? base::TimeDelta::FromMinutes(charging_time
).InSeconds()
94 : std::numeric_limits
<double>::infinity();
96 // Battery is not charging.
97 // Set chargingTime to +infinity if the battery is not charged. Otherwise
98 // leave the default value, which is 0.
100 status
->charging_time
= std::numeric_limits
<double>::infinity();
102 // Set dischargingTime if it's available and valid, i.e. when on battery
103 // power. Otherwise leave the default value, which is +infinity.
104 if (on_battery_power
) {
105 SInt64 discharging_time
=
106 GetValueAsSInt64(description
, CFSTR(kIOPSTimeToEmptyKey
), -1);
107 if (discharging_time
!= -1) {
108 status
->discharging_time
=
109 base::TimeDelta::FromMinutes(discharging_time
).InSeconds();
115 std::vector
<BatteryStatus
> GetInternalBatteriesStates() {
116 std::vector
<BatteryStatus
> internal_sources
;
118 base::ScopedCFTypeRef
<CFTypeRef
> info(IOPSCopyPowerSourcesInfo());
119 base::ScopedCFTypeRef
<CFArrayRef
> power_sources_list(
120 IOPSCopyPowerSourcesList(info
));
121 CFIndex count
= CFArrayGetCount(power_sources_list
);
123 for (CFIndex i
= 0; i
< count
; ++i
) {
124 CFDictionaryRef description
= IOPSGetPowerSourceDescription(info
,
125 CFArrayGetValueAtIndex(power_sources_list
, i
));
130 CFStringRef transport_type
=
131 base::mac::GetValueFromDictionary
<CFStringRef
>(description
,
132 CFSTR(kIOPSTransportTypeKey
));
134 bool internal_source
=
135 CFStringsAreEqual(transport_type
, CFSTR(kIOPSInternalType
));
136 bool source_present
=
137 GetValueAsBoolean(description
, CFSTR(kIOPSIsPresentKey
), false);
139 if (internal_source
&& source_present
) {
140 BatteryStatus status
;
141 FetchBatteryStatus(description
, &status
);
142 internal_sources
.push_back(status
);
146 return internal_sources
;
149 void OnBatteryStatusChanged(const BatteryCallback
& callback
) {
150 std::vector
<BatteryStatus
> batteries(GetInternalBatteriesStates());
152 if (batteries
.empty()) {
153 callback
.Run(BatteryStatus());
157 // TODO(timvolodine): implement the case when there are multiple internal
158 // sources, e.g. when multiple batteries are present. Currently this will
160 DCHECK(batteries
.size() == 1);
161 callback
.Run(batteries
.front());
164 class BatteryStatusObserver
{
166 explicit BatteryStatusObserver(const BatteryCallback
& callback
)
167 : callback_(callback
) {}
169 ~BatteryStatusObserver() { DCHECK(!notifier_run_loop_source_
); }
172 if (notifier_run_loop_source_
)
175 notifier_run_loop_source_
.reset(
176 IOPSNotificationCreateRunLoopSource(CallOnBatteryStatusChanged
,
177 static_cast<void*>(&callback_
)));
178 if (!notifier_run_loop_source_
) {
179 LOG(ERROR
) << "Failed to create battery status notification run loop";
180 // Make sure to execute to callback with the default values.
181 callback_
.Run(BatteryStatus());
185 CallOnBatteryStatusChanged(static_cast<void*>(&callback_
));
186 CFRunLoopAddSource(CFRunLoopGetCurrent(), notifier_run_loop_source_
,
187 kCFRunLoopDefaultMode
);
188 UpdateNumberBatteriesHistogram(GetInternalBatteriesStates().size());
192 if (!notifier_run_loop_source_
)
195 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), notifier_run_loop_source_
,
196 kCFRunLoopDefaultMode
);
197 notifier_run_loop_source_
.reset();
201 static void CallOnBatteryStatusChanged(void* callback
) {
202 OnBatteryStatusChanged(*static_cast<BatteryCallback
*>(callback
));
205 BatteryCallback callback_
;
206 base::ScopedCFTypeRef
<CFRunLoopSourceRef
> notifier_run_loop_source_
;
208 DISALLOW_COPY_AND_ASSIGN(BatteryStatusObserver
);
211 class BatteryStatusManagerMac
: public BatteryStatusManager
{
213 explicit BatteryStatusManagerMac(const BatteryCallback
& callback
)
214 : notifier_(new BatteryStatusObserver(callback
)) {}
216 ~BatteryStatusManagerMac() override
{ notifier_
->Stop(); }
218 // BatteryStatusManager:
219 bool StartListeningBatteryChange() override
{
224 void StopListeningBatteryChange() override
{
229 scoped_ptr
<BatteryStatusObserver
> notifier_
;
231 DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerMac
);
237 scoped_ptr
<BatteryStatusManager
> BatteryStatusManager::Create(
238 const BatteryStatusService::BatteryUpdateCallback
& callback
) {
239 return scoped_ptr
<BatteryStatusManager
>(
240 new BatteryStatusManagerMac(callback
));
243 } // namespace device