// 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 <Foundation/Foundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/ps/IOPSKeys.h>
#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<SInt64> GetValueAsSInt64(CFDictionaryRef description,
CFStringRef key) {
CFNumberRef number_ref =
base::apple::GetValueFromDictionary<CFNumberRef>(description, key);
SInt64 value;
if (number_ref && CFNumberGetValue(number_ref, kCFNumberSInt64Type, &value))
return value;
return std::nullopt;
}
std::optional<bool> GetValueAsBoolean(CFDictionaryRef description,
CFStringRef key) {
CFBooleanRef boolean =
base::apple::GetValueFromDictionary<CFBooleanRef>(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<void(const std::optional<BatteryState>&)> callback)
override {
std::move(callback).Run(GetBatteryStateImpl());
}
private:
std::optional<BatteryState> GetBatteryStateImpl();
};
std::unique_ptr<BatteryLevelProvider> BatteryLevelProvider::Create() {
return std::make_unique<BatteryLevelProviderMac>();
}
std::optional<BatteryLevelProviderMac::BatteryState>
BatteryLevelProviderMac::GetBatteryStateImpl() {
const base::mac::ScopedIOObject<io_service_t> 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<CFMutableDictionaryRef> 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<bool> 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<bool> 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<SInt64> current_capacity =
GetValueAsSInt64(dict.get(), CFSTR("AppleRawCurrentCapacity"));
if (!current_capacity.has_value()) {
return std::nullopt;
}
std::optional<SInt64> max_capacity =
GetValueAsSInt64(dict.get(), CFSTR("AppleRawMaxCapacity"));
if (!max_capacity.has_value()) {
return std::nullopt;
}
std::optional<SInt64> 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<uint64_t>(current_capacity.value()),
.full_charged_capacity = static_cast<uint64_t>(max_capacity.value()),
.voltage_mv = static_cast<uint64_t>(voltage_mv.value()),
.charge_unit = BatteryLevelUnit::kMAh}});
}
} // namespace base