| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- /*
- * Copyright 2019 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORPlatform.h"
- #import <sys/sysctl.h>
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORAssert.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORReachability.h"
- #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
- #ifdef GDTCOR_VERSION
- #define STR(x) STR_EXPAND(x)
- #define STR_EXPAND(x) #x
- NSString *const kGDTCORVersion = @STR(GDTCOR_VERSION);
- #else
- NSString *const kGDTCORVersion = @"Unknown";
- #endif // GDTCOR_VERSION
- const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid = 0;
- NSString *const kGDTCORApplicationDidEnterBackgroundNotification =
- @"GDTCORApplicationDidEnterBackgroundNotification";
- NSString *const kGDTCORApplicationWillEnterForegroundNotification =
- @"GDTCORApplicationWillEnterForegroundNotification";
- NSString *const kGDTCORApplicationWillTerminateNotification =
- @"GDTCORApplicationWillTerminateNotification";
- NSURL *GDTCORRootDirectory(void) {
- static NSURL *GDTPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSString *cachePath =
- NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
- GDTPath =
- [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/google-sdks-events", cachePath]];
- GDTCORLogDebug(@"GDT's state will be saved to: %@", GDTPath);
- });
- NSError *error;
- [[NSFileManager defaultManager] createDirectoryAtPath:GDTPath.path
- withIntermediateDirectories:YES
- attributes:nil
- error:&error];
- GDTCORAssert(error == nil, @"There was an error creating GDT's path");
- return GDTPath;
- }
- BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags) {
- #if !TARGET_OS_WATCH
- BOOL reachable =
- (flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable;
- BOOL connectionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired) ==
- kSCNetworkReachabilityFlagsConnectionRequired;
- return reachable && !connectionRequired;
- #else
- return (flags & kGDTCORNetworkReachabilityFlagsReachable) ==
- kGDTCORNetworkReachabilityFlagsReachable;
- #endif
- }
- BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags) {
- #if TARGET_OS_IOS
- return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
- #else
- // Assume network connection not WWAN on macOS, tvOS, watchOS.
- return NO;
- #endif // TARGET_OS_IOS
- }
- GDTCORNetworkType GDTCORNetworkTypeMessage() {
- #if !TARGET_OS_WATCH
- SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags];
- if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) ==
- kSCNetworkReachabilityFlagsReachable) {
- if (GDTCORReachabilityFlagsContainWWAN(reachabilityFlags)) {
- return GDTCORNetworkTypeMobile;
- } else {
- return GDTCORNetworkTypeWIFI;
- }
- }
- #endif
- return GDTCORNetworkTypeUNKNOWN;
- }
- GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() {
- #if TARGET_OS_IOS
- static NSDictionary<NSString *, NSNumber *> *CTRadioAccessTechnologyToNetworkSubTypeMessage;
- static CTTelephonyNetworkInfo *networkInfo;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- CTRadioAccessTechnologyToNetworkSubTypeMessage = @{
- CTRadioAccessTechnologyGPRS : @(GDTCORNetworkMobileSubtypeGPRS),
- CTRadioAccessTechnologyEdge : @(GDTCORNetworkMobileSubtypeEdge),
- CTRadioAccessTechnologyWCDMA : @(GDTCORNetworkMobileSubtypeWCDMA),
- CTRadioAccessTechnologyHSDPA : @(GDTCORNetworkMobileSubtypeHSDPA),
- CTRadioAccessTechnologyHSUPA : @(GDTCORNetworkMobileSubtypeHSUPA),
- CTRadioAccessTechnologyCDMA1x : @(GDTCORNetworkMobileSubtypeCDMA1x),
- CTRadioAccessTechnologyCDMAEVDORev0 : @(GDTCORNetworkMobileSubtypeCDMAEVDORev0),
- CTRadioAccessTechnologyCDMAEVDORevA : @(GDTCORNetworkMobileSubtypeCDMAEVDORevA),
- CTRadioAccessTechnologyCDMAEVDORevB : @(GDTCORNetworkMobileSubtypeCDMAEVDORevB),
- CTRadioAccessTechnologyeHRPD : @(GDTCORNetworkMobileSubtypeHRPD),
- CTRadioAccessTechnologyLTE : @(GDTCORNetworkMobileSubtypeLTE),
- };
- networkInfo = [[CTTelephonyNetworkInfo alloc] init];
- });
- NSString *networkCurrentRadioAccessTechnology;
- #if TARGET_OS_MACCATALYST
- NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
- networkInfo.serviceCurrentRadioAccessTechnology;
- if (networkCurrentRadioAccessTechnologyDict.count) {
- networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
- }
- #else // TARGET_OS_MACCATALYST
- if (@available(iOS 12.0, *)) {
- NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
- networkInfo.serviceCurrentRadioAccessTechnology;
- if (networkCurrentRadioAccessTechnologyDict.count) {
- // In iOS 12, multiple radio technologies can be captured. We prefer not particular radio
- // tech to another, so we'll just return the first value in the dictionary.
- networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
- }
- } else {
- networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology;
- }
- #endif // TARGET_OS_MACCATALYST
- if (networkCurrentRadioAccessTechnology) {
- NSNumber *networkMobileSubtype =
- CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology];
- return networkMobileSubtype.intValue;
- } else {
- return GDTCORNetworkMobileSubtypeUNKNOWN;
- }
- #else
- return GDTCORNetworkMobileSubtypeUNKNOWN;
- #endif
- }
- NSString *_Nonnull GDTCORDeviceModel() {
- static NSString *deviceModel = @"Unknown";
- #if TARGET_OS_IOS || TARGET_OS_TV
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- size_t size;
- char *keyToExtract = "hw.machine";
- sysctlbyname(keyToExtract, NULL, &size, NULL, 0);
- if (size > 0) {
- char *machine = calloc(1, size);
- sysctlbyname(keyToExtract, machine, &size, NULL, 0);
- deviceModel = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
- free(machine);
- } else {
- deviceModel = [UIDevice currentDevice].model;
- }
- });
- #endif
- return deviceModel;
- }
- NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
- NSString *filePath,
- NSError *_Nullable *error) {
- BOOL result = NO;
- if (filePath.length > 0) {
- result = [[NSFileManager defaultManager]
- createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
- withIntermediateDirectories:YES
- attributes:nil
- error:error];
- if (result == NO || *error) {
- GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *error);
- return nil;
- }
- }
- NSData *resultData;
- if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
- resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
- requiringSecureCoding:YES
- error:error];
- if (resultData == nil || (error != NULL && *error != nil)) {
- GDTCORLogDebug(@"Encoding an object failed: %@", *error);
- return nil;
- }
- if (filePath.length > 0) {
- result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
- if (result == NO || *error) {
- GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error);
- } else {
- GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
- }
- }
- } else {
- @try {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- resultData = [NSKeyedArchiver archivedDataWithRootObject:obj];
- #pragma clang diagnostic pop
- if (filePath.length > 0) {
- result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
- if (result == NO || *error) {
- GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", filePath, *error);
- } else {
- GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
- }
- }
- } @catch (NSException *exception) {
- NSString *errorString =
- [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
- *error = [NSError errorWithDomain:NSCocoaErrorDomain
- code:-1
- userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
- }
- GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@",
- result ? @"YES" : @"NO", filePath, *error);
- }
- return resultData;
- }
- id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
- NSString *_Nullable archivePath,
- NSData *_Nullable archiveData,
- NSError *_Nullable *error) {
- id<NSSecureCoding> unarchivedObject = nil;
- if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
- NSData *data = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
- if (data) {
- unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass
- fromData:data
- error:error];
- }
- } else {
- @try {
- NSData *archivedData =
- archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
- #pragma clang diagnostic pop
- } @catch (NSException *exception) {
- NSString *errorString =
- [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
- *error = [NSError errorWithDomain:NSCocoaErrorDomain
- code:-1
- userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
- }
- }
- return unarchivedObject;
- }
- BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError) {
- BOOL result = NO;
- if (filePath.length > 0) {
- result = [[NSFileManager defaultManager]
- createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
- withIntermediateDirectories:YES
- attributes:nil
- error:outError];
- if (result == NO || *outError) {
- GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *outError);
- return result;
- }
- }
- if (filePath.length > 0) {
- result = [data writeToFile:filePath options:NSDataWritingAtomic error:outError];
- if (result == NO || *outError) {
- GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *outError);
- } else {
- GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
- }
- }
- return result;
- }
- @interface GDTCORApplication ()
- /**
- Private flag to match the existing `readonly` public flag. This will be accurate for all platforms,
- since we handle each platform's lifecycle notifications separately.
- */
- @property(atomic, readwrite) BOOL isRunningInBackground;
- @end
- @implementation GDTCORApplication
- #if TARGET_OS_WATCH
- /** A dispatch queue on which all task semaphores will populate and remove from
- * gBackgroundIdentifierToSemaphoreMap.
- */
- static dispatch_queue_t gSemaphoreQueue;
- /** For mapping backgroundIdentifier to task semaphore. */
- static NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *gBackgroundIdentifierToSemaphoreMap;
- #endif
- + (void)load {
- GDTCORLogDebug(
- @"%@", @"GDT is initializing. Please note that if you quit the app via the "
- "debugger and not through a lifecycle event, event data will remain on disk but "
- "storage won't have a reference to them since the singleton wasn't saved to disk.");
- #if TARGET_OS_IOS || TARGET_OS_TV
- // If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues.
- GDTCORFatalAssert(
- GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid,
- @"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same.");
- #endif
- [self sharedApplication];
- }
- + (void)initialize {
- #if TARGET_OS_WATCH
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- gSemaphoreQueue = dispatch_queue_create("com.google.GDTCORApplication", DISPATCH_QUEUE_SERIAL);
- GDTCORLogDebug(
- @"%@",
- @"GDTCORApplication is initializing on watchOS, gSemaphoreQueue has been initialized.");
- gBackgroundIdentifierToSemaphoreMap = [[NSMutableDictionary alloc] init];
- GDTCORLogDebug(@"%@", @"GDTCORApplication is initializing on watchOS, "
- @"gBackgroundIdentifierToSemaphoreMap has been initialized.");
- });
- #endif
- }
- + (nullable GDTCORApplication *)sharedApplication {
- static GDTCORApplication *application;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- application = [[GDTCORApplication alloc] init];
- });
- return application;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- // This class will be instantiated in the foreground.
- _isRunningInBackground = NO;
- #if TARGET_OS_IOS || TARGET_OS_TV
- NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationDidEnterBackground:)
- name:UIApplicationDidEnterBackgroundNotification
- object:nil];
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationWillEnterForeground:)
- name:UIApplicationWillEnterForegroundNotification
- object:nil];
- NSString *name = UIApplicationWillTerminateNotification;
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationWillTerminate:)
- name:name
- object:nil];
- #if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
- if (@available(iOS 13, tvOS 13.0, *)) {
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationWillEnterForeground:)
- name:UISceneWillEnterForegroundNotification
- object:nil];
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationDidEnterBackground:)
- name:UISceneWillDeactivateNotification
- object:nil];
- }
- #endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
- #elif TARGET_OS_OSX
- NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
- [notificationCenter addObserver:self
- selector:@selector(macOSApplicationWillTerminate:)
- name:NSApplicationWillTerminateNotification
- object:nil];
- #elif TARGET_OS_WATCH
- // TODO: Notification on watchOS platform is currently posted by strings which are frangible.
- // TODO: Needs improvements here.
- NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationDidEnterBackground:)
- name:@"UIApplicationDidEnterBackgroundNotification"
- object:nil];
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationWillEnterForeground:)
- name:@"UIApplicationWillEnterForegroundNotification"
- object:nil];
- // Adds observers for app extension on watchOS platform
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationDidEnterBackground:)
- name:NSExtensionHostDidEnterBackgroundNotification
- object:nil];
- [notificationCenter addObserver:self
- selector:@selector(iOSApplicationWillEnterForeground:)
- name:NSExtensionHostWillEnterForegroundNotification
- object:nil];
- #endif
- }
- return self;
- }
- #if TARGET_OS_WATCH
- /** Generates and maps a unique background identifier to the given semaphore.
- *
- * @param semaphore The semaphore to map.
- * @return A unique GDTCORBackgroundIdentifier mapped to the given semaphore.
- */
- + (GDTCORBackgroundIdentifier)createAndMapBackgroundIdentifierToSemaphore:
- (dispatch_semaphore_t)semaphore {
- __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
- dispatch_queue_t queue = gSemaphoreQueue;
- NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
- if (queue && map) {
- dispatch_sync(queue, ^{
- bgID = arc4random();
- NSNumber *bgIDNumber = @(bgID);
- while (bgID == GDTCORBackgroundIdentifierInvalid || map[bgIDNumber]) {
- bgID = arc4random();
- bgIDNumber = @(bgID);
- }
- map[bgIDNumber] = semaphore;
- });
- }
- return bgID;
- }
- /** Returns the semaphore mapped to given bgID and removes the value from the map.
- *
- * @param bgID The unique NSUInteger as GDTCORBackgroundIdentifier.
- * @return The semaphore mapped by given bgID.
- */
- + (dispatch_semaphore_t)semaphoreForBackgroundIdentifier:(GDTCORBackgroundIdentifier)bgID {
- __block dispatch_semaphore_t semaphore;
- dispatch_queue_t queue = gSemaphoreQueue;
- NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
- NSNumber *bgIDNumber = @(bgID);
- if (queue && map) {
- dispatch_sync(queue, ^{
- semaphore = map[bgIDNumber];
- [map removeObjectForKey:bgIDNumber];
- });
- }
- return semaphore;
- }
- #endif
- - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
- expirationHandler:(void (^)(void))handler {
- __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
- #if !TARGET_OS_WATCH
- bgID = [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name
- expirationHandler:handler];
- #if !NDEBUG
- if (bgID != GDTCORBackgroundIdentifierInvalid) {
- GDTCORLogDebug(@"Creating background task with name:%@ bgID:%ld", name, (long)bgID);
- }
- #endif // !NDEBUG
- #elif TARGET_OS_WATCH
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- bgID = [GDTCORApplication createAndMapBackgroundIdentifierToSemaphore:semaphore];
- if (bgID != GDTCORBackgroundIdentifierInvalid) {
- GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID);
- }
- [[self sharedNSProcessInfoForBackgroundTask]
- performExpiringActivityWithReason:name
- usingBlock:^(BOOL expired) {
- if (expired) {
- if (handler) {
- handler();
- }
- dispatch_semaphore_signal(semaphore);
- GDTCORLogDebug(
- @"Activity with name:%@ bgID:%ld on watchOS is expiring.",
- name, (long)bgID);
- } else {
- dispatch_semaphore_wait(
- semaphore,
- dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
- }
- }];
- #endif
- return bgID;
- }
- - (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
- #if !TARGET_OS_WATCH
- if (bgID != GDTCORBackgroundIdentifierInvalid) {
- GDTCORLogDebug(@"Ending background task with ID:%ld was successful", (long)bgID);
- [[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID];
- return;
- }
- #elif TARGET_OS_WATCH
- if (bgID != GDTCORBackgroundIdentifierInvalid) {
- dispatch_semaphore_t semaphore = [GDTCORApplication semaphoreForBackgroundIdentifier:bgID];
- GDTCORLogDebug(@"Ending activity with bgID:%ld on watchOS.", (long)bgID);
- if (semaphore) {
- dispatch_semaphore_signal(semaphore);
- GDTCORLogDebug(@"Signaling semaphore with bgID:%ld on watchOS.", (long)bgID);
- } else {
- GDTCORLogDebug(@"Semaphore with bgID:%ld is nil on watchOS.", (long)bgID);
- }
- }
- #endif // !TARGET_OS_WATCH
- }
- #pragma mark - App environment helpers
- - (BOOL)isAppExtension {
- BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
- return appExtension;
- }
- /** Returns a UIApplication or NSProcessInfo instance if on the appropriate platform.
- *
- * @return The shared UIApplication or NSProcessInfo if on the appropriate platform.
- */
- #if TARGET_OS_IOS || TARGET_OS_TV
- - (nullable UIApplication *)sharedApplicationForBackgroundTask {
- #elif TARGET_OS_WATCH
- - (nullable NSProcessInfo *)sharedNSProcessInfoForBackgroundTask {
- #else
- - (nullable id)sharedApplicationForBackgroundTask {
- #endif
- id sharedInstance = nil;
- #if TARGET_OS_IOS || TARGET_OS_TV
- if (![self isAppExtension]) {
- Class uiApplicationClass = NSClassFromString(@"UIApplication");
- if (uiApplicationClass &&
- [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
- sharedInstance = [uiApplicationClass sharedApplication];
- }
- }
- #elif TARGET_OS_WATCH
- sharedInstance = [NSProcessInfo processInfo];
- #endif
- return sharedInstance;
- }
- #pragma mark - UIApplicationDelegate and WKExtensionDelegate
- #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
- - (void)iOSApplicationDidEnterBackground:(NSNotification *)notif {
- _isRunningInBackground = YES;
- NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
- GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is backgrounding.");
- [notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil];
- }
- - (void)iOSApplicationWillEnterForeground:(NSNotification *)notif {
- _isRunningInBackground = NO;
- NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
- GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is foregrounding.");
- [notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil];
- }
- #endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
- #pragma mark - UIApplicationDelegate
- #if TARGET_OS_IOS || TARGET_OS_TV
- - (void)iOSApplicationWillTerminate:(NSNotification *)notif {
- NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
- GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
- [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
- }
- #endif // TARGET_OS_IOS || TARGET_OS_TV
- #pragma mark - NSApplicationDelegate
- #if TARGET_OS_OSX
- - (void)macOSApplicationWillTerminate:(NSNotification *)notif {
- NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
- GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
- [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
- }
- #endif // TARGET_OS_OSX
- @end
|