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