// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/power_monitor/battery_level_provider.h" #import #include #include #include "base/apple/foundation_util.h" #include "base/apple/scoped_cftyperef.h" #include "base/mac/scoped_ioobject.h" namespace base { namespace { // Returns the value corresponding to |key| in the dictionary |description|. // Returns |default_value| if the dictionary does not contain |key|, the // corresponding value is nullptr or it could not be converted to SInt64. std::optional GetValueAsSInt64(CFDictionaryRef description, CFStringRef key) { CFNumberRef number_ref = base::apple::GetValueFromDictionary(description, key); SInt64 value; if (number_ref && CFNumberGetValue(number_ref, kCFNumberSInt64Type, &value)) return value; return std::nullopt; } std::optional GetValueAsBoolean(CFDictionaryRef description, CFStringRef key) { CFBooleanRef boolean = base::apple::GetValueFromDictionary(description, key); if (!boolean) return std::nullopt; return CFBooleanGetValue(boolean); } } // namespace class BatteryLevelProviderMac : public BatteryLevelProvider { public: BatteryLevelProviderMac() = default; ~BatteryLevelProviderMac() override = default; void GetBatteryState( base::OnceCallback&)> callback) override { std::move(callback).Run(GetBatteryStateImpl()); } private: std::optional GetBatteryStateImpl(); }; std::unique_ptr BatteryLevelProvider::Create() { return std::make_unique(); } std::optional BatteryLevelProviderMac::GetBatteryStateImpl() { const base::mac::ScopedIOObject service( IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMPowerSource"))); if (!service) { // Macs without a battery don't necessarily provide the IOPMPowerSource // service (e.g. test bots). Don't report this as an error. return MakeBatteryState(/* battery_details=*/{}); } apple::ScopedCFTypeRef dict; kern_return_t result = IORegistryEntryCreateCFProperties(service.get(), dict.InitializeInto(), /*allocator=*/nullptr, /*options=*/0); if (result != KERN_SUCCESS) { // Failing to retrieve the dictionary is unexpected. return std::nullopt; } std::optional battery_installed = GetValueAsBoolean(dict.get(), CFSTR("BatteryInstalled")); if (!battery_installed.has_value()) { // Failing to access the BatteryInstalled property is unexpected. return std::nullopt; } if (!battery_installed.value()) { // BatteryInstalled == false means that there is no battery. return MakeBatteryState(/* battery_details=*/{}); } std::optional external_connected = GetValueAsBoolean(dict.get(), CFSTR("ExternalConnected")); if (!external_connected.has_value()) { // Failing to access the ExternalConnected property is unexpected. return std::nullopt; } std::optional current_capacity = GetValueAsSInt64(dict.get(), CFSTR("AppleRawCurrentCapacity")); if (!current_capacity.has_value()) { return std::nullopt; } std::optional max_capacity = GetValueAsSInt64(dict.get(), CFSTR("AppleRawMaxCapacity")); if (!max_capacity.has_value()) { return std::nullopt; } std::optional voltage_mv = GetValueAsSInt64(dict.get(), CFSTR(kIOPSVoltageKey)); if (!voltage_mv.has_value()) { return std::nullopt; } DCHECK_GE(*current_capacity, 0); DCHECK_GE(*max_capacity, 0); DCHECK_GE(*voltage_mv, 0); return MakeBatteryState({BatteryDetails{ .is_external_power_connected = external_connected.value(), .current_capacity = static_cast(current_capacity.value()), .full_charged_capacity = static_cast(max_capacity.value()), .voltage_mv = static_cast(voltage_mv.value()), .charge_unit = BatteryLevelUnit::kMAh}}); } } // namespace base