Skip to content

Commit

Permalink
implement "2 step" approach for location perms on iOS 13.4+
Browse files Browse the repository at this point in the history
e-mission/e-mission-docs#1094 (comment)

If on iOS 13.4 or higher, we will first ask for 'whenInUse' authorization. If the user accepts this, we will receive the authorization status change in didChangeAuthorizationStatus; at which point we will attempt to trigger another request, this time for 'always', which should show the user a second prompt. If that fails, we will navigate to the app settings.

This approach involved re-registering the foreground delegate from within that foreground delegate's handler, so I adjusted the way the delegate list is cleared, so as to not clear out the new additions.

Also adjusted the strings in plugin.xml to be more descriptive and transparent about what permissions are needed for tracking, as well as when and why.
  • Loading branch information
JGreenlee committed Oct 4, 2024
1 parent 6946f45 commit 987a432
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 21 deletions.
10 changes: 5 additions & 5 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,27 +212,27 @@
</config-file>

<config-file target="*-Info.plist" parent="NSLocationAlwaysAndWhenInUseUsageDescription">
<string>We use your data to create an automatic trip diary</string>
<string>OpenPATH requires "Precise" location access allowed "Always" to create an automatic trip diary</string>
</config-file>

<config-file target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">
<string>We use your data to create an automatic trip diary</string>
<string>OpenPATH also requires "Always" location access to track your trips in the background</string>
</config-file>

<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string>We use your data to create an automatic trip diary</string>
<string>OpenPATH requires "Precise" location access allowed "While Using App" to create an automatic trip diary</string>
</config-file>

<config-file target="*-Info.plist" parent="NSMotionUsageDescription">
<string>Our app uses the motion sensors to determine the transportation mode for the sections of your trip</string>
<string>OpenPATH uses your phone's motion sensors to determine the modes of transportation during your trips</string>
</config-file>

<config-file target="*-Info.plist" parent="ITSAppUsesNonExemptEncryption">
<false/>
</config-file>

<config-file target="*-Info.plist" parent="NSBluetoothAlwaysUsageDescription">
<string>We need Bluetooth access to interact with BLE beacons for the fleet version of the app.</string>
<string>OpenPATH requires Bluetooth access to interact with BLE beacons for the fleet version of the app</string>
</config-file>

<podspec>
Expand Down
57 changes: 41 additions & 16 deletions src/ios/Verification/SensorControlForegroundDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ - (void) didChangeAuthorizationStatus:(CLAuthorizationStatus)status
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_OK];
[commandDelegate sendPluginResult:result callbackId:callbackId];
} else if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
if (IsAtLeastiOSVersion(@"13.4")) {
NSLog(@"iOS >=13.4 detected and 'whenInUse' authorized, need second step to request 'always'");
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[[TripDiaryStateMachine instance].locMgr requestAlwaysAuthorization];
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateInactive) {
NSLog(@"App is active, i.e. request did not launch (happens if the user chose 'Allow Once' in step 1). Launching app settings to manually enable 'Always'");
[self openAppSettings];
}
}
} else {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"status %d != always %d, returning error", status, kCLAuthorizationStatusAuthorizedAlways]];
NSString* msg = NSLocalizedStringFromTable(@"location_permission_off_app_open", @"DCLocalizable", nil);
Expand Down Expand Up @@ -281,25 +291,39 @@ - (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings*)newSet
}

-(void)promptForPermission:(CLLocationManager*)locMgr {
if (IsAtLeastiOSVersion(@"13.0")) {
NSLog(@"iOS 13+ detected, launching UI settings to easily enable always");
if (IsAtLeastiOSVersion(@"13.4")) {
NSLog(@"iOS >=13.4 detected, using two-step approach of 'when in use' first and then 'always'");
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
NSLog(@"Current location authorization = %d, when in use = %d, requesting when in use",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedWhenInUse);
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[locMgr requestWhenInUseAuthorization];
} else if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) {
NSLog(@"Current location authorization = %d, always = %d, requesting always",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[locMgr requestAlwaysAuthorization];
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateInactive) {
NSLog(@"App is active, i.e. request did not launch (happens if the user chose 'Allow Once' in step 1). Launching app settings to manually enable 'Always'");
[self openAppSettings];
}
} else {
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[self openAppSettings];
}
} else if (IsAtLeastiOSVersion(@"13.0")) {
NSLog(@"iOS >=13,<13.4 detected, launching UI settings to manually 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];
[[TripDiaryStateMachine instance].locMgr startUpdatingLocation];
[self openAppSettings];
}
else {
} else {
NSLog(@"iOS <13 detected, simply requesting 'always'");
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
if ([CLLocationManager instancesRespondToSelector:@selector(requestAlwaysAuthorization)]) {
NSLog(@"Current location authorization = %d, always = %d, requesting always",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[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"];
}
NSLog(@"Current location authorization = %d, always = %d, requesting always",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
[[TripDiaryStateMachine delegate] registerForegroundDelegate:self];
[locMgr requestAlwaysAuthorization];
} 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];
Expand Down Expand Up @@ -346,11 +370,12 @@ - (void)locationManager:(CLLocationManager *)manager
if (foregroundDelegateList.count > 0) {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"%lu foreground delegates found, calling didChangeAuthorizationStatus to return the new value %d", (unsigned long)foregroundDelegateList.count, status]];

int originalDelegateCount = (int)foregroundDelegateList.count;
for (id currDelegate in foregroundDelegateList) {
[currDelegate didChangeAuthorizationStatus:(CLAuthorizationStatus)status];
}
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Notified all foreground delegates, removing all of them"]];
[foregroundDelegateList removeAllObjects];
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Notified all foreground delegates, removing %d delegates", originalDelegateCount]];
[foregroundDelegateList removeObjectsInRange:NSMakeRange(0, originalDelegateCount)];
} else {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"No foreground delegate found, calling SensorControlBackgroundChecker from didChangeAuthorizationStatus to verify location service status and permission"]];
[SensorControlBackgroundChecker checkAppState];
Expand Down

0 comments on commit 987a432

Please sign in to comment.