Files
firebase-ios-sdk/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m
T
Michael Haney 4e900ca551 Address GDT test issues (#5186)
* Address GDT test issues

1. Fixes the event  generator such that derived event fileURL will now be non-nil
2. Removes reachability check for integration tests since being locally connected via LAN but not connected to the internet would cause the test disabling check to pass. Instead, we attempt an actual NSURLRequest to a WAN target and set the value based on the success of that request.

* Create GDT's directory on first usage
2020-03-24 17:35:33 -07:00

408 lines
17 KiB
Objective-C

/*
* 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/GDTCORPlatform.h>
#import <GoogleDataTransport/GDTCORAssert.h>
#import <GoogleDataTransport/GDTCORConsoleLogger.h>
#import <GoogleDataTransport/GDTCORReachability.h>
#import "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);
if (![[NSFileManager defaultManager] fileExistsAtPath:GDTPath.path]) {
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;
}
#if !TARGET_OS_WATCH
BOOL GDTCORReachabilityFlagsContainWWAN(SCNetworkReachabilityFlags flags) {
#if TARGET_OS_IOS
return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
#else
return NO;
#endif // TARGET_OS_IOS
}
#endif // !TARGET_OS_WATCH
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 defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
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 {
#else // defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology;
#endif // // defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
}
#endif // TARGET_OS_MACCATALYST
if (networkCurrentRadioAccessTechnology) {
NSNumber *networkMobileSubtype =
CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology];
return networkMobileSubtype.intValue;
} else {
return GDTCORNetworkMobileSubtypeUNKNOWN;
}
#else
return GDTCORNetworkMobileSubtypeUNKNOWN;
#endif
}
NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
NSString *archivePath,
NSError *_Nullable *error) {
NSData *resultData;
#if (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
(defined(__MAC_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
(defined(__TVOS_11_0) && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
(defined(__WATCHOS_4_0) && __WATCH_OS_VERSION_MAX_ALLOWED >= 040000) || \
(defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST)
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
requiringSecureCoding:YES
error:error];
if (*error) {
GDTCORLogDebug(@"Encoding an object failed: %@", *error);
return nil;
}
if (archivePath) {
BOOL result = [resultData writeToFile:archivePath options:NSDataWritingAtomic error:error];
if (result == NO || *error) {
GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", archivePath, *error);
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", archivePath);
}
}
} else {
#endif
BOOL result = NO;
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj];
#pragma clang diagnostic pop
if (archivePath) {
result = [resultData writeToFile:archivePath options:NSDataWritingAtomic error:error];
if (result == NO || *error) {
GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", archivePath, *error);
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", archivePath);
}
}
} @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", archivePath, *error);
}
return resultData;
}
id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
NSString *_Nullable archivePath,
NSData *_Nullable archiveData,
NSError *_Nullable *error) {
id<NSSecureCoding> unarchivedObject = nil;
#if (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
(defined(__MAC_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
(defined(__TVOS_11_0) && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
(defined(__WATCHOS_4_0) && __WATCH_OS_VERSION_MAX_ALLOWED >= 040000) || \
(defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST)
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 {
#endif
@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;
}
@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
+ (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];
}
+ (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];
#endif // TARGET_OS_IOS || TARGET_OS_TV
}
return self;
}
- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
expirationHandler:(void (^)(void))handler {
GDTCORBackgroundIdentifier 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
return bgID;
}
- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug("Ending background task with ID:%ld was successful", (long)bgID);
[[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID];
return;
}
}
#pragma mark - App environment helpers
- (BOOL)isAppExtension {
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
return appExtension;
#elif TARGET_OS_OSX
return NO;
#endif
}
/** Returns a UIApplication instance if on the appropriate platform.
*
* @return The shared UIApplication if on the appropriate platform.
*/
#if TARGET_OS_IOS || TARGET_OS_TV
- (nullable UIApplication *)sharedApplicationForBackgroundTask {
#else
- (nullable id)sharedApplicationForBackgroundTask {
#endif
if ([self isAppExtension]) {
return nil;
}
id sharedApplication = nil;
Class uiApplicationClass = NSClassFromString(@"UIApplication");
if (uiApplicationClass &&
[uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
sharedApplication = [uiApplicationClass sharedApplication];
}
return sharedApplication;
}
#pragma mark - UIApplicationDelegate
#if TARGET_OS_IOS || TARGET_OS_TV
- (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];
}
- (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