158 lines
5.3 KiB
Objective-C
158 lines
5.3 KiB
Objective-C
// Copyright 2016-present 650 Industries. All rights reserved.
|
|
|
|
#import <ExpoLocation/EXBackgroundLocationPermissionRequester.h>
|
|
|
|
#import <objc/message.h>
|
|
#import <CoreLocation/CLLocationManagerDelegate.h>
|
|
|
|
static SEL alwaysAuthorizationSelector;
|
|
|
|
@interface EXBackgroundLocationPermissionRequester ()
|
|
|
|
@property (nonatomic, assign) bool wasAsked;
|
|
@property (nonatomic, assign) bool isWaitingForTimeout;
|
|
|
|
@end
|
|
|
|
@implementation EXBackgroundLocationPermissionRequester
|
|
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
_wasAsked = false;
|
|
_isWaitingForTimeout = false;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (NSString *)permissionType
|
|
{
|
|
return @"locationBackground";
|
|
}
|
|
|
|
+ (void)load
|
|
{
|
|
alwaysAuthorizationSelector = NSSelectorFromString([@"request" stringByAppendingString:@"AlwaysAuthorization"]);
|
|
}
|
|
|
|
- (void)requestLocationPermissions
|
|
{
|
|
if ([EXBaseLocationRequester isConfiguredForAlwaysAuthorization] && [self.locationManager respondsToSelector:alwaysAuthorizationSelector]) {
|
|
_wasAsked = true;
|
|
CLAuthorizationStatus status = [self.locationManager authorizationStatus];
|
|
|
|
if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
|
|
// We already have a foreground permission granted:
|
|
// When asking for background location, we might or might not have asked for foreground permission
|
|
// before we get here. An issue here is if the user has a temporary permission ("Allow once") - which
|
|
// results in the status being "kCLAuthorizationStatusAuthorizedWhenInUse" - without us knowing.
|
|
// We need to handle this special case which is not possible to detect through the API.
|
|
// What we do is that we'll wait 1.5 seconds on an UIApplicationWillResignActiveNotification
|
|
// notification (which will be emitted almost directly if the permission dialog is displayed). If the permission
|
|
// dialog is not displayed we'll timeout and can resolve the waiting promise with an updated denied status.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(handleAppBecomingInactive)
|
|
name:UIApplicationWillResignActiveNotification
|
|
object:nil];
|
|
|
|
// Setup timeout - if no permission dialog was displayed we can just stop listening and deny
|
|
// the request
|
|
[self setupAppInactivateTimeout];
|
|
}
|
|
|
|
// Request permissions
|
|
((void (*)(id, SEL))objc_msgSend)(self.locationManager, alwaysAuthorizationSelector);
|
|
} else {
|
|
self.reject(@"ERR_LOCATION_INFO_PLIST", @"One of the `NSLocation*UsageDescription` keys must be present in Info.plist to be able to use geolocation.", nil);
|
|
|
|
self.resolve = nil;
|
|
self.reject = nil;
|
|
}
|
|
}
|
|
|
|
- (void)handleAppBecomingActive
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
if (self.resolve) {
|
|
self.resolve([self getPermissions]);
|
|
self.resolve = nil;
|
|
self.reject = nil;
|
|
}
|
|
}
|
|
|
|
- (void)handleAppBecomingInactive
|
|
{
|
|
// Let's wait until the app becomes inactive - this happens when OS displays the
|
|
// permission dialog - then we can cancel the timeout handler.
|
|
|
|
_isWaitingForTimeout = false;
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
// When the app is inactive it means that a permission dialog is showing and we should ask to be
|
|
// notified when the dialog is closed:
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(handleAppBecomingActive)
|
|
name:UIApplicationDidBecomeActiveNotification
|
|
object:nil];
|
|
}
|
|
|
|
- (void)setupAppInactivateTimeout
|
|
{
|
|
_isWaitingForTimeout = true;
|
|
|
|
// Obtain a reference to the current queue
|
|
dispatch_queue_t currentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
|
|
|
// Calculate the time for the delay
|
|
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC));
|
|
|
|
EX_WEAKIFY(self);
|
|
|
|
// Schedule the block to be executed after the delay
|
|
dispatch_after(delayTime, currentQueue, ^{
|
|
EX_ENSURE_STRONGIFY(self)
|
|
// Check if we are still waiting - ie. we haven't seen a permission dialog
|
|
if (self.isWaitingForTimeout && self.resolve) {
|
|
self.isWaitingForTimeout = false;
|
|
self.resolve([self getPermissions]);
|
|
self.resolve = nil;
|
|
self.reject = nil;
|
|
}
|
|
});
|
|
}
|
|
|
|
- (NSDictionary *)parsePermissions:(CLAuthorizationStatus)systemStatus
|
|
{
|
|
EXPermissionStatus status;
|
|
|
|
switch (systemStatus) {
|
|
case kCLAuthorizationStatusAuthorizedAlways: {
|
|
status = EXPermissionStatusGranted;
|
|
break;
|
|
}
|
|
case kCLAuthorizationStatusDenied:
|
|
case kCLAuthorizationStatusRestricted: {
|
|
status = EXPermissionStatusDenied;
|
|
break;
|
|
}
|
|
case kCLAuthorizationStatusAuthorizedWhenInUse: {
|
|
if (_wasAsked) {
|
|
status = EXPermissionStatusDenied;
|
|
} else {
|
|
status = EXPermissionStatusUndetermined;
|
|
}
|
|
break;
|
|
}
|
|
case kCLAuthorizationStatusNotDetermined:
|
|
default: {
|
|
|
|
status = EXPermissionStatusUndetermined;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return @{ @"status": @(status), @"scope": @(systemStatus == kCLAuthorizationStatusAuthorizedWhenInUse ? "whenInUse" : systemStatus == kCLAuthorizationStatusAuthorizedAlways ? "always" : "none") };
|
|
}
|
|
|
|
@end
|