GDTCORPlatform.m 24 KB

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