GDTCORPlatform.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORPlatform.h"
  17. #import <sys/sysctl.h>
  18. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORAssert.h"
  19. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
  20. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORReachability.h"
  21. #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
  22. #ifdef GDTCOR_VERSION
  23. #define STR(x) STR_EXPAND(x)
  24. #define STR_EXPAND(x) #x
  25. NSString *const kGDTCORVersion = @STR(GDTCOR_VERSION);
  26. #else
  27. NSString *const kGDTCORVersion = @"Unknown";
  28. #endif // GDTCOR_VERSION
  29. const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid = 0;
  30. NSString *const kGDTCORApplicationDidEnterBackgroundNotification =
  31. @"GDTCORApplicationDidEnterBackgroundNotification";
  32. NSString *const kGDTCORApplicationWillEnterForegroundNotification =
  33. @"GDTCORApplicationWillEnterForegroundNotification";
  34. NSString *const kGDTCORApplicationWillTerminateNotification =
  35. @"GDTCORApplicationWillTerminateNotification";
  36. NSURL *GDTCORRootDirectory(void) {
  37. static NSURL *GDTPath;
  38. static dispatch_once_t onceToken;
  39. dispatch_once(&onceToken, ^{
  40. NSString *cachePath =
  41. NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
  42. GDTPath =
  43. [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/google-sdks-events", cachePath]];
  44. GDTCORLogDebug(@"GDT's state will be saved to: %@", GDTPath);
  45. });
  46. NSError *error;
  47. [[NSFileManager defaultManager] createDirectoryAtPath:GDTPath.path
  48. withIntermediateDirectories:YES
  49. attributes:nil
  50. error:&error];
  51. GDTCORAssert(error == nil, @"There was an error creating GDT's path");
  52. return GDTPath;
  53. }
  54. BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags) {
  55. #if !TARGET_OS_WATCH
  56. BOOL reachable =
  57. (flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable;
  58. BOOL connectionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired) ==
  59. kSCNetworkReachabilityFlagsConnectionRequired;
  60. return reachable && !connectionRequired;
  61. #else
  62. return (flags & kGDTCORNetworkReachabilityFlagsReachable) ==
  63. kGDTCORNetworkReachabilityFlagsReachable;
  64. #endif
  65. }
  66. BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags) {
  67. #if TARGET_OS_IOS
  68. return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
  69. #else
  70. // Assume network connection not WWAN on macOS, tvOS, watchOS.
  71. return NO;
  72. #endif // TARGET_OS_IOS
  73. }
  74. GDTCORNetworkType GDTCORNetworkTypeMessage() {
  75. #if !TARGET_OS_WATCH
  76. SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags];
  77. if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) ==
  78. kSCNetworkReachabilityFlagsReachable) {
  79. if (GDTCORReachabilityFlagsContainWWAN(reachabilityFlags)) {
  80. return GDTCORNetworkTypeMobile;
  81. } else {
  82. return GDTCORNetworkTypeWIFI;
  83. }
  84. }
  85. #endif
  86. return GDTCORNetworkTypeUNKNOWN;
  87. }
  88. GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() {
  89. #if TARGET_OS_IOS
  90. static NSDictionary<NSString *, NSNumber *> *CTRadioAccessTechnologyToNetworkSubTypeMessage;
  91. static CTTelephonyNetworkInfo *networkInfo;
  92. static dispatch_once_t onceToken;
  93. dispatch_once(&onceToken, ^{
  94. CTRadioAccessTechnologyToNetworkSubTypeMessage = @{
  95. CTRadioAccessTechnologyGPRS : @(GDTCORNetworkMobileSubtypeGPRS),
  96. CTRadioAccessTechnologyEdge : @(GDTCORNetworkMobileSubtypeEdge),
  97. CTRadioAccessTechnologyWCDMA : @(GDTCORNetworkMobileSubtypeWCDMA),
  98. CTRadioAccessTechnologyHSDPA : @(GDTCORNetworkMobileSubtypeHSDPA),
  99. CTRadioAccessTechnologyHSUPA : @(GDTCORNetworkMobileSubtypeHSUPA),
  100. CTRadioAccessTechnologyCDMA1x : @(GDTCORNetworkMobileSubtypeCDMA1x),
  101. CTRadioAccessTechnologyCDMAEVDORev0 : @(GDTCORNetworkMobileSubtypeCDMAEVDORev0),
  102. CTRadioAccessTechnologyCDMAEVDORevA : @(GDTCORNetworkMobileSubtypeCDMAEVDORevA),
  103. CTRadioAccessTechnologyCDMAEVDORevB : @(GDTCORNetworkMobileSubtypeCDMAEVDORevB),
  104. CTRadioAccessTechnologyeHRPD : @(GDTCORNetworkMobileSubtypeHRPD),
  105. CTRadioAccessTechnologyLTE : @(GDTCORNetworkMobileSubtypeLTE),
  106. };
  107. networkInfo = [[CTTelephonyNetworkInfo alloc] init];
  108. });
  109. NSString *networkCurrentRadioAccessTechnology;
  110. #if TARGET_OS_MACCATALYST
  111. NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
  112. networkInfo.serviceCurrentRadioAccessTechnology;
  113. if (networkCurrentRadioAccessTechnologyDict.count) {
  114. networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
  115. }
  116. #else // TARGET_OS_MACCATALYST
  117. #if defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
  118. if (@available(iOS 12.0, *)) {
  119. NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
  120. networkInfo.serviceCurrentRadioAccessTechnology;
  121. if (networkCurrentRadioAccessTechnologyDict.count) {
  122. // In iOS 12, multiple radio technologies can be captured. We prefer not particular radio
  123. // tech to another, so we'll just return the first value in the dictionary.
  124. networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
  125. }
  126. } else {
  127. #else // defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
  128. networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology;
  129. #endif // // defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
  130. }
  131. #endif // TARGET_OS_MACCATALYST
  132. if (networkCurrentRadioAccessTechnology) {
  133. NSNumber *networkMobileSubtype =
  134. CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology];
  135. return networkMobileSubtype.intValue;
  136. } else {
  137. return GDTCORNetworkMobileSubtypeUNKNOWN;
  138. }
  139. #else
  140. return GDTCORNetworkMobileSubtypeUNKNOWN;
  141. #endif
  142. }
  143. NSString *_Nonnull GDTCORDeviceModel() {
  144. static NSString *deviceModel = @"Unknown";
  145. #if TARGET_OS_IOS || TARGET_OS_TV
  146. static dispatch_once_t onceToken;
  147. dispatch_once(&onceToken, ^{
  148. size_t size;
  149. char *keyToExtract = "hw.machine";
  150. sysctlbyname(keyToExtract, NULL, &size, NULL, 0);
  151. if (size > 0) {
  152. char *machine = calloc(1, size);
  153. sysctlbyname(keyToExtract, machine, &size, NULL, 0);
  154. deviceModel = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
  155. free(machine);
  156. } else {
  157. deviceModel = [UIDevice currentDevice].model;
  158. }
  159. });
  160. #endif
  161. return deviceModel;
  162. }
  163. NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
  164. NSString *filePath,
  165. NSError *_Nullable *error) {
  166. BOOL result = NO;
  167. if (filePath.length > 0) {
  168. result = [[NSFileManager defaultManager]
  169. createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
  170. withIntermediateDirectories:YES
  171. attributes:nil
  172. error:error];
  173. if (result == NO || *error) {
  174. GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *error);
  175. return nil;
  176. }
  177. }
  178. NSData *resultData;
  179. #if (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
  180. (defined(__MAC_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
  181. (defined(__TVOS_11_0) && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
  182. (defined(__WATCHOS_4_0) && __WATCH_OS_VERSION_MAX_ALLOWED >= 040000) || \
  183. (defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST)
  184. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
  185. resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
  186. requiringSecureCoding:YES
  187. error:error];
  188. if (resultData == nil || (error != NULL && *error != nil)) {
  189. GDTCORLogDebug(@"Encoding an object failed: %@", *error);
  190. return nil;
  191. }
  192. if (filePath.length > 0) {
  193. result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
  194. if (result == NO || *error) {
  195. GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error);
  196. } else {
  197. GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
  198. }
  199. }
  200. } else {
  201. #endif
  202. @try {
  203. #pragma clang diagnostic push
  204. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  205. resultData = [NSKeyedArchiver archivedDataWithRootObject:obj];
  206. #pragma clang diagnostic pop
  207. if (filePath.length > 0) {
  208. result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
  209. if (result == NO || *error) {
  210. GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", filePath, *error);
  211. } else {
  212. GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
  213. }
  214. }
  215. } @catch (NSException *exception) {
  216. NSString *errorString =
  217. [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
  218. *error = [NSError errorWithDomain:NSCocoaErrorDomain
  219. code:-1
  220. userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
  221. }
  222. GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@",
  223. result ? @"YES" : @"NO", filePath, *error);
  224. }
  225. return resultData;
  226. }
  227. id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
  228. NSString *_Nullable archivePath,
  229. NSData *_Nullable archiveData,
  230. NSError *_Nullable *error) {
  231. id<NSSecureCoding> unarchivedObject = nil;
  232. #if (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
  233. (defined(__MAC_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
  234. (defined(__TVOS_11_0) && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
  235. (defined(__WATCHOS_4_0) && __WATCH_OS_VERSION_MAX_ALLOWED >= 040000) || \
  236. (defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST)
  237. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
  238. NSData *data = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
  239. if (data) {
  240. unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass
  241. fromData:data
  242. error:error];
  243. }
  244. } else {
  245. #endif
  246. @try {
  247. NSData *archivedData =
  248. archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
  249. #pragma clang diagnostic push
  250. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  251. unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
  252. #pragma clang diagnostic pop
  253. } @catch (NSException *exception) {
  254. NSString *errorString =
  255. [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
  256. *error = [NSError errorWithDomain:NSCocoaErrorDomain
  257. code:-1
  258. userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
  259. }
  260. }
  261. return unarchivedObject;
  262. }
  263. BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError) {
  264. BOOL result = NO;
  265. if (filePath.length > 0) {
  266. result = [[NSFileManager defaultManager]
  267. createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
  268. withIntermediateDirectories:YES
  269. attributes:nil
  270. error:outError];
  271. if (result == NO || *outError) {
  272. GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *outError);
  273. return result;
  274. }
  275. }
  276. if (filePath.length > 0) {
  277. result = [data writeToFile:filePath options:NSDataWritingAtomic error:outError];
  278. if (result == NO || *outError) {
  279. GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *outError);
  280. } else {
  281. GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
  282. }
  283. }
  284. return result;
  285. }
  286. @interface GDTCORApplication ()
  287. /**
  288. Private flag to match the existing `readonly` public flag. This will be accurate for all platforms,
  289. since we handle each platform's lifecycle notifications separately.
  290. */
  291. @property(atomic, readwrite) BOOL isRunningInBackground;
  292. @end
  293. @implementation GDTCORApplication
  294. #if TARGET_OS_WATCH
  295. /** A dispatch queue on which all task semaphores will populate and remove from
  296. * gBackgroundIdentifierToSemaphoreMap.
  297. */
  298. static dispatch_queue_t gSemaphoreQueue;
  299. /** For mapping backgroundIdentifier to task semaphore. */
  300. static NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *gBackgroundIdentifierToSemaphoreMap;
  301. #endif
  302. + (void)load {
  303. GDTCORLogDebug(
  304. @"%@", @"GDT is initializing. Please note that if you quit the app via the "
  305. "debugger and not through a lifecycle event, event data will remain on disk but "
  306. "storage won't have a reference to them since the singleton wasn't saved to disk.");
  307. #if TARGET_OS_IOS || TARGET_OS_TV
  308. // If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues.
  309. GDTCORFatalAssert(
  310. GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid,
  311. @"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same.");
  312. #endif
  313. [self sharedApplication];
  314. }
  315. + (void)initialize {
  316. #if TARGET_OS_WATCH
  317. static dispatch_once_t onceToken;
  318. dispatch_once(&onceToken, ^{
  319. gSemaphoreQueue = dispatch_queue_create("com.google.GDTCORApplication", DISPATCH_QUEUE_SERIAL);
  320. GDTCORLogDebug(
  321. @"%@",
  322. @"GDTCORApplication is initializing on watchOS, gSemaphoreQueue has been initialized.");
  323. gBackgroundIdentifierToSemaphoreMap = [[NSMutableDictionary alloc] init];
  324. GDTCORLogDebug(@"%@", @"GDTCORApplication is initializing on watchOS, "
  325. @"gBackgroundIdentifierToSemaphoreMap has been initialized.");
  326. });
  327. #endif
  328. }
  329. + (nullable GDTCORApplication *)sharedApplication {
  330. static GDTCORApplication *application;
  331. static dispatch_once_t onceToken;
  332. dispatch_once(&onceToken, ^{
  333. application = [[GDTCORApplication alloc] init];
  334. });
  335. return application;
  336. }
  337. - (instancetype)init {
  338. self = [super init];
  339. if (self) {
  340. // This class will be instantiated in the foreground.
  341. _isRunningInBackground = NO;
  342. #if TARGET_OS_IOS || TARGET_OS_TV
  343. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  344. [notificationCenter addObserver:self
  345. selector:@selector(iOSApplicationDidEnterBackground:)
  346. name:UIApplicationDidEnterBackgroundNotification
  347. object:nil];
  348. [notificationCenter addObserver:self
  349. selector:@selector(iOSApplicationWillEnterForeground:)
  350. name:UIApplicationWillEnterForegroundNotification
  351. object:nil];
  352. NSString *name = UIApplicationWillTerminateNotification;
  353. [notificationCenter addObserver:self
  354. selector:@selector(iOSApplicationWillTerminate:)
  355. name:name
  356. object:nil];
  357. #if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  358. if (@available(iOS 13, tvOS 13.0, *)) {
  359. [notificationCenter addObserver:self
  360. selector:@selector(iOSApplicationWillEnterForeground:)
  361. name:UISceneWillEnterForegroundNotification
  362. object:nil];
  363. [notificationCenter addObserver:self
  364. selector:@selector(iOSApplicationDidEnterBackground:)
  365. name:UISceneWillDeactivateNotification
  366. object:nil];
  367. }
  368. #endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  369. #elif TARGET_OS_OSX
  370. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  371. [notificationCenter addObserver:self
  372. selector:@selector(macOSApplicationWillTerminate:)
  373. name:NSApplicationWillTerminateNotification
  374. object:nil];
  375. #elif TARGET_OS_WATCH
  376. // TODO: Notification on watchOS platform is currently posted by strings which are frangible.
  377. // TODO: Needs improvements here.
  378. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  379. [notificationCenter addObserver:self
  380. selector:@selector(iOSApplicationDidEnterBackground:)
  381. name:@"UIApplicationDidEnterBackgroundNotification"
  382. object:nil];
  383. [notificationCenter addObserver:self
  384. selector:@selector(iOSApplicationWillEnterForeground:)
  385. name:@"UIApplicationWillEnterForegroundNotification"
  386. object:nil];
  387. // Adds observers for app extension on watchOS platform
  388. [notificationCenter addObserver:self
  389. selector:@selector(iOSApplicationDidEnterBackground:)
  390. name:NSExtensionHostDidEnterBackgroundNotification
  391. object:nil];
  392. [notificationCenter addObserver:self
  393. selector:@selector(iOSApplicationWillEnterForeground:)
  394. name:NSExtensionHostWillEnterForegroundNotification
  395. object:nil];
  396. #endif
  397. }
  398. return self;
  399. }
  400. #if TARGET_OS_WATCH
  401. /** Generates and maps a unique background identifier to the given semaphore.
  402. *
  403. * @param semaphore The semaphore to map.
  404. * @return A unique GDTCORBackgroundIdentifier mapped to the given semaphore.
  405. */
  406. + (GDTCORBackgroundIdentifier)createAndMapBackgroundIdentifierToSemaphore:
  407. (dispatch_semaphore_t)semaphore {
  408. __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
  409. dispatch_queue_t queue = gSemaphoreQueue;
  410. NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
  411. if (queue && map) {
  412. dispatch_sync(queue, ^{
  413. bgID = arc4random();
  414. NSNumber *bgIDNumber = @(bgID);
  415. while (bgID == GDTCORBackgroundIdentifierInvalid || map[bgIDNumber]) {
  416. bgID = arc4random();
  417. bgIDNumber = @(bgID);
  418. }
  419. map[bgIDNumber] = semaphore;
  420. });
  421. }
  422. return bgID;
  423. }
  424. /** Returns the semaphore mapped to given bgID and removes the value from the map.
  425. *
  426. * @param bgID The unique NSUInteger as GDTCORBackgroundIdentifier.
  427. * @return The semaphore mapped by given bgID.
  428. */
  429. + (dispatch_semaphore_t)semaphoreForBackgroundIdentifier:(GDTCORBackgroundIdentifier)bgID {
  430. __block dispatch_semaphore_t semaphore;
  431. dispatch_queue_t queue = gSemaphoreQueue;
  432. NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
  433. NSNumber *bgIDNumber = @(bgID);
  434. if (queue && map) {
  435. dispatch_sync(queue, ^{
  436. semaphore = map[bgIDNumber];
  437. [map removeObjectForKey:bgIDNumber];
  438. });
  439. }
  440. return semaphore;
  441. }
  442. #endif
  443. - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
  444. expirationHandler:(void (^)(void))handler {
  445. __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
  446. #if !TARGET_OS_WATCH
  447. bgID = [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name
  448. expirationHandler:handler];
  449. #if !NDEBUG
  450. if (bgID != GDTCORBackgroundIdentifierInvalid) {
  451. GDTCORLogDebug(@"Creating background task with name:%@ bgID:%ld", name, (long)bgID);
  452. }
  453. #endif // !NDEBUG
  454. #elif TARGET_OS_WATCH
  455. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  456. bgID = [GDTCORApplication createAndMapBackgroundIdentifierToSemaphore:semaphore];
  457. if (bgID != GDTCORBackgroundIdentifierInvalid) {
  458. GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID);
  459. }
  460. [[self sharedNSProcessInfoForBackgroundTask]
  461. performExpiringActivityWithReason:name
  462. usingBlock:^(BOOL expired) {
  463. if (expired) {
  464. if (handler) {
  465. handler();
  466. }
  467. dispatch_semaphore_signal(semaphore);
  468. GDTCORLogDebug(
  469. @"Activity with name:%@ bgID:%ld on watchOS is expiring.",
  470. name, (long)bgID);
  471. } else {
  472. dispatch_semaphore_wait(
  473. semaphore,
  474. dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
  475. }
  476. }];
  477. #endif
  478. return bgID;
  479. }
  480. - (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
  481. #if !TARGET_OS_WATCH
  482. if (bgID != GDTCORBackgroundIdentifierInvalid) {
  483. GDTCORLogDebug(@"Ending background task with ID:%ld was successful", (long)bgID);
  484. [[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID];
  485. return;
  486. }
  487. #elif TARGET_OS_WATCH
  488. if (bgID != GDTCORBackgroundIdentifierInvalid) {
  489. dispatch_semaphore_t semaphore = [GDTCORApplication semaphoreForBackgroundIdentifier:bgID];
  490. GDTCORLogDebug(@"Ending activity with bgID:%ld on watchOS.", (long)bgID);
  491. if (semaphore) {
  492. dispatch_semaphore_signal(semaphore);
  493. GDTCORLogDebug(@"Signaling semaphore with bgID:%ld on watchOS.", (long)bgID);
  494. } else {
  495. GDTCORLogDebug(@"Semaphore with bgID:%ld is nil on watchOS.", (long)bgID);
  496. }
  497. }
  498. #endif // !TARGET_OS_WATCH
  499. }
  500. #pragma mark - App environment helpers
  501. - (BOOL)isAppExtension {
  502. BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
  503. return appExtension;
  504. }
  505. /** Returns a UIApplication or NSProcessInfo instance if on the appropriate platform.
  506. *
  507. * @return The shared UIApplication or NSProcessInfo if on the appropriate platform.
  508. */
  509. #if TARGET_OS_IOS || TARGET_OS_TV
  510. - (nullable UIApplication *)sharedApplicationForBackgroundTask {
  511. #elif TARGET_OS_WATCH
  512. - (nullable NSProcessInfo *)sharedNSProcessInfoForBackgroundTask {
  513. #else
  514. - (nullable id)sharedApplicationForBackgroundTask {
  515. #endif
  516. id sharedInstance = nil;
  517. #if TARGET_OS_IOS || TARGET_OS_TV
  518. if (![self isAppExtension]) {
  519. Class uiApplicationClass = NSClassFromString(@"UIApplication");
  520. if (uiApplicationClass &&
  521. [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
  522. sharedInstance = [uiApplicationClass sharedApplication];
  523. }
  524. }
  525. #elif TARGET_OS_WATCH
  526. sharedInstance = [NSProcessInfo processInfo];
  527. #endif
  528. return sharedInstance;
  529. }
  530. #pragma mark - UIApplicationDelegate and WKExtensionDelegate
  531. #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
  532. - (void)iOSApplicationDidEnterBackground:(NSNotification *)notif {
  533. _isRunningInBackground = YES;
  534. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  535. GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is backgrounding.");
  536. [notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil];
  537. }
  538. - (void)iOSApplicationWillEnterForeground:(NSNotification *)notif {
  539. _isRunningInBackground = NO;
  540. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  541. GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is foregrounding.");
  542. [notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil];
  543. }
  544. #endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
  545. #pragma mark - UIApplicationDelegate
  546. #if TARGET_OS_IOS || TARGET_OS_TV
  547. - (void)iOSApplicationWillTerminate:(NSNotification *)notif {
  548. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  549. GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
  550. [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
  551. }
  552. #endif // TARGET_OS_IOS || TARGET_OS_TV
  553. #pragma mark - NSApplicationDelegate
  554. #if TARGET_OS_OSX
  555. - (void)macOSApplicationWillTerminate:(NSNotification *)notif {
  556. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  557. GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
  558. [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
  559. }
  560. #endif // TARGET_OS_OSX
  561. @end