Skip to content

Commit

Permalink
Finish the full cycle for the location permissions
Browse files Browse the repository at this point in the history
- create a new category for the TripDiaryDelegate in the ForegroundDelegate
- since the trip diary state manager is singleton, its delegate is also a singleton
    - expose the singleton delegate
- register the foreground delegate as a callback to the TripDiaryDelegate from foreground calls
- if the callback is for a foreground call, return the appropriate response from the plugin
- if not, check the settings and generate a notification

Includes fixes to the boolean checks (using @(NO) in a boolean check always returns TRUE)
Includes copying over the `promptForPermissions` and `openAppStatus` methods.

Related design decisions:
- e-mission/e-mission-docs#680 (comment)
- e-mission/e-mission-docs#680 (comment)
- e-mission/e-mission-docs#680 (comment)
- e-mission/e-mission-docs#680 (comment)
  • Loading branch information
shankari committed Feb 12, 2022
1 parent b7ff5d6 commit ca979bd
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 58 deletions.
2 changes: 2 additions & 0 deletions src/ios/BEMDataCollection.m
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ - (void)isValidLocationSettings:(CDVInvokedUrlCommand*)command

- (void)fixLocationPermissions:(CDVInvokedUrlCommand*)command
{
[[[SensorControlForegroundDelegate alloc] initWithDelegate:self.commandDelegate
forCommand:command] checkAndPromptLocationPermissions];
}

- (void)isValidLocationPermissions:(CDVInvokedUrlCommand*)command
Expand Down
1 change: 1 addition & 0 deletions src/ios/Location/TripDiaryStateMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ typedef void(^GeofenceStatusCallback)(NSString* geofenceStatus);

@interface TripDiaryStateMachine : NSObject
+ (TripDiaryStateMachine*) instance;
+ (id) delegate;

-(void)registerForNotifications;

Expand Down
5 changes: 5 additions & 0 deletions src/ios/Location/TripDiaryStateMachine.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ + (TripDiaryStateMachine*) instance {
return sharedInstance;
}

+ (TripDiaryDelegate*) delegate {
return [self instance]->_locDelegate;
}


- (id) init {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

Expand Down
10 changes: 5 additions & 5 deletions src/ios/Verification/SensorControlBackgroundChecker.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ +(void)checkAppState
{
[LocalNotificationManager cancelNotification:OPEN_APP_STATUS_PAGE_ID];

NSArray* allChecks = @[
NSArray<NSNumber*>* allChecks = @[
@([TripDiarySensorControlChecks checkLocationSettings]),
@([TripDiarySensorControlChecks checkLocationPermissions]),
@([TripDiarySensorControlChecks checkMotionActivitySettings]),
@([TripDiarySensorControlChecks checkMotionActivityPermissions]),
@([TripDiarySensorControlChecks checkNotificationsEnabled])
];
BOOL allChecksPass = true;
for (id check in allChecks) {
allChecksPass = allChecksPass && check;
BOOL allChecksPass = TRUE;
for (NSNumber* check in allChecks) {
allChecksPass = allChecksPass && check.boolValue;
}

BOOL locChecksPass = allChecks[0] && allChecks[1];
BOOL locChecksPass = allChecks[0].boolValue && allChecks[1].boolValue;

if (allChecksPass) {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"All settings valid, nothing to prompt"]];
Expand Down
9 changes: 9 additions & 0 deletions src/ios/Verification/SensorControlForegroundDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#import <CoreLocation/CoreLocation.h>
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
#import "TripDiaryDelegate.h"

@interface SensorControlForegroundDelegate: NSObject

Expand All @@ -15,4 +16,12 @@

- (void) checkAndPromptLocationSettings;
- (void) checkAndPromptLocationPermissions;
- (void) didChangeAuthorizationStatus:(CLAuthorizationStatus)status;
@end

@interface TripDiaryDelegate (TripDiaryDelegatePermissions)
- (void)registerForegroundDelegate:(SensorControlForegroundDelegate*) foregroundDelegate;
- (void)locationManager:(CLLocationManager *)manager
didChangeAuthorizationStatus:(CLAuthorizationStatus)status;

@end
124 changes: 124 additions & 0 deletions src/ios/Verification/SensorControlForegroundDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,128 @@ -(void) checkAndPromptLocationSettings
[self sendCheckResult:result
errorKey:@"location-turned-off-problem"];
}

-(void) checkAndPromptLocationPermissions
{
NSString* callbackId = [command callbackId];
@try {
BOOL result = [TripDiarySensorControlChecks checkLocationPermissions];

[LocalNotificationManager addNotification:[NSString stringWithFormat:@"in checkLocationSettingsAndPermissions, locationService is enabled, but the permission is %d", [CLLocationManager authorizationStatus]]];

if (result) {
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_OK];
[commandDelegate sendPluginResult:result callbackId:callbackId];
} else {
[self promptForPermission:[TripDiaryStateMachine instance].locMgr];
}
}
@catch (NSException *exception) {
NSString* msg = [NSString stringWithFormat: @"While getting settings, error %@", exception];
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_ERROR
messageAsString:msg];
[commandDelegate sendPluginResult:result callbackId:callbackId];
}
}

- (void) didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
NSString* callbackId = [command callbackId];
@try {
if (status == kCLAuthorizationStatusAuthorizedAlways) {
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_OK];
[commandDelegate sendPluginResult:result callbackId:callbackId];
} else {
NSString* msg = NSLocalizedStringFromTable(@"location_permission_off_app_open", @"DCLocalizable", nil);
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_ERROR
messageAsString:msg];
[commandDelegate sendPluginResult:result callbackId:callbackId];
}
}
@catch (NSException *exception) {
NSString* msg = [NSString stringWithFormat: @"While handling auth callback, error %@", exception];
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_ERROR
messageAsString:msg];
[commandDelegate sendPluginResult:result callbackId:callbackId];
}

}

-(void)promptForPermission:(CLLocationManager*)locMgr {
if (IsAtLeastiOSVersion(@"13.0")) {
NSLog(@"iOS 13+ detected, launching UI settings to easily enable always");
// we want to leave the registration in the prompt for permission, since we don't want to register callbacks when we open the app settings for other reasons
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[self openAppSettings];
}
else {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
if ([CLLocationManager instancesRespondToSelector:@selector(requestAlwaysAuthorization)]) {
NSLog(@"Current location authorization = %d, always = %d, requesting always",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
[locMgr requestAlwaysAuthorization];
} else {
// TODO: should we remove this? Not sure when it will ever be called, given that
// requestAlwaysAuthorization is available in iOS8+
[LocalNotificationManager addNotification:@"Don't need to request authorization, system will automatically prompt for it"];
}
} else {
// we want to leave the registration in the prompt for permission, since we don't want to register callbacks when we open the app settings for other reasons
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[self openAppSettings];
}
}
}

-(void) openAppSettings {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) {
if (success) {
NSLog(@"Opened url");
} else {
NSLog(@"Failed open");
}}];
}

@end

@implementation TripDiaryDelegate (TripDiaryDelegatePermissions)

NSMutableArray* foregroundDelegateList;

/*
* This is a bit tricky since this function is called whenever the authorization is changed
* Design decisions are at:
* https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-1035972636
* https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-1035976420
* https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-1035984060
*/

- (void)registerForegroundDelegate:(SensorControlForegroundDelegate*) foregroundDelegate
{
if (foregroundDelegateList == nil) {
foregroundDelegateList = [NSMutableArray new];
}
[foregroundDelegateList addObject:foregroundDelegate];
}

- (void)locationManager:(CLLocationManager *)manager
didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"In checker's didChangeAuthorizationStatus, new authorization status = %d, always = %d", status, kCLAuthorizationStatusAuthorizedAlways]];

[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Calling TripDiarySettingsCheck from didChangeAuthorizationStatus to verify location service status and permission"]];
if (foregroundDelegateList.count > 0) {
for (id currDelegate in foregroundDelegateList) {
[currDelegate didChangeAuthorizationStatus:(CLAuthorizationStatus)status];
}
[foregroundDelegateList removeAllObjects];
} else {
[SensorControlBackgroundChecker checkAppState];
}
}

@end
54 changes: 1 addition & 53 deletions src/ios/Verification/TripDiarySensorControlChecks.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,65 +48,13 @@ +(UIUserNotificationSettings*) REQUESTED_NOTIFICATION_TYPES {
categories:nil];
}


/*
+(void)checkSettingsAndPermission {
[TripDiarySettingsCheck checkLocationSettingsAndPermission:TRUE];
[TripDiarySettingsCheck checkMotionSettingsAndPermission:TRUE];
}
+(void)checkLocationSettingsAndPermission:(BOOL)inBackground {
if (![CLLocationManager locationServicesEnabled]) {
// first, check to see if location services are enabled
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"in checkLocationSettingsAndPermissions, locationService is not enabled"]];
NSString* errorDescription = NSLocalizedStringFromTable(@"location-turned-off-problem", @"DCLocalizable", nil);
if (inBackground) {
[LocalNotificationManager showNotificationAfterSecs:errorDescription withUserInfo:NULL secsLater:60];
}
} else {
// next, check to see if it is "always"
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedAlways) {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"in checkLocationSettingsAndPermissions, locationService is enabled, but the permission is %d", [CLLocationManager authorizationStatus]]];
NSString* errorDescription = NSLocalizedStringFromTable(@"location-permission-problem", @"DCLocalizable", nil);
if (inBackground) {
[LocalNotificationManager showNotificationAfterSecs:errorDescription withUserInfo:NULL secsLater:60];
}
[TripDiarySettingsCheck showLaunchSettingsAlert:@"permission-problem-title" withMessage:@"location-permission-problem" button:@"fix-permission-action-button"];
} else {
// finally, check to see if it is "precise"
// we currently check these in a cascade, since generating multiple alerts results in
// "Attempt to present <UIAlertController: 0x7fd6c1018400> on <MainViewController: 0x7fd6e7c0a2a0> (from <MainViewController: 0x7fd6e7c0a2a0>) which is already presenting <UIAlertController: 0x7fd6e000ac00>."
CLLocationManager* currLocMgr = [TripDiaryStateMachine instance].locMgr;
if (@available(iOS 14.0, *)) {
CLAccuracyAuthorization preciseOrNot = [currLocMgr accuracyAuthorization];
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"in checkLocationSettingsAndPermissions, locationService is enabled, permission is 'always', accuracy status is %ld", preciseOrNot]];
if (preciseOrNot != CLAccuracyAuthorizationFullAccuracy) {
[TripDiarySettingsCheck showLaunchSettingsAlert:@"permission-problem-title" withMessage:@"precise-location-problem" button:@"fix-permission-action-button"];
}
} else {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"No precise location check needed for iOS < 14"]];
}
}
}
}
+(void)promptForPermission:(CLLocationManager*)locMgr {
if (IsAtLeastiOSVersion(@"13.0")) {
NSLog(@"iOS 13+ detected, launching UI settings to easily enable always");
[TripDiarySettingsCheck openAppSettings];
}
else if ([CLLocationManager instancesRespondToSelector:@selector(requestAlwaysAuthorization)]) {
NSLog(@"Current location authorization = %d, always = %d, requesting always",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
[locMgr requestAlwaysAuthorization];
} else {
// TODO: should we remove this? Not sure when it will ever be called, given that
// requestAlwaysAuthorization is available in iOS8+
[LocalNotificationManager addNotification:@"Don't need to request authorization, system will automatically prompt for it"];
}
}
+(void)checkMotionSettingsAndPermission:(BOOL)inBackground {
if ([CMMotionActivityManager isActivityAvailable] == YES) {
[LocalNotificationManager addNotification:@"Motion activity available, checking auth status"];
Expand Down

0 comments on commit ca979bd

Please sign in to comment.