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  base::ScopedCFTypeRef<CFArrayRef> power_sources_list(
29      IOPSCopyPowerSourcesList(info));
30
31  const CFIndex count = CFArrayGetCount(power_sources_list);
32  for (CFIndex i = 0; i < count; ++i) {
33    const CFDictionaryRef description = IOPSGetPowerSourceDescription(
34        info, CFArrayGetValueAtIndex(power_sources_list, i));
35    if (!description)
36      continue;
37
38    CFStringRef current_state = base::mac::GetValueFromDictionary<CFStringRef>(
39        description, CFSTR(kIOPSPowerSourceStateKey));
40
41    if (!current_state)
42      continue;
43
44    // We only report "on battery power" if no source is on AC power.
45    if (CFStringCompare(current_state, CFSTR(kIOPSBatteryPowerValue), 0) !=
46        kCFCompareEqualTo) {
47      return false;
48    }
49  }
50
51  return true;
52}
53
54namespace {
55
56void BatteryEventCallback(void*) {
57  ProcessPowerEventHelper(PowerMonitorSource::POWER_STATE_EVENT);
58}
59
60}  // namespace
61
62void PowerMonitorDeviceSource::PlatformInit() {
63  power_manager_port_ = IORegisterForSystemPower(
64      this,
65      mac::ScopedIONotificationPortRef::Receiver(notification_port_).get(),
66      &SystemPowerEventCallback, &notifier_);
67  DCHECK_NE(power_manager_port_, IO_OBJECT_NULL);
68
69  // Add the sleep/wake notification event source to the runloop.
70  CFRunLoopAddSource(
71      CFRunLoopGetCurrent(),
72      IONotificationPortGetRunLoopSource(notification_port_.get()),
73      kCFRunLoopCommonModes);
74
75  // Create and add the power-source-change event source to the runloop.
76  power_source_run_loop_source_.reset(
77      IOPSNotificationCreateRunLoopSource(&BatteryEventCallback, nullptr));
78  // Verify that the source was created. This may fail if the sandbox does not
79  // permit the process to access the underlying system service. See
80  // https://crbug.com/897557 for an example of such a configuration bug.
81  DCHECK(power_source_run_loop_source_);
82
83  CFRunLoopAddSource(CFRunLoopGetCurrent(), power_source_run_loop_source_,
84                     kCFRunLoopDefaultMode);
85}
86
87void PowerMonitorDeviceSource::PlatformDestroy() {
88  CFRunLoopRemoveSource(
89      CFRunLoopGetCurrent(),
90      IONotificationPortGetRunLoopSource(notification_port_.get()),
91      kCFRunLoopCommonModes);
92
93  CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
94                        power_source_run_loop_source_.get(),
95                        kCFRunLoopDefaultMode);
96
97  // Deregister for system power notifications.
98  IODeregisterForSystemPower(&notifier_);
99
100  // Close the connection to the IOPMrootDomain that was opened in
101  // PlatformInit().
102  IOServiceClose(power_manager_port_);
103  power_manager_port_ = IO_OBJECT_NULL;
104}
105
106void PowerMonitorDeviceSource::SystemPowerEventCallback(
107    void* refcon,
108    io_service_t service,
109    natural_t message_type,
110    void* message_argument) {
111  auto* thiz = static_cast<PowerMonitorDeviceSource*>(refcon);
112
113  switch (message_type) {
114    // If this message is not handled the system may delay sleep for 30 seconds.
115    case kIOMessageCanSystemSleep:
116      IOAllowPowerChange(thiz->power_manager_port_,
117                         reinterpret_cast<intptr_t>(message_argument));
118      break;
119    case kIOMessageSystemWillSleep:
120      ProcessPowerEventHelper(PowerMonitorSource::SUSPEND_EVENT);
121      IOAllowPowerChange(thiz->power_manager_port_,
122                         reinterpret_cast<intptr_t>(message_argument));
123      break;
124
125    case kIOMessageSystemWillPowerOn:
126      ProcessPowerEventHelper(PowerMonitorSource::RESUME_EVENT);
127      break;
128  }
129}
130
131}  // namespace base
132