CocosIOSPlatformBridge.mm 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. #import "CocosIOSPlatformBridge.h"
  2. #import <Security/Security.h>
  3. #import <UIKit/UIKit.h>
  4. #import <AppTrackingTransparency/AppTrackingTransparency.h>
  5. @implementation CocosIOSPlatformBridge
  6. + (void)requestTrackingAuthorization {
  7. dispatch_async(dispatch_get_main_queue(), ^{
  8. // All accesses are serialized on the main queue.
  9. static BOOL requestInFlight = NO;
  10. static id activeObserver = nil;
  11. if (@available(iOS 14, *)) {
  12. if (requestInFlight) return;
  13. NSString *usage = [NSBundle.mainBundle objectForInfoDictionaryKey:@"NSUserTrackingUsageDescription"];
  14. if (![usage isKindOfClass:NSString.class] || !usage.length) {
  15. NSLog(@"[ATT] Missing NSUserTrackingUsageDescription; request skipped.");
  16. return;
  17. }
  18. if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
  19. if (!activeObserver) {
  20. activeObserver = [NSNotificationCenter.defaultCenter
  21. addObserverForName:UIApplicationDidBecomeActiveNotification
  22. object:nil queue:NSOperationQueue.mainQueue
  23. usingBlock:^(NSNotification *notification) {
  24. [NSNotificationCenter.defaultCenter removeObserver:activeObserver];
  25. activeObserver = nil;
  26. [CocosIOSPlatformBridge requestTrackingAuthorization];
  27. }];
  28. }
  29. return;
  30. }
  31. if (activeObserver) {
  32. [NSNotificationCenter.defaultCenter removeObserver:activeObserver];
  33. activeObserver = nil;
  34. }
  35. ATTrackingManagerAuthorizationStatus status = ATTrackingManager.trackingAuthorizationStatus;
  36. if (status != ATTrackingManagerAuthorizationStatusNotDetermined) {
  37. NSLog(@"[ATT] Existing status: %ld", (long)status);
  38. return;
  39. }
  40. requestInFlight = YES;
  41. [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus result) {
  42. dispatch_async(dispatch_get_main_queue(), ^{
  43. requestInFlight = NO;
  44. // 0=notDetermined, 1=restricted, 2=denied, 3=authorized.
  45. NSLog(@"[ATT] Authorization status: %ld", (long)result);
  46. });
  47. }];
  48. } else {
  49. NSLog(@"[ATT] Not available before iOS 14.");
  50. }
  51. });
  52. }
  53. + (nullable NSString *)getDeviceID {
  54. @synchronized(self) {
  55. NSString *bundleID = NSBundle.mainBundle.bundleIdentifier;
  56. if (!bundleID.length) return nil;
  57. // Keep these keys stable between releases to preserve the identifier.
  58. NSDictionary *key = @{
  59. (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
  60. (__bridge id)kSecAttrService: [bundleID stringByAppendingString:@".device-identifier"],
  61. (__bridge id)kSecAttrAccount: @"deviceUUID.v1",
  62. (__bridge id)kSecAttrSynchronizable: @NO,
  63. };
  64. // Retry a read if another process created the item between our read/add.
  65. for (NSUInteger attempt = 0; attempt < 2; ++attempt) {
  66. NSMutableDictionary *query = [key mutableCopy];
  67. query[(__bridge id)kSecReturnData] = @YES;
  68. query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
  69. CFTypeRef result = NULL;
  70. OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  71. id data = CFBridgingRelease(result);
  72. if (status == errSecSuccess) {
  73. NSString *savedUUID = [data isKindOfClass:NSData.class]
  74. ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
  75. if (savedUUID.length && [[NSUUID alloc] initWithUUIDString:savedUUID]) {
  76. return savedUUID;
  77. }
  78. // A damaged value must not silently replace an existing identity.
  79. NSLog(@"[iOS DeviceID] Invalid Keychain UUID; keeping the existing item.");
  80. return nil;
  81. }
  82. if (status != errSecItemNotFound) {
  83. // Includes a locked/unavailable Keychain. Do not create a new ID.
  84. NSLog(@"[iOS DeviceID] Keychain read failed: %d", (int)status);
  85. return nil;
  86. }
  87. NSString *uuid = NSUUID.UUID.UUIDString;
  88. NSMutableDictionary *item = [key mutableCopy];
  89. item[(__bridge id)kSecValueData] = [uuid dataUsingEncoding:NSUTF8StringEncoding];
  90. // No iCloud sync or migration to another device through backups.
  91. item[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
  92. status = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
  93. if (status == errSecSuccess) return uuid;
  94. if (status != errSecDuplicateItem) {
  95. NSLog(@"[iOS DeviceID] Keychain write failed: %d", (int)status);
  96. return nil;
  97. }
  98. }
  99. NSLog(@"[iOS DeviceID] Keychain item changed concurrently; retry on next call.");
  100. return nil;
  101. }
  102. }
  103. @end