1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Implementation based on sample code from
6// http://developer.apple.com/library/mac/#qa/qa1340/_index.html.
7
8#include "base/power_monitor/power_monitor_device_source.h"
9
10#include "base/mac/foundation_util.h"
11#include "base/mac/scoped_cftyperef.h"
12#include "base/power_monitor/power_monitor.h"
13#include "base/power_monitor/power_monitor_source.h"
14
15#include <IOKit/IOMessage.h>
16#include <IOKit/ps/IOPSKeys.h>
17#include <IOKit/ps/IOPowerSources.h>
18#include <IOKit/pwr_mgt/IOPMLib.h>
19
20namespace base {
21
22void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) {
23  PowerMonitorSource::ProcessPowerEvent(event);
24}
25
26bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() {
27  base::ScopedCFTypeRef<CFTypeRef> info(IOPSCopyPowerSourcesInfo());
28  if (!info)
29    return false;
30  base::ScopedCFTypeRef<CFArrayRef> power_sources_list(
31      IOPSCopyPowerSourcesList(info));
32  if (!power_sources_list)
33    return false;
34
35  bool found_battery = false;
36  const CFIndex count = CFArrayGetCount(power_sources_list);
37  for (CFIndex i = 0; i < count; ++i) {
38    const CFDictionaryRef description = IOPSGetPowerSourceDescription(
39        info, CFArrayGetValueAtIndex(power_sources_list, i));
40    if (!description)
41      continue;
42
43    CFStringRef current_state = base::mac::GetValueFromDictionary<CFStringRef>(
44        description, CFSTR(kIOPSPowerSourceStateKey));
45    if (!current_state)
46      continue;
47
48    // We only report "on battery power" if no source is on AC power.
49    if (CFStringCompare(current_state, CFSTR(kIOPSOffLineValue), 0) ==
50        kCFCompareEqualTo) {
51      continue;
52    } else if (CFStringCompare(current_state, CFSTR(kIOPSACPowerValue), 0) ==
53               kCFCompareEqualTo) {
54      return false;
55    }
56
57    DCHECK_EQ(CFStringCompare(current_state, CFSTR(kIOPSBatteryPowerValue), 0),
58              kCFCompareEqualTo)
59        << "Power source state is not one of 3 documented values";
60
61    found_battery = true;
62  }
63
64  // At this point, either there were no readable batteries found, in which case
65  // this Mac is not on battery power, or all the readable batteries were on
66  // battery power, in which case, count this as being on battery power.
67  return found_battery;
68}
69
70PowerObserver::DeviceThermalState
71PowerMonitorDeviceSource::GetCurrentThermalState() {
72  if (@available(macOS 10.10.3, *)) {
73    return thermal_state_observer_->GetCurrentThermalState();
74  };
75  return PowerObserver::DeviceThermalState::kUnknown;
76}
77
78namespace {
79
80void BatteryEventCallback(void*) {
81  ProcessPowerEventHelper(PowerMonitorSource::POWER_STATE_EVENT);
82}
83
84}  // namespace
85
86void PowerMonitorDeviceSource::PlatformInit() {
87  power_manager_port_ = IORegisterForSystemPower(
88      this,
89      mac::ScopedIONotificationPortRef::Receiver(notification_port_).get(),
90      &SystemPowerEventCallback, &notifier_);
91  DCHECK_NE(power_manager_port_, IO_OBJECT_NULL);
92
93  // Add the sleep/wake notification event source to the runloop.
94  CFRunLoopAddSource(
95      CFRunLoopGetCurrent(),
96      IONotificationPortGetRunLoopSource(notification_port_.get()),
97      kCFRunLoopCommonModes);
98
99  // Create and add the power-source-change event source to the runloop.
100  power_source_run_loop_source_.reset(
101      IOPSNotificationCreateRunLoopSource(&BatteryEventCallback, nullptr));
102  // Verify that the source was created. This may fail if the sandbox does not
103  // permit the process to access the underlying system service. See
104  // https://crbug.com/897557 for an example of such a configuration bug.
105  DCHECK(power_source_run_loop_source_);
106
107  CFRunLoopAddSource(CFRunLoopGetCurrent(), power_source_run_loop_source_,
108                     kCFRunLoopDefaultMode);
109
110  if (@available(macOS 10.10.3, *)) {
111    thermal_state_observer_ = std::make_unique<ThermalStateObserverMac>(
112        BindRepeating(&PowerMonitorSource::ProcessThermalEvent));
113  };
114}
115
116void PowerMonitorDeviceSource::PlatformDestroy() {
117  CFRunLoopRemoveSource(
118      CFRunLoopGetCurrent(),
119      IONotificationPortGetRunLoopSource(notification_port_.get()),
120      kCFRunLoopCommonModes);
121
122  CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
123                        power_source_run_loop_source_.get(),
124                        kCFRunLoopDefaultMode);
125
126  // Deregister for system power notifications.
127  IODeregisterForSystemPower(&notifier_);
128
129  // Close the connection to the IOPMrootDomain that was opened in
130  // PlatformInit().
131  IOServiceClose(power_manager_port_);
132  power_manager_port_ = IO_OBJECT_NULL;
133}
134
135void PowerMonitorDeviceSource::SystemPowerEventCallback(
136    void* refcon,
137    io_service_t service,
138    natural_t message_type,
139    void* message_argument) {
140  auto* thiz = static_cast<PowerMonitorDeviceSource*>(refcon);
141
142  switch (message_type) {
143    // If this message is not handled the system may delay sleep for 30 seconds.
144    case kIOMessageCanSystemSleep:
145      IOAllowPowerChange(thiz->power_manager_port_,
146                         reinterpret_cast<intptr_t>(message_argument));
147      break;
148    case kIOMessageSystemWillSleep:
149      ProcessPowerEventHelper(PowerMonitorSource::SUSPEND_EVENT);
150      IOAllowPowerChange(thiz->power_manager_port_,
151                         reinterpret_cast<intptr_t>(message_argument));
152      break;
153
154    case kIOMessageSystemWillPowerOn:
155      ProcessPowerEventHelper(PowerMonitorSource::RESUME_EVENT);
156      break;
157  }
158}
159
160}  // namespace base
161