GDTCORPlatform.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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/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. #if !TARGET_OS_WATCH
  56. BOOL GDTCORReachabilityFlagsContainWWAN(SCNetworkReachabilityFlags flags) {
  57. #if TARGET_OS_IOS
  58. return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
  59. #else
  60. return NO;
  61. #endif // TARGET_OS_IOS
  62. }
  63. #endif // !TARGET_OS_WATCH
  64. GDTCORNetworkType GDTCORNetworkTypeMessage() {
  65. #if !TARGET_OS_WATCH
  66. SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags];
  67. if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) ==
  68. kSCNetworkReachabilityFlagsReachable) {
  69. if (GDTCORReachabilityFlagsContainWWAN(reachabilityFlags)) {
  70. return GDTCORNetworkTypeMobile;
  71. } else {
  72. return GDTCORNetworkTypeWIFI;
  73. }
  74. }
  75. #endif
  76. return GDTCORNetworkTypeUNKNOWN;
  77. }
  78. GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() {
  79. #if TARGET_OS_IOS
  80. static NSDictionary<NSString *, NSNumber *> *CTRadioAccessTechnologyToNetworkSubTypeMessage;
  81. static CTTelephonyNetworkInfo *networkInfo;
  82. static dispatch_once_t onceToken;
  83. dispatch_once(&onceToken, ^{
  84. CTRadioAccessTechnologyToNetworkSubTypeMessage = @{
  85. CTRadioAccessTechnologyGPRS : @(GDTCORNetworkMobileSubtypeGPRS),
  86. CTRadioAccessTechnologyEdge : @(GDTCORNetworkMobileSubtypeEdge),
  87. CTRadioAccessTechnologyWCDMA : @(GDTCORNetworkMobileSubtypeWCDMA),
  88. CTRadioAccessTechnologyHSDPA : @(GDTCORNetworkMobileSubtypeHSDPA),
  89. CTRadioAccessTechnologyHSUPA : @(GDTCORNetworkMobileSubtypeHSUPA),
  90. CTRadioAccessTechnologyCDMA1x : @(GDTCORNetworkMobileSubtypeCDMA1x),
  91. CTRadioAccessTechnologyCDMAEVDORev0 : @(GDTCORNetworkMobileSubtypeCDMAEVDORev0),
  92. CTRadioAccessTechnologyCDMAEVDORevA : @(GDTCORNetworkMobileSubtypeCDMAEVDORevA),
  93. CTRadioAccessTechnologyCDMAEVDORevB : @(GDTCORNetworkMobileSubtypeCDMAEVDORevB),
  94. CTRadioAccessTechnologyeHRPD : @(GDTCORNetworkMobileSubtypeHRPD),
  95. CTRadioAccessTechnologyLTE : @(GDTCORNetworkMobileSubtypeLTE),
  96. };
  97. networkInfo = [[CTTelephonyNetworkInfo alloc] init];
  98. });
  99. NSString *networkCurrentRadioAccessTechnology;
  100. #if TARGET_OS_MACCATALYST
  101. NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
  102. networkInfo.serviceCurrentRadioAccessTechnology;
  103. if (networkCurrentRadioAccessTechnologyDict.count) {
  104. networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
  105. }
  106. #else // TARGET_OS_MACCATALYST
  107. #if defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
  108. if (@available(iOS 12.0, *)) {
  109. NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
  110. networkInfo.serviceCurrentRadioAccessTechnology;
  111. if (networkCurrentRadioAccessTechnologyDict.count) {
  112. // In iOS 12, multiple radio technologies can be captured. We prefer not particular radio
  113. // tech to another, so we'll just return the first value in the dictionary.
  114. networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
  115. }
  116. } else {
  117. #else // defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
  118. networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology;
  119. #endif // // defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
  120. }
  121. #endif // TARGET_OS_MACCATALYST
  122. if (networkCurrentRadioAccessTechnology) {
  123. NSNumber *networkMobileSubtype =
  124. CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology];
  125. return networkMobileSubtype.intValue;
  126. } else {
  127. return GDTCORNetworkMobileSubtypeUNKNOWN;
  128. }
  129. #else
  130. return GDTCORNetworkMobileSubtypeUNKNOWN;
  131. #endif
  132. }
  133. NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
  134. NSString *archivePath,
  135. NSError *_Nullable *error) {
  136. NSData *resultData;
  137. #if (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
  138. (defined(__MAC_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
  139. (defined(__TVOS_11_0) && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
  140. (defined(__WATCHOS_4_0) && __WATCH_OS_VERSION_MAX_ALLOWED >= 040000) || \
  141. (defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST)
  142. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
  143. resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
  144. requiringSecureCoding:YES
  145. error:error];
  146. if (*error) {
  147. GDTCORLogDebug(@"Encoding an object failed: %@", *error);
  148. return nil;
  149. }
  150. if (archivePath) {
  151. BOOL result = [resultData writeToFile:archivePath options:NSDataWritingAtomic error:error];
  152. if (result == NO || *error) {
  153. GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", archivePath, *error);
  154. } else {
  155. GDTCORLogDebug(@"Writing archive succeeded: %@", archivePath);
  156. }
  157. }
  158. } else {
  159. #endif
  160. BOOL result = NO;
  161. @try {
  162. #pragma clang diagnostic push
  163. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  164. resultData = [NSKeyedArchiver archivedDataWithRootObject:obj];
  165. #pragma clang diagnostic pop
  166. if (archivePath) {
  167. result = [resultData writeToFile:archivePath options:NSDataWritingAtomic error:error];
  168. if (result == NO || *error) {
  169. GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", archivePath, *error);
  170. } else {
  171. GDTCORLogDebug(@"Writing archive succeeded: %@", archivePath);
  172. }
  173. }
  174. } @catch (NSException *exception) {
  175. NSString *errorString =
  176. [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
  177. *error = [NSError errorWithDomain:NSCocoaErrorDomain
  178. code:-1
  179. userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
  180. }
  181. GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@",
  182. result ? @"YES" : @"NO", archivePath, *error);
  183. }
  184. return resultData;
  185. }
  186. id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
  187. NSString *_Nullable archivePath,
  188. NSData *_Nullable archiveData,
  189. NSError *_Nullable *error) {
  190. id<NSSecureCoding> unarchivedObject = nil;
  191. #if (defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
  192. (defined(__MAC_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
  193. (defined(__TVOS_11_0) && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
  194. (defined(__WATCHOS_4_0) && __WATCH_OS_VERSION_MAX_ALLOWED >= 040000) || \
  195. (defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST)
  196. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
  197. NSData *data = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
  198. if (data) {
  199. unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass
  200. fromData:data
  201. error:error];
  202. }
  203. } else {
  204. #endif
  205. @try {
  206. NSData *archivedData =
  207. archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
  208. #pragma clang diagnostic push
  209. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  210. unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
  211. #pragma clang diagnostic pop
  212. } @catch (NSException *exception) {
  213. NSString *errorString =
  214. [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
  215. *error = [NSError errorWithDomain:NSCocoaErrorDomain
  216. code:-1
  217. userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
  218. }
  219. }
  220. return unarchivedObject;
  221. }
  222. @interface GDTCORApplication ()
  223. /**
  224. Private flag to match the existing `readonly` public flag. This will be accurate for all platforms,
  225. since we handle each platform's lifecycle notifications separately.
  226. */
  227. @property(atomic, readwrite) BOOL isRunningInBackground;
  228. @end
  229. @implementation GDTCORApplication
  230. + (void)load {
  231. GDTCORLogDebug(
  232. "%@", @"GDT is initializing. Please note that if you quit the app via the "
  233. "debugger and not through a lifecycle event, event data will remain on disk but "
  234. "storage won't have a reference to them since the singleton wasn't saved to disk.");
  235. #if TARGET_OS_IOS || TARGET_OS_TV
  236. // If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues.
  237. GDTCORFatalAssert(
  238. GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid,
  239. @"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same.");
  240. #endif
  241. [self sharedApplication];
  242. }
  243. + (nullable GDTCORApplication *)sharedApplication {
  244. static GDTCORApplication *application;
  245. static dispatch_once_t onceToken;
  246. dispatch_once(&onceToken, ^{
  247. application = [[GDTCORApplication alloc] init];
  248. });
  249. return application;
  250. }
  251. - (instancetype)init {
  252. self = [super init];
  253. if (self) {
  254. // This class will be instantiated in the foreground.
  255. _isRunningInBackground = NO;
  256. #if TARGET_OS_IOS || TARGET_OS_TV
  257. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  258. [notificationCenter addObserver:self
  259. selector:@selector(iOSApplicationDidEnterBackground:)
  260. name:UIApplicationDidEnterBackgroundNotification
  261. object:nil];
  262. [notificationCenter addObserver:self
  263. selector:@selector(iOSApplicationWillEnterForeground:)
  264. name:UIApplicationWillEnterForegroundNotification
  265. object:nil];
  266. NSString *name = UIApplicationWillTerminateNotification;
  267. [notificationCenter addObserver:self
  268. selector:@selector(iOSApplicationWillTerminate:)
  269. name:name
  270. object:nil];
  271. #if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  272. if (@available(iOS 13, tvOS 13.0, *)) {
  273. [notificationCenter addObserver:self
  274. selector:@selector(iOSApplicationWillEnterForeground:)
  275. name:UISceneWillEnterForegroundNotification
  276. object:nil];
  277. [notificationCenter addObserver:self
  278. selector:@selector(iOSApplicationDidEnterBackground:)
  279. name:UISceneWillDeactivateNotification
  280. object:nil];
  281. }
  282. #endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  283. #elif TARGET_OS_OSX
  284. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  285. [notificationCenter addObserver:self
  286. selector:@selector(macOSApplicationWillTerminate:)
  287. name:NSApplicationWillTerminateNotification
  288. object:nil];
  289. #endif // TARGET_OS_IOS || TARGET_OS_TV
  290. }
  291. return self;
  292. }
  293. - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
  294. expirationHandler:(void (^)(void))handler {
  295. GDTCORBackgroundIdentifier bgID =
  296. [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name
  297. expirationHandler:handler];
  298. #if !NDEBUG
  299. if (bgID != GDTCORBackgroundIdentifierInvalid) {
  300. GDTCORLogDebug("Creating background task with name:%@ bgID:%ld", name, (long)bgID);
  301. }
  302. #endif // !NDEBUG
  303. return bgID;
  304. }
  305. - (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
  306. if (bgID != GDTCORBackgroundIdentifierInvalid) {
  307. GDTCORLogDebug("Ending background task with ID:%ld was successful", (long)bgID);
  308. [[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID];
  309. return;
  310. }
  311. }
  312. #pragma mark - App environment helpers
  313. - (BOOL)isAppExtension {
  314. #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
  315. BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
  316. return appExtension;
  317. #elif TARGET_OS_OSX
  318. return NO;
  319. #endif
  320. }
  321. /** Returns a UIApplication instance if on the appropriate platform.
  322. *
  323. * @return The shared UIApplication if on the appropriate platform.
  324. */
  325. #if TARGET_OS_IOS || TARGET_OS_TV
  326. - (nullable UIApplication *)sharedApplicationForBackgroundTask {
  327. #else
  328. - (nullable id)sharedApplicationForBackgroundTask {
  329. #endif
  330. if ([self isAppExtension]) {
  331. return nil;
  332. }
  333. id sharedApplication = nil;
  334. Class uiApplicationClass = NSClassFromString(@"UIApplication");
  335. if (uiApplicationClass &&
  336. [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
  337. sharedApplication = [uiApplicationClass sharedApplication];
  338. }
  339. return sharedApplication;
  340. }
  341. #pragma mark - UIApplicationDelegate
  342. #if TARGET_OS_IOS || TARGET_OS_TV
  343. - (void)iOSApplicationDidEnterBackground:(NSNotification *)notif {
  344. _isRunningInBackground = YES;
  345. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  346. GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is backgrounding.");
  347. [notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil];
  348. }
  349. - (void)iOSApplicationWillEnterForeground:(NSNotification *)notif {
  350. _isRunningInBackground = NO;
  351. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  352. GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is foregrounding.");
  353. [notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil];
  354. }
  355. - (void)iOSApplicationWillTerminate:(NSNotification *)notif {
  356. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  357. GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
  358. [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
  359. }
  360. #endif // TARGET_OS_IOS || TARGET_OS_TV
  361. #pragma mark - NSApplicationDelegate
  362. #if TARGET_OS_OSX
  363. - (void)macOSApplicationWillTerminate:(NSNotification *)notif {
  364. NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
  365. GDTCORLogDebug("%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
  366. [notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
  367. }
  368. #endif // TARGET_OS_OSX
  369. @end