Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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