// // RNDeviceInfo.m // Learnium // // Created by Rebecca Hughes on 03/08/2015. // Copyright © 2015 Learnium Limited. All rights reserved. // #include #include #import #import #import #import #import "RNDeviceInfo.h" #import "DeviceUID.h" #import #import "EnvironmentUtil.h" #if !(TARGET_OS_TV) #import #import #endif typedef NS_ENUM(NSInteger, DeviceType) { DeviceTypeHandset, DeviceTypeTablet, DeviceTypeTv, DeviceTypeDesktop, DeviceTypeHeadset, DeviceTypeUnknown }; #define DeviceTypeValues [NSArray arrayWithObjects: @"Handset", @"Tablet", @"Tv", @"Desktop", @"Headset", @"unknown", nil] #if (!(TARGET_OS_TV || TARGET_OS_VISION)) @import CoreTelephony; #endif #if !TARGET_OS_TV @import Darwin.sys.sysctl; #endif @implementation RNDeviceInfo { bool hasListeners; } RCT_EXPORT_MODULE(); + (BOOL)requiresMainQueueSetup { return NO; } - (NSArray *)supportedEvents { return @[@"RNDeviceInfo_batteryLevelDidChange", @"RNDeviceInfo_batteryLevelIsLow", @"RNDeviceInfo_powerStateDidChange", @"RNDeviceInfo_headphoneConnectionDidChange", @"RNDeviceInfo_headphoneWiredConnectionDidChange", @"RNDeviceInfo_headphoneBluetoothConnectionDidChange", @"RNDeviceInfo_brightnessDidChange"]; } - (NSDictionary *)constantsToExport { return @{ @"deviceId": [self getDeviceId], @"bundleId": [self getBundleId], @"systemName": [self getSystemName], @"systemVersion": [self getSystemVersion], @"appVersion": [self getAppVersion], @"buildNumber": [self getBuildNumber], @"isTablet": @([self isTablet]), @"appName": [self getAppName], @"brand": @"Apple", @"model": [self getModel], @"deviceType": [self getDeviceTypeName], @"isDisplayZoomed": @([self isDisplayZoomed]), }; } - (id)init { if ((self = [super init])) { #if !TARGET_OS_TV _lowBatteryThreshold = 0.20; [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(batteryLevelDidChange:) name:UIDeviceBatteryLevelDidChangeNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(powerStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(powerStateDidChange:) name:NSProcessInfoPowerStateDidChangeNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(headphoneConnectionDidChange:) name:AVAudioSessionRouteChangeNotification object: [AVAudioSession sharedInstance]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(headphoneWiredConnectionDidChange:) name:AVAudioSessionRouteChangeNotification object: [AVAudioSession sharedInstance]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(headphoneBluetoothConnectionDidChange:) name:AVAudioSessionRouteChangeNotification object: [AVAudioSession sharedInstance]]; #if !TARGET_OS_VISION [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(brightnessDidChange:) name:UIScreenBrightnessDidChangeNotification object: nil]; #endif #endif } return self; } - (void)startObserving { hasListeners = YES; } - (void)stopObserving { hasListeners = NO; } - (DeviceType) getDeviceType { switch ([[UIDevice currentDevice] userInterfaceIdiom]) { case UIUserInterfaceIdiomPhone: return DeviceTypeHandset; case UIUserInterfaceIdiomPad: if (TARGET_OS_MACCATALYST) { return DeviceTypeDesktop; } if (@available(iOS 14.0, *)) { if ([NSProcessInfo processInfo].isiOSAppOnMac) { return DeviceTypeDesktop; } } return DeviceTypeTablet; case UIUserInterfaceIdiomTV: return DeviceTypeTv; case UIUserInterfaceIdiomMac: return DeviceTypeDesktop; #if TARGET_OS_VISION case UIUserInterfaceIdiomVision: return DeviceTypeHeadset; #endif default: return DeviceTypeUnknown; } } - (NSDictionary *) getStorageDictionary { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); return [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: nil]; } - (NSString *) getSystemName { UIDevice *currentDevice = [UIDevice currentDevice]; return currentDevice.systemName; } - (NSString *) getSystemVersion { UIDevice *currentDevice = [UIDevice currentDevice]; return currentDevice.systemVersion; } - (NSString *) getDeviceName { UIDevice *currentDevice = [UIDevice currentDevice]; return currentDevice.name; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getDeviceNameSync) { return self.getDeviceName; } RCT_EXPORT_METHOD(getDeviceName:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getDeviceName); } - (BOOL) isDisplayZoomed { #if !TARGET_OS_VISION return [UIScreen mainScreen].scale != [UIScreen mainScreen].nativeScale; #else return NO; #endif } - (NSString *) getAppName { NSString *displayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; NSString *bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; return displayName ? displayName : bundleName; } - (NSString *) getBundleId { return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; } - (NSString *) getAppVersion { return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; } - (NSString *) getBuildNumber { return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; } - (NSDictionary *) getDeviceNamesByCode { return @{ @"iPod1,1": @"iPod Touch", // (Original) @"iPod2,1": @"iPod Touch", // (Second Generation) @"iPod3,1": @"iPod Touch", // (Third Generation) @"iPod4,1": @"iPod Touch", // (Fourth Generation) @"iPod5,1": @"iPod Touch", // (Fifth Generation) @"iPod7,1": @"iPod Touch", // (Sixth Generation) @"iPod9,1": @"iPod Touch", // (Seventh Generation) @"iPhone1,1": @"iPhone", // (Original) @"iPhone1,2": @"iPhone 3G", // (3G) @"iPhone2,1": @"iPhone 3GS", // (3GS) @"iPad1,1": @"iPad", // (Original) @"iPad2,1": @"iPad 2", // @"iPad2,2": @"iPad 2", // @"iPad2,3": @"iPad 2", // @"iPad2,4": @"iPad 2", // @"iPad3,1": @"iPad", // (3rd Generation) @"iPad3,2": @"iPad", // (3rd Generation) @"iPad3,3": @"iPad", // (3rd Generation) @"iPhone3,1": @"iPhone 4", // (GSM) @"iPhone3,2": @"iPhone 4", // iPhone 4 @"iPhone3,3": @"iPhone 4", // (CDMA/Verizon/Sprint) @"iPhone4,1": @"iPhone 4S", // @"iPhone5,1": @"iPhone 5", // (model A1428, AT&T/Canada) @"iPhone5,2": @"iPhone 5", // (model A1429, everything else) @"iPad3,4": @"iPad", // (4th Generation) @"iPad3,5": @"iPad", // (4th Generation) @"iPad3,6": @"iPad", // (4th Generation) @"iPad2,5": @"iPad Mini", // (Original) @"iPad2,6": @"iPad Mini", // (Original) @"iPad2,7": @"iPad Mini", // (Original) @"iPhone5,3": @"iPhone 5c", // (model A1456, A1532 | GSM) @"iPhone5,4": @"iPhone 5c", // (model A1507, A1516, A1526 (China), A1529 | Global) @"iPhone6,1": @"iPhone 5s", // (model A1433, A1533 | GSM) @"iPhone6,2": @"iPhone 5s", // (model A1457, A1518, A1528 (China), A1530 | Global) @"iPhone7,1": @"iPhone 6 Plus", // @"iPhone7,2": @"iPhone 6", // @"iPhone8,1": @"iPhone 6s", // @"iPhone8,2": @"iPhone 6s Plus", // @"iPhone8,4": @"iPhone SE", // @"iPhone9,1": @"iPhone 7", // (model A1660 | CDMA) @"iPhone9,3": @"iPhone 7", // (model A1778 | Global) @"iPhone9,2": @"iPhone 7 Plus", // (model A1661 | CDMA) @"iPhone9,4": @"iPhone 7 Plus", // (model A1784 | Global) @"iPhone10,3": @"iPhone X", // (model A1865, A1902) @"iPhone10,6": @"iPhone X", // (model A1901) @"iPhone10,1": @"iPhone 8", // (model A1863, A1906, A1907) @"iPhone10,4": @"iPhone 8", // (model A1905) @"iPhone10,2": @"iPhone 8 Plus", // (model A1864, A1898, A1899) @"iPhone10,5": @"iPhone 8 Plus", // (model A1897) @"iPhone11,2": @"iPhone XS", // (model A2097, A2098) @"iPhone11,4": @"iPhone XS Max", // (model A1921, A2103) @"iPhone11,6": @"iPhone XS Max", // (model A2104) @"iPhone11,8": @"iPhone XR", // (model A1882, A1719, A2105) @"iPhone12,1": @"iPhone 11", @"iPhone12,3": @"iPhone 11 Pro", @"iPhone12,5": @"iPhone 11 Pro Max", @"iPhone12,8": @"iPhone SE", // (2nd Generation iPhone SE), @"iPhone13,1": @"iPhone 12 mini", @"iPhone13,2": @"iPhone 12", @"iPhone13,3": @"iPhone 12 Pro", @"iPhone13,4": @"iPhone 12 Pro Max", @"iPhone14,4": @"iPhone 13 mini", @"iPhone14,5": @"iPhone 13", @"iPhone14,2": @"iPhone 13 Pro", @"iPhone14,3": @"iPhone 13 Pro Max", @"iPhone14,6": @"iPhone SE", // (3nd Generation iPhone SE), @"iPhone14,7": @"iPhone 14", @"iPhone14,8": @"iPhone 14 Plus", @"iPhone15,2": @"iPhone 14 Pro", @"iPhone15,3": @"iPhone 14 Pro Max", @"iPhone15,4": @"iPhone 15", @"iPhone15,5": @"iPhone 15 Plus", @"iPhone16,1": @"iPhone 15 Pro", @"iPhone16,2": @"iPhone 15 Pro Max", @"iPad4,1": @"iPad Air", // 5th Generation iPad (iPad Air) - Wifi @"iPad4,2": @"iPad Air", // 5th Generation iPad (iPad Air) - Cellular @"iPad4,3": @"iPad Air", // 5th Generation iPad (iPad Air) @"iPad4,4": @"iPad Mini 2", // (2nd Generation iPad Mini - Wifi) @"iPad4,5": @"iPad Mini 2", // (2nd Generation iPad Mini - Cellular) @"iPad4,6": @"iPad Mini 2", // (2nd Generation iPad Mini) @"iPad4,7": @"iPad Mini 3", // (3rd Generation iPad Mini) @"iPad4,8": @"iPad Mini 3", // (3rd Generation iPad Mini) @"iPad4,9": @"iPad Mini 3", // (3rd Generation iPad Mini) @"iPad5,1": @"iPad Mini 4", // (4th Generation iPad Mini) @"iPad5,2": @"iPad Mini 4", // (4th Generation iPad Mini) @"iPad5,3": @"iPad Air 2", // 6th Generation iPad (iPad Air 2) @"iPad5,4": @"iPad Air 2", // 6th Generation iPad (iPad Air 2) @"iPad6,3": @"iPad Pro 9.7-inch", // iPad Pro 9.7-inch @"iPad6,4": @"iPad Pro 9.7-inch", // iPad Pro 9.7-inch @"iPad6,7": @"iPad Pro 12.9-inch", // iPad Pro 12.9-inch @"iPad6,8": @"iPad Pro 12.9-inch", // iPad Pro 12.9-inch @"iPad6,11": @"iPad (5th generation)", // Apple iPad 9.7 inch (5th generation) - WiFi @"iPad6,12": @"iPad (5th generation)", // Apple iPad 9.7 inch (5th generation) - WiFi + cellular @"iPad7,1": @"iPad Pro 12.9-inch", // 2nd Generation iPad Pro 12.5-inch - Wifi @"iPad7,2": @"iPad Pro 12.9-inch", // 2nd Generation iPad Pro 12.5-inch - Cellular @"iPad7,3": @"iPad Pro 10.5-inch", // iPad Pro 10.5-inch - Wifi @"iPad7,4": @"iPad Pro 10.5-inch", // iPad Pro 10.5-inch - Cellular @"iPad7,5": @"iPad (6th generation)", // iPad (6th generation) - Wifi @"iPad7,6": @"iPad (6th generation)", // iPad (6th generation) - Cellular @"iPad7,11": @"iPad (7th generation)", // iPad 10.2 inch (7th generation) - Wifi @"iPad7,12": @"iPad (7th generation)", // iPad 10.2 inch (7th generation) - Wifi + cellular @"iPad8,1": @"iPad Pro 11-inch (3rd generation)", // iPad Pro 11 inch (3rd generation) - Wifi @"iPad8,2": @"iPad Pro 11-inch (3rd generation)", // iPad Pro 11 inch (3rd generation) - 1TB - Wifi @"iPad8,3": @"iPad Pro 11-inch (3rd generation)", // iPad Pro 11 inch (3rd generation) - Wifi + cellular @"iPad8,4": @"iPad Pro 11-inch (3rd generation)", // iPad Pro 11 inch (3rd generation) - 1TB - Wifi + cellular @"iPad8,5": @"iPad Pro 12.9-inch (3rd generation)", // iPad Pro 12.9 inch (3rd generation) - Wifi @"iPad8,6": @"iPad Pro 12.9-inch (3rd generation)", // iPad Pro 12.9 inch (3rd generation) - 1TB - Wifi @"iPad8,7": @"iPad Pro 12.9-inch (3rd generation)", // iPad Pro 12.9 inch (3rd generation) - Wifi + cellular @"iPad8,8": @"iPad Pro 12.9-inch (3rd generation)", // iPad Pro 12.9 inch (3rd generation) - 1TB - Wifi + cellular @"iPad11,1": @"iPad Mini 5", // (5th Generation iPad Mini) @"iPad11,2": @"iPad Mini 5", // (5th Generation iPad Mini) @"iPad11,3": @"iPad Air (3rd generation)", @"iPad11,4": @"iPad Air (3rd generation)", @"iPad13,1": @"iPad Air (4th generation)", @"iPad13,2": @"iPad Air (4th generation)", @"AppleTV2,1": @"Apple TV", // Apple TV (2nd Generation) @"AppleTV3,1": @"Apple TV", // Apple TV (3rd Generation) @"AppleTV3,2": @"Apple TV", // Apple TV (3rd Generation - Rev A) @"AppleTV5,3": @"Apple TV", // Apple TV (4th Generation) @"AppleTV6,2": @"Apple TV 4K", // Apple TV 4K @"RealityDevice14,1": @"Apple Vision Pro" // Apple Vision Pro }; } - (NSString *) getModel { NSString* deviceId = [self getDeviceId]; NSDictionary* deviceNamesByCode = [self getDeviceNamesByCode]; NSString* deviceName =[deviceNamesByCode valueForKey:deviceId]; // Return the real device name if we have it if (deviceName) { return deviceName; } // If we don't have the real device name, try a generic if ([deviceId hasPrefix:@"iPod"]) { return @"iPod Touch"; } else if ([deviceId hasPrefix:@"iPad"]) { return @"iPad"; } else if ([deviceId hasPrefix:@"iPhone"]) { return @"iPhone"; } else if ([deviceId hasPrefix:@"AppleTV"]) { return @"Apple TV"; } else if ([deviceId hasPrefix:@"RealityDevice"]) { return @"Apple Vision"; } // If we could not even get a generic, it's unknown return @"unknown"; } - (NSString *) getCarrier { #if (TARGET_OS_TV || TARGET_OS_MACCATALYST || TARGET_OS_VISION) return @"unknown"; #else CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init]; CTCarrier *carrier = [netinfo subscriberCellularProvider]; if (carrier.carrierName != nil) { return carrier.carrierName; } return @"unknown"; #endif } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCarrierSync) { return self.getCarrier; } RCT_EXPORT_METHOD(getCarrier:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getCarrier); } - (NSString *) getBuildId { #if TARGET_OS_TV return @"unknown"; #else size_t bufferSize = 64; NSMutableData *buffer = [[NSMutableData alloc] initWithLength:bufferSize]; int status = sysctlbyname("kern.osversion", buffer.mutableBytes, &bufferSize, NULL, 0); if (status != 0) { return @"unknown"; } NSString* buildId = [[NSString alloc] initWithCString:buffer.mutableBytes encoding:NSUTF8StringEncoding]; return buildId; #endif } RCT_EXPORT_METHOD(getBuildId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getBuildId); } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBuildIdSync) { return self.getBuildId; } - (NSString *) uniqueId { return [DeviceUID uid]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getUniqueIdSync) { return self.uniqueId; } RCT_EXPORT_METHOD(getUniqueId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.uniqueId); } RCT_EXPORT_METHOD(syncUniqueId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve([DeviceUID syncUid]); } - (NSString *) getDeviceId { struct utsname systemInfo; uname(&systemInfo); NSString* deviceId = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; #if TARGET_IPHONE_SIMULATOR deviceId = [NSString stringWithFormat:@"%s", getenv("SIMULATOR_MODEL_IDENTIFIER")]; #endif return deviceId; } - (BOOL) isEmulator { #if TARGET_IPHONE_SIMULATOR return YES; #else return NO; #endif } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isEmulatorSync) { return @(self.isEmulator); } RCT_EXPORT_METHOD(isEmulator:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isEmulator)); } - (BOOL) isTablet { if ([self getDeviceType] == DeviceTypeTablet) { return YES; } else { return NO; } } RCT_EXPORT_METHOD(getDeviceToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { if (@available(iOS 11.0, *)) { if (TARGET_IPHONE_SIMULATOR) { reject(@"NOT AVAILABLE", @"Device check is only available for physical devices", nil); return; } DCDevice *device = DCDevice.currentDevice; if ([device isSupported]) { [DCDevice.currentDevice generateTokenWithCompletionHandler:^(NSData * _Nullable token, NSError * _Nullable error) { if (error) { reject(@"ERROR GENERATING TOKEN", error.localizedDescription, error); return; } resolve([token base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]); }]; } else { reject(@"NOT SUPPORTED", @"Device check is not supported by this device", nil); return; } } else { reject(@"NOT AVAILABLE", @"Device check is only available for iOS > 11", nil); return; } } - (float) getFontScale { // Font scales based on font sizes from https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/ float fontScale = 1.0; #if !TARGET_OS_VISION UITraitCollection *traitCollection = [[UIScreen mainScreen] traitCollection]; // Shared application is unavailable in an app extension. if (traitCollection) { __block NSString *contentSize = nil; RCTUnsafeExecuteOnMainQueueSync(^{ if (@available(iOS 10.0, tvOS 10.0, macCatalyst 13.0, *)) { contentSize = traitCollection.preferredContentSizeCategory; } else { // if we can't get contentSize, we'll fall back to 1.0 } }); if ([contentSize isEqual: @"UICTContentSizeCategoryXS"]) fontScale = 0.82; else if ([contentSize isEqual: @"UICTContentSizeCategoryS"]) fontScale = 0.88; else if ([contentSize isEqual: @"UICTContentSizeCategoryM"]) fontScale = 0.95; else if ([contentSize isEqual: @"UICTContentSizeCategoryL"]) fontScale = 1.0; else if ([contentSize isEqual: @"UICTContentSizeCategoryXL"]) fontScale = 1.12; else if ([contentSize isEqual: @"UICTContentSizeCategoryXXL"]) fontScale = 1.23; else if ([contentSize isEqual: @"UICTContentSizeCategoryXXXL"]) fontScale = 1.35; else if ([contentSize isEqual: @"UICTContentSizeCategoryAccessibilityM"]) fontScale = 1.64; else if ([contentSize isEqual: @"UICTContentSizeCategoryAccessibilityL"]) fontScale = 1.95; else if ([contentSize isEqual: @"UICTContentSizeCategoryAccessibilityXL"]) fontScale = 2.35; else if ([contentSize isEqual: @"UICTContentSizeCategoryAccessibilityXXL"]) fontScale = 2.76; else if ([contentSize isEqual: @"UICTContentSizeCategoryAccessibilityXXXL"]) fontScale = 3.12; } #endif return fontScale; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getFontScaleSync) { return @(self.getFontScale); } RCT_EXPORT_METHOD(getFontScale:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.getFontScale)); } - (double) getTotalMemory { return [NSProcessInfo processInfo].physicalMemory; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getTotalMemorySync) { return @(self.getTotalMemory); } RCT_EXPORT_METHOD(getTotalMemory:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.getTotalMemory)); } - (double) getTotalDiskCapacity { uint64_t totalSpace = 0; NSDictionary *storage = [self getStorageDictionary]; if (storage) { NSNumber *fileSystemSizeInBytes = [storage objectForKey: NSFileSystemSize]; totalSpace = [fileSystemSizeInBytes unsignedLongLongValue]; } return (double) totalSpace; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getTotalDiskCapacitySync) { return @(self.getTotalDiskCapacity); } RCT_EXPORT_METHOD(getTotalDiskCapacity:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.getTotalDiskCapacity)); } - (double) getFreeDiskStorage { uint64_t freeSpace = 0; NSDictionary *storage = [self getStorageDictionary]; if (storage) { NSNumber *freeFileSystemSizeInBytes = [storage objectForKey: NSFileSystemFreeSize]; freeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue]; } return (double) freeSpace; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getFreeDiskStorageSync) { return @(self.getFreeDiskStorage); } RCT_EXPORT_METHOD(getFreeDiskStorage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.getFreeDiskStorage)); } - (NSString *) getDeviceTypeName { return [DeviceTypeValues objectAtIndex: [self getDeviceType]]; } - (NSArray *) getSupportedAbis { /* https://stackoverflow.com/questions/19859388/how-can-i-get-the-ios-device-cpu-architecture-in-runtime */ const NXArchInfo *info = NXGetLocalArchInfo(); NSString *typeOfCpu = [NSString stringWithUTF8String:info->description]; return @[typeOfCpu]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getSupportedAbisSync) { return self.getSupportedAbis; } RCT_EXPORT_METHOD(getSupportedAbis:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getSupportedAbis); } - (NSString *) getIpAddress { NSString *address = @"0.0.0.0"; struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; while(temp_addr != NULL) { sa_family_t addr_family = temp_addr->ifa_addr->sa_family; // Check for IPv4 or IPv6-only interfaces if(addr_family == AF_INET || addr_family == AF_INET6) { NSString* ifname = [NSString stringWithUTF8String:temp_addr->ifa_name]; if( // Check if interface is en0 which is the wifi connection the iPhone // and the ethernet connection on the Apple TV [ifname isEqualToString:@"en0"] || // Check if interface is en1 which is the wifi connection on the Apple TV [ifname isEqualToString:@"en1"] ) { const struct sockaddr_in *addr = (const struct sockaddr_in*)temp_addr->ifa_addr; socklen_t addr_len = addr_family == AF_INET ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; char addr_buffer[addr_len]; // We use inet_ntop because it also supports getting an address from // interfaces that are IPv6-only const char *netname = inet_ntop(addr_family, &addr->sin_addr, addr_buffer, addr_len); // Get NSString from C String address = [NSString stringWithUTF8String:netname]; } } temp_addr = temp_addr->ifa_next; } } // Free memory freeifaddrs(interfaces); return address; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getIpAddressSync) { return self.getIpAddress; } RCT_EXPORT_METHOD(getIpAddress:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getIpAddress); } - (BOOL) isPinOrFingerprintSet { #if TARGET_OS_TV return NO; #else LAContext *context = [[LAContext alloc] init]; return [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:nil]; #endif } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isPinOrFingerprintSetSync) { return @(self.isPinOrFingerprintSet); } RCT_EXPORT_METHOD(isPinOrFingerprintSet:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isPinOrFingerprintSet)); } - (void) batteryLevelDidChange:(NSNotification *)notification { if (!hasListeners) { return; } float batteryLevel = self.getBatteryLevel; [self sendEventWithName:@"RNDeviceInfo_batteryLevelDidChange" body:@(batteryLevel)]; if (batteryLevel <= _lowBatteryThreshold) { [self sendEventWithName:@"RNDeviceInfo_batteryLevelIsLow" body:@(batteryLevel)]; } } - (void) powerStateDidChange:(NSNotification *)notification { if (!hasListeners) { return; } [self sendEventWithName:@"RNDeviceInfo_powerStateDidChange" body:self.powerState]; } - (void) headphoneConnectionDidChange:(NSNotification *)notification { if (!hasListeners) { return; } BOOL isConnected = [self isHeadphonesConnected]; [self sendEventWithName:@"RNDeviceInfo_headphoneConnectionDidChange" body:[NSNumber numberWithBool:isConnected]]; } - (void) headphoneWiredConnectionDidChange:(NSNotification *)notification { if (!hasListeners) { return; } BOOL isConnected = [self isWiredHeadphonesConnected]; [self sendEventWithName:@"RNDeviceInfo_headphoneWiredConnectionDidChange" body:[NSNumber numberWithBool:isConnected]]; } - (void) headphoneBluetoothConnectionDidChange:(NSNotification *)notification { if (!hasListeners) { return; } BOOL isConnected = [self isBluetoothHeadphonesConnected]; [self sendEventWithName:@"RNDeviceInfo_headphoneBluetoothConnectionDidChange" body:[NSNumber numberWithBool:isConnected]]; } - (void) brightnessDidChange:(NSNotification *)notification { if (!hasListeners) { return; } [self sendEventWithName:@"RNDeviceInfo_brightnessDidChange" body:self.getBrightness]; } - (NSDictionary *) powerState { #if RCT_DEV && (!TARGET_IPHONE_SIMULATOR) && !TARGET_OS_TV if ([UIDevice currentDevice].isBatteryMonitoringEnabled != true) { RCTLogWarn(@"Battery monitoring is not enabled. " "You need to enable monitoring with `[UIDevice currentDevice].batteryMonitoringEnabled = TRUE`"); } #endif #if RCT_DEV && TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateUnknown) { RCTLogWarn(@"Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS."); } #endif float batteryLevel = self.getBatteryLevel; return @{ #if TARGET_OS_TV @"batteryLevel": @(batteryLevel), @"batteryState": @"full", #else @"batteryLevel": @(batteryLevel), @"batteryState": [@[@"unknown", @"unplugged", @"charging", @"full"] objectAtIndex: [UIDevice currentDevice].batteryState], @"lowPowerMode": @([NSProcessInfo processInfo].isLowPowerModeEnabled), #endif }; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getPowerStateSync) { return self.powerState; } RCT_EXPORT_METHOD(getPowerState:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.powerState); } - (float) getBatteryLevel { #if TARGET_OS_TV return [@1 floatValue]; #else return [@([UIDevice currentDevice].batteryLevel) floatValue]; #endif } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBatteryLevelSync) { return @(self.getBatteryLevel); } RCT_EXPORT_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.getBatteryLevel)); } - (BOOL) isBatteryCharging { return [self.powerState[@"batteryState"] isEqualToString:@"charging"]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isBatteryChargingSync) { return @(self.isBatteryCharging); } RCT_EXPORT_METHOD(isBatteryCharging:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isBatteryCharging)); } - (BOOL) isLocationEnabled { return [CLLocationManager locationServicesEnabled]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isLocationEnabledSync) { return @(self.isLocationEnabled); } RCT_EXPORT_METHOD(isLocationEnabled:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isLocationEnabled)); } - (BOOL) isHeadphonesConnected { AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute]; for (AVAudioSessionPortDescription* desc in [route outputs]) { if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones]) { return YES; } if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP]) { return YES; } if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP]) { return YES; } } return NO; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isHeadphonesConnectedSync) { return @(self.isHeadphonesConnected); } RCT_EXPORT_METHOD(isHeadphonesConnected:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isHeadphonesConnected)); } - (BOOL) isWiredHeadphonesConnected { AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute]; for (AVAudioSessionPortDescription* desc in [route outputs]) { if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones]) { return YES; } } return NO; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isWiredHeadphonesConnectedSync) { return @(self.isWiredHeadphonesConnected); } RCT_EXPORT_METHOD(isWiredHeadphonesConnected:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isWiredHeadphonesConnected)); } - (BOOL) isBluetoothHeadphonesConnected { AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute]; for (AVAudioSessionPortDescription* desc in [route outputs]) { if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP]) { return YES; } if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP]) { return YES; } } return NO; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isBluetoothHeadphonesConnectedSync) { return @(self.isBluetoothHeadphonesConnected); } RCT_EXPORT_METHOD(isBluetoothHeadphonesConnected:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.isBluetoothHeadphonesConnected)); } - (unsigned long) getUsedMemory { struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size); if (kerr != KERN_SUCCESS) { return -1; } return (unsigned long)info.resident_size; } RCT_EXPORT_METHOD(getUsedMemory:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { unsigned long usedMemory = self.getUsedMemory; if (usedMemory == -1) { reject(@"fetch_error", @"task_info failed", nil); } else { resolve(@(usedMemory)); } } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getUsedMemorySync) { return @(self.getUsedMemory); } RCT_EXPORT_METHOD(getUserAgent:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { #if TARGET_OS_TV reject(@"not_available_error", @"not available on tvOS", nil); #else __weak RNDeviceInfo *weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ __strong RNDeviceInfo *strongSelf = weakSelf; if (strongSelf) { // Save WKWebView (it might deallocate before we ask for user Agent) __block WKWebView *webView = [[WKWebView alloc] init]; [webView evaluateJavaScript:@"window.navigator.userAgent;" completionHandler:^(id _Nullable result, NSError * _Nullable error) { if (error) { reject(@"getUserAgentError", error.localizedDescription, error); }else{ resolve([NSString stringWithFormat:@"%@", result]); } // Destroy the WKWebView after task is complete webView = nil; }]; } }); #endif } - (NSDictionary *) getAvailableLocationProviders { #if !TARGET_OS_TV return @{ @"locationServicesEnabled": [NSNumber numberWithBool: [CLLocationManager locationServicesEnabled]], @"significantLocationChangeMonitoringAvailable": [NSNumber numberWithBool: [CLLocationManager significantLocationChangeMonitoringAvailable]], @"headingAvailable": [NSNumber numberWithBool: [CLLocationManager headingAvailable]], @"isRangingAvailable": [NSNumber numberWithBool: [CLLocationManager isRangingAvailable]] }; #else return @{ @"locationServicesEnabled": [NSNumber numberWithBool: [CLLocationManager locationServicesEnabled]] }; #endif } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getAvailableLocationProvidersSync) { return self.getAvailableLocationProviders; } RCT_EXPORT_METHOD(getAvailableLocationProviders:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getAvailableLocationProviders); } #pragma mark - Installer Package Name - RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getInstallerPackageNameSync) { return [EnvironmentValues objectAtIndex:[EnvironmentUtil currentAppEnvironment]]; } RCT_EXPORT_METHOD(getInstallerPackageName:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve([EnvironmentValues objectAtIndex:[EnvironmentUtil currentAppEnvironment]]); } - (NSNumber *) getBrightness { #if !TARGET_OS_TV && !TARGET_OS_VISION return @([UIScreen mainScreen].brightness); #else return @(-1); #endif } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBrightnessSync) { return self.getBrightness; } RCT_EXPORT_METHOD(getBrightness:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(self.getBrightness); } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getFirstInstallTimeSync) { return @(self.getFirstInstallTime); } RCT_EXPORT_METHOD(getFirstInstallTime:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@(self.getFirstInstallTime)); } - (long long) getFirstInstallTime { NSURL* urlToDocumentsFolder = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; NSError *error; NSDate *installDate = [[[NSFileManager defaultManager] attributesOfItemAtPath:urlToDocumentsFolder.path error:&error] objectForKey:NSFileCreationDate]; return [@(floor([installDate timeIntervalSince1970] * 1000)) longLongValue]; } #pragma mark - dealloc - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end