1// Copyright 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "aemu/base/Optional.h"
16#include "aemu/base/system/Memory.h"
17#include "aemu/base/system/System.h"
18
19#include <CoreFoundation/CoreFoundation.h>
20#include <IOKit/IOBSD.h>
21#include <IOKit/IOKitLib.h>
22#include <AppKit/AppKit.h>
23
24#include <IOKit/kext/KextManager.h>
25#include <IOKit/storage/IOBlockStorageDevice.h>
26
27#import <Foundation/Foundation.h>
28#import <Foundation/NSProcessInfo.h>
29
30#include <mach/mach_init.h>
31#include <mach/thread_act.h>
32#include <mach/mach_port.h>
33
34#include <stdlib.h>
35#include <sys/stat.h>
36
37namespace android {
38namespace base {
39
40// From:
41// https://stackoverflow.com/questions/49677034/thread-sleeps-too-long-on-os-x-when-in-background
42void disableAppNap_macImpl(void) {
43   if ([[NSProcessInfo processInfo]
44        respondsToSelector:@selector(beginActivityWithOptions:reason:)]) {
45      [[NSProcessInfo processInfo]
46       beginActivityWithOptions:0x00FFFFFF
47       reason:@"Not sleepy and don't want to nap"];
48   }
49}
50
51// based on https://stackoverflow.com/questions/13893134/get-current-pthread-cpu-usage-mac-os-x
52void cpuUsageCurrentThread_macImpl(
53    uint64_t* user,
54    uint64_t* sys) {
55
56    kern_return_t kr;
57    mach_msg_type_number_t count;
58    mach_port_t thread;
59    thread_basic_info_data_t info;
60
61    thread = mach_thread_self();
62
63    count = THREAD_BASIC_INFO_COUNT;
64    kr = thread_info(
65        thread,
66        THREAD_BASIC_INFO,
67        (thread_info_t)&info,
68        &count);
69
70    if (kr == KERN_SUCCESS) {
71        *user =
72            info.user_time.seconds * 1000000ULL +
73            info.user_time.microseconds;
74        *sys =
75            info.system_time.seconds * 1000000ULL +
76            info.system_time.microseconds;
77    } else {
78        fprintf(stderr, "%s: WARNING: could not get thread info for CPU usage\n", __func__);
79    }
80
81    mach_port_deallocate(mach_task_self(), thread);
82}
83
84Optional<DiskKind> nativeDiskKind(int st_dev) {
85    const char* devName = devname(st_dev, S_IFBLK);
86    if (!devName) {
87        return {};
88    }
89    CFMutableDictionaryRef classesToMatch =
90            IOBSDNameMatching(kIOMasterPortDefault, 0, devName);
91    if (!classesToMatch) {
92        NSLog(@"Could not find io classes of disk");
93        return {};
94    }
95
96    // get iterator of matching services
97    io_iterator_t entryIterator;
98
99    if (KERN_SUCCESS != IOServiceGetMatchingServices(kIOMasterPortDefault,
100                                                     classesToMatch,
101                                                     &entryIterator)) {
102        NSLog(@"Can't iterate services");
103        return {};
104    }
105
106    // iterate over all found medias
107    io_object_t serviceEntry, parentMedia;
108    while ((serviceEntry = IOIteratorNext(entryIterator)) != 0) {
109        // We assume there won't be more levels of nesting here. The limit is
110        // arbitrary and can be increased if we hit it.
111        int maxlevels = 8;
112        do {
113            kern_return_t kernResult = IORegistryEntryGetParentEntry(
114                    serviceEntry, kIOServicePlane, &parentMedia);
115            IOObjectRelease(serviceEntry);
116
117            if (KERN_SUCCESS != kernResult) {
118                serviceEntry = 0;
119                NSLog(@"Error while getting parent service entry");
120                break;
121            }
122
123            serviceEntry = parentMedia;
124            if (!parentMedia) {
125                break;  // finished iterator
126            }
127
128            CFTypeRef res = IORegistryEntryCreateCFProperty(
129                    serviceEntry, CFSTR(kIOPropertyDeviceCharacteristicsKey),
130                    kCFAllocatorDefault, 0);
131            if (res) {
132                NSString* type = [(NSDictionary*)res
133                        objectForKey:(id)CFSTR(kIOPropertyMediumTypeKey)];
134                if ([@kIOPropertyMediumTypeSolidStateKey
135                            isEqualToString:type]) {
136                    CFRelease(res);
137                    return DiskKind::Ssd;
138                } else if ([@kIOPropertyMediumTypeRotationalKey
139                                   isEqualToString:type]) {
140                    CFRelease(res);
141                    return DiskKind::Hdd;
142                }
143                CFRelease(res);
144            }
145        } while (maxlevels--);
146
147        if (serviceEntry) {
148            IOObjectRelease(serviceEntry);
149        }
150    }
151    IOObjectRelease(entryIterator);
152
153    return {};
154}
155
156// From:
157// https://stackoverflow.com/questions/6796028/start-a-gui-process-in-mac-os-x-without-dock-icon
158void hideDockIcon_macImpl(void) {
159    if (NSApp == nil) {
160        // Initialize the global variable "NSApp"
161        [NSApplication sharedApplication];
162    }
163    [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
164
165}
166
167}  // namespace base
168}  // namespace android
169