GDTCORPlatform.m 23 KB

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