• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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