1// Copyright 2020 The Chromium Authors 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 "base/power_monitor/battery_level_provider.h" 6 7#import <Foundation/Foundation.h> 8#include <IOKit/IOKitLib.h> 9#include <IOKit/ps/IOPSKeys.h> 10 11#include "base/mac/foundation_util.h" 12#include "base/mac/scoped_cftyperef.h" 13#include "base/mac/scoped_ioobject.h" 14 15namespace base { 16namespace { 17 18// Returns the value corresponding to |key| in the dictionary |description|. 19// Returns |default_value| if the dictionary does not contain |key|, the 20// corresponding value is nullptr or it could not be converted to SInt64. 21absl::optional<SInt64> GetValueAsSInt64(CFDictionaryRef description, 22 CFStringRef key) { 23 CFNumberRef number_ref = 24 base::mac::GetValueFromDictionary<CFNumberRef>(description, key); 25 26 SInt64 value; 27 if (number_ref && CFNumberGetValue(number_ref, kCFNumberSInt64Type, &value)) 28 return value; 29 30 return absl::nullopt; 31} 32 33absl::optional<bool> GetValueAsBoolean(CFDictionaryRef description, 34 CFStringRef key) { 35 CFBooleanRef boolean = 36 base::mac::GetValueFromDictionary<CFBooleanRef>(description, key); 37 if (!boolean) 38 return absl::nullopt; 39 return CFBooleanGetValue(boolean); 40} 41 42} // namespace 43 44class BatteryLevelProviderMac : public BatteryLevelProvider { 45 public: 46 BatteryLevelProviderMac() = default; 47 ~BatteryLevelProviderMac() override = default; 48 49 void GetBatteryState( 50 base::OnceCallback<void(const absl::optional<BatteryState>&)> callback) 51 override { 52 std::move(callback).Run(GetBatteryStateImpl()); 53 } 54 55 private: 56 absl::optional<BatteryState> GetBatteryStateImpl(); 57}; 58 59std::unique_ptr<BatteryLevelProvider> BatteryLevelProvider::Create() { 60 return std::make_unique<BatteryLevelProviderMac>(); 61} 62 63absl::optional<BatteryLevelProviderMac::BatteryState> 64BatteryLevelProviderMac::GetBatteryStateImpl() { 65 const base::mac::ScopedIOObject<io_service_t> service( 66 IOServiceGetMatchingService(kIOMasterPortDefault, 67 IOServiceMatching("IOPMPowerSource"))); 68 if (service == IO_OBJECT_NULL) { 69 // Macs without a battery don't necessarily provide the IOPMPowerSource 70 // service (e.g. test bots). Don't report this as an error. 71 return MakeBatteryState(/* battery_details=*/{}); 72 } 73 74 base::ScopedCFTypeRef<CFMutableDictionaryRef> dict; 75 kern_return_t result = IORegistryEntryCreateCFProperties( 76 service.get(), dict.InitializeInto(), 0, 0); 77 78 if (result != KERN_SUCCESS) { 79 // Failing to retrieve the dictionary is unexpected. 80 return absl::nullopt; 81 } 82 83 absl::optional<bool> battery_installed = 84 GetValueAsBoolean(dict, CFSTR("BatteryInstalled")); 85 if (!battery_installed.has_value()) { 86 // Failing to access the BatteryInstalled property is unexpected. 87 return absl::nullopt; 88 } 89 90 if (!battery_installed.value()) { 91 // BatteryInstalled == false means that there is no battery. 92 return MakeBatteryState(/* battery_details=*/{}); 93 } 94 95 absl::optional<bool> external_connected = 96 GetValueAsBoolean(dict, CFSTR("ExternalConnected")); 97 if (!external_connected.has_value()) { 98 // Failing to access the ExternalConnected property is unexpected. 99 return absl::nullopt; 100 } 101 102 CFStringRef capacity_key; 103 CFStringRef max_capacity_key; 104 105 // Use the correct capacity keys depending on macOS version. 106 if (@available(macOS 10.14.0, *)) { 107 capacity_key = CFSTR("AppleRawCurrentCapacity"); 108 max_capacity_key = CFSTR("AppleRawMaxCapacity"); 109 } else { 110 capacity_key = CFSTR("CurrentCapacity"); 111 max_capacity_key = CFSTR("RawMaxCapacity"); 112 } 113 114 absl::optional<SInt64> current_capacity = 115 GetValueAsSInt64(dict, capacity_key); 116 if (!current_capacity.has_value()) { 117 return absl::nullopt; 118 } 119 120 absl::optional<SInt64> max_capacity = 121 GetValueAsSInt64(dict, max_capacity_key); 122 if (!max_capacity.has_value()) { 123 return absl::nullopt; 124 } 125 126 absl::optional<SInt64> voltage_mv = 127 GetValueAsSInt64(dict, CFSTR(kIOPSVoltageKey)); 128 if (!voltage_mv.has_value()) { 129 return absl::nullopt; 130 } 131 132 DCHECK_GE(*current_capacity, 0); 133 DCHECK_GE(*max_capacity, 0); 134 DCHECK_GE(*voltage_mv, 0); 135 136 return MakeBatteryState({BatteryDetails{ 137 .is_external_power_connected = external_connected.value(), 138 .current_capacity = static_cast<uint64_t>(current_capacity.value()), 139 .full_charged_capacity = static_cast<uint64_t>(max_capacity.value()), 140 .voltage_mv = static_cast<uint64_t>(voltage_mv.value()), 141 .charge_unit = BatteryLevelUnit::kMAh}}); 142} 143 144} // namespace base 145