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/apple/foundation_util.h" 12#include "base/apple/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. 21std::optional<SInt64> GetValueAsSInt64(CFDictionaryRef description, 22 CFStringRef key) { 23 CFNumberRef number_ref = 24 base::apple::GetValueFromDictionary<CFNumberRef>(description, key); 25 26 SInt64 value; 27 if (number_ref && CFNumberGetValue(number_ref, kCFNumberSInt64Type, &value)) 28 return value; 29 30 return std::nullopt; 31} 32 33std::optional<bool> GetValueAsBoolean(CFDictionaryRef description, 34 CFStringRef key) { 35 CFBooleanRef boolean = 36 base::apple::GetValueFromDictionary<CFBooleanRef>(description, key); 37 if (!boolean) 38 return std::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 std::optional<BatteryState>&)> callback) 51 override { 52 std::move(callback).Run(GetBatteryStateImpl()); 53 } 54 55 private: 56 std::optional<BatteryState> GetBatteryStateImpl(); 57}; 58 59std::unique_ptr<BatteryLevelProvider> BatteryLevelProvider::Create() { 60 return std::make_unique<BatteryLevelProviderMac>(); 61} 62 63std::optional<BatteryLevelProviderMac::BatteryState> 64BatteryLevelProviderMac::GetBatteryStateImpl() { 65 const base::mac::ScopedIOObject<io_service_t> service( 66 IOServiceGetMatchingService(kIOMasterPortDefault, 67 IOServiceMatching("IOPMPowerSource"))); 68 if (!service) { 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 apple::ScopedCFTypeRef<CFMutableDictionaryRef> dict; 75 kern_return_t result = 76 IORegistryEntryCreateCFProperties(service.get(), dict.InitializeInto(), 77 /*allocator=*/nullptr, /*options=*/0); 78 79 if (result != KERN_SUCCESS) { 80 // Failing to retrieve the dictionary is unexpected. 81 return std::nullopt; 82 } 83 84 std::optional<bool> battery_installed = 85 GetValueAsBoolean(dict.get(), CFSTR("BatteryInstalled")); 86 if (!battery_installed.has_value()) { 87 // Failing to access the BatteryInstalled property is unexpected. 88 return std::nullopt; 89 } 90 91 if (!battery_installed.value()) { 92 // BatteryInstalled == false means that there is no battery. 93 return MakeBatteryState(/* battery_details=*/{}); 94 } 95 96 std::optional<bool> external_connected = 97 GetValueAsBoolean(dict.get(), CFSTR("ExternalConnected")); 98 if (!external_connected.has_value()) { 99 // Failing to access the ExternalConnected property is unexpected. 100 return std::nullopt; 101 } 102 103 std::optional<SInt64> current_capacity = 104 GetValueAsSInt64(dict.get(), CFSTR("AppleRawCurrentCapacity")); 105 if (!current_capacity.has_value()) { 106 return std::nullopt; 107 } 108 109 std::optional<SInt64> max_capacity = 110 GetValueAsSInt64(dict.get(), CFSTR("AppleRawMaxCapacity")); 111 if (!max_capacity.has_value()) { 112 return std::nullopt; 113 } 114 115 std::optional<SInt64> voltage_mv = 116 GetValueAsSInt64(dict.get(), CFSTR(kIOPSVoltageKey)); 117 if (!voltage_mv.has_value()) { 118 return std::nullopt; 119 } 120 121 DCHECK_GE(*current_capacity, 0); 122 DCHECK_GE(*max_capacity, 0); 123 DCHECK_GE(*voltage_mv, 0); 124 125 return MakeBatteryState({BatteryDetails{ 126 .is_external_power_connected = external_connected.value(), 127 .current_capacity = static_cast<uint64_t>(current_capacity.value()), 128 .full_charged_capacity = static_cast<uint64_t>(max_capacity.value()), 129 .voltage_mv = static_cast<uint64_t>(voltage_mv.value()), 130 .charge_unit = BatteryLevelUnit::kMAh}}); 131} 132 133} // namespace base 134