Skip to content

Commit

Permalink
Merge pull request #191 from shankari/improve_ios_onboarding
Browse files Browse the repository at this point in the history
Improve ios onboarding
  • Loading branch information
shankari authored Jun 5, 2021
2 parents f5d10bd + ff92e36 commit dc48073
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 55 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-em-datacollection",
"version": "1.5.0",
"version": "1.6.0",
"description": "The main tracking for the e-mission platform",
"license": "BSD-3-clause",
"cordova": {
Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
id="cordova-plugin-em-datacollection"
version="1.5.0">
version="1.6.0">

<name>DataCollection</name>
<description>Background data collection FTW! This is the part that I really
Expand Down
15 changes: 10 additions & 5 deletions res/ios/en.lproj/DCLocalizable.strings
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"dont-force-kill-please" = "Please don't force-kill. It actually increases battery drain because we don't get silent push notifications and can't stop tracking properly. Click to relaunch.";
"dont-force-kill-please" = "Please don't quit the app. Keep it running in the background. Click to relaunch.";
"new-data-collections-terms" = "New data collection terms - collection paused until consent";
"error-reading-activities" = "Error while reading activities";
"travel-mode-unavailable" = "Travel mode detection may be unavailable.";
"activity-detection-unsupported" = "Activity detection unsupported";
"activity-permission-problem" = "The app does not have permission to read your motion activities - automatic mode detection will not work. Please fix in Settings -> Privacy -> Motion & Fitness";
"activity-permission-problem" = "No 'Motion & Fitness' permission - automatic mode detection will not work. Turn it on (Settings -> app)";
"activity-turned-off-problem" = "Motion & Fitness Service disabled - automatic mode detection will not work. Turn it on (Settings -> Privacy)";
"travel-mode-unknown" = "Travel mode detection unavailable - all trips will be UNKNOWN.";
"bad-loc-tracking-problem" = "Background location accuracy is consistently poor - trip tracking may not work. Try to resolve the problem by turning location services, WiFi and cellular data off and on. If this message persists, please 'Email Log' for additional investigation";
"location-turned-off-problem" = "Location Services are turned off - trip tracking will not work. Try to resolve the problem by turning on location services (Settings -> Privacy)";
"location-permission-problem" = "The app does not have the 'always' permission - background trip tracking will not work. Please fix in Settings -> Privacy -> Location Services";
"bad-loc-tracking-problem" = "Background location accuracy is consistently poor - trip tracking may not work. Report problem.";
"location-turned-off-problem" = "Location Services are turned off - trip tracking will not work. Turn it on (Settings -> Privacy)";
"fix-service-action-button" = "Launch Settings";
"permission-problem-title" = "Incorrect permission";
"location-permission-problem" = "The app does not have the 'always' permission - background trip tracking will not work.";
"fix-permission-action-button" = "Fix permission";
"precise-location-problem" = "The app does not have permission to read 'precise' location - background trip tracking will not work.";
46 changes: 7 additions & 39 deletions src/ios/BEMDataCollection.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "DataUtils.h"
#import "StatsEvent.h"
#import "BEMBuiltinUserCache.h"
#import "TripDiarySettingsCheck.h"
#import <CoreLocation/CoreLocation.h>

@implementation BEMDataCollection
Expand Down Expand Up @@ -58,46 +59,13 @@ - (void)markConsented:(CDVInvokedUrlCommand*)command
[DataUtils dictToWrapper:newDict wrapper:newCfg];
[ConfigManager setConsented:newCfg];

// Refactored code for simplicity
// for the motion activity code, we just call checkMotionSettingsAndPermission directly
// but for location, there is an alternative to opening the settings, on iOS11 and iOS12,
// which is actually easier ("always allow")
// so in that case, we continue calling the init code in TripDiaryStateMachine
[self initWithConsent];

/*
* We don't strictly need to do this here since we don't use the data right now, but when we read
* the data to sync to the server. But doing it here allows us to notify users who don't have a sufficiently
* late model iPhone (https://github.com/e-mission/e-mission-phone/issues/60), and also gets all the notifications
* out of the way so that the user is not confronted with a random permission popup hours after installing the app.
* If/when we deal with users saying "no" to the permission prompts, it will make it easier to handle this in one
* place as well.
*/

if ([CMMotionActivityManager isActivityAvailable] == YES) {
CMMotionActivityManager* activityMgr = [[CMMotionActivityManager alloc] init];
NSOperationQueue* mq = [NSOperationQueue mainQueue];
NSDate* startDate = [NSDate new];
NSTimeInterval dayAgoSecs = 24 * 60 * 60;
NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow:-(dayAgoSecs)];
[activityMgr queryActivityStartingFromDate:startDate toDate:endDate toQueue:mq withHandler:^(NSArray *activities, NSError *error) {
if (error == nil) {
[LocalNotificationManager addNotification:@"activity recognition works fine"];
} else {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Error %@ while reading activities, travel mode detection may be unavailable", error]];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedStringFromTable(@"error-reading-activities", @"DCLocalizable", nil)
message:NSLocalizedStringFromTable(@"travel-mode-unavailable", @"DCLocalizable", nil)
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
}];
} else {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Activity detection unsupported, all trips will be UNKNOWN"]];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedStringFromTable(@"activity-detection-unsupported", @"DCLocalizable", nil)
message:NSLocalizedStringFromTable(@"travel-mode-unknown", @"DCLocalizable", nil)
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}

[TripDiarySettingsCheck checkMotionSettingsAndPermission:FALSE];
CDVPluginResult* result = [CDVPluginResult
resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
Expand Down
1 change: 0 additions & 1 deletion src/ios/ConfigManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,4 @@ + (void) setConsented:(ConsentConfig*)newConsent {
[[BuiltinUserCache database] putReadWriteDocument:CONSENT_CONFIG_KEY value:newConsent];
}


@end
15 changes: 14 additions & 1 deletion src/ios/Location/TripDiaryDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "LocationTrackingConfig.h"
#import "ConfigManager.h"
#import "BEMAppDelegate.h"
#import "TripDiarySettingsCheck.h"

#define ACCURACY_THRESHOLD 200

Expand Down Expand Up @@ -218,7 +219,19 @@ - (void)locationManager:(CLLocationManager *)manager

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

[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Calling TripDiarySettingsCheck from didChangeAuthorizationStatus to verify location service status and permission"]];

// This is currently a separate check here instead of being folded in with checkLocationSettingsAndPermission
// because the pre-iOS13 option to prompt the user requires a reference to the location manager
// and the background call to checkLocationSettingsAndPermission from the remote push code
// doesn't have that reference. Can simplify this after we stop supporting iOS13.
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
[TripDiarySettingsCheck promptForPermission:manager];
} else {
[TripDiarySettingsCheck checkLocationSettingsAndPermission:FALSE];
}

if (_tdsm.currState == kStartState) {
[[NSNotificationCenter defaultCenter] postNotificationName:CFCTransitionNotificationName
Expand Down
5 changes: 5 additions & 0 deletions src/ios/Location/TripDiarySettingsCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
@interface TripDiarySettingsCheck: NSObject

+(void)checkSettingsAndPermission;
+(void)checkLocationSettingsAndPermission:(BOOL)inBackground;
+(void)checkMotionSettingsAndPermission:(BOOL)inBackground;
+(void)promptForPermission:(CLLocationManager*)locMgr;
+(void)openAppSettings;
+(void)showSettingsAlert:(UIAlertController*)alert;

@end
118 changes: 118 additions & 0 deletions src/ios/Location/TripDiarySettingsCheck.m
Original file line number Diff line number Diff line change
@@ -1,25 +1,143 @@
#import "TripDiarySettingsCheck.h"
#import "LocalNotificationManager.h"
#import "BEMAppDelegate.h"
#import "BEMActivitySync.h"

#import <CoreMotion/CoreMotion.h>

@implementation TripDiarySettingsCheck

+(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"];
CMAuthorizationStatus currAuthStatus = [CMMotionActivityManager authorizationStatus];
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Auth status = %ld", currAuthStatus]];

if (currAuthStatus == CMAuthorizationStatusRestricted) {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Activity detection not enabled, prompting user to change Settings"]];
if (inBackground) {
NSString* errorDescription = NSLocalizedStringFromTable(@"activity-turned-off-problem", @"DCLocalizable", nil);
[LocalNotificationManager showNotificationAfterSecs:errorDescription withUserInfo:NULL secsLater:60];
}
[TripDiarySettingsCheck showLaunchSettingsAlert:@"activity-detection-unsupported" withMessage:@"activity-turned-off-problem" button:@"fix-service-action-button"];
}
if (currAuthStatus == CMAuthorizationStatusNotDetermined) {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Activity status not determined, initializing to get regular prompt"]];
[BEMActivitySync initWithConsent];
}
if ([CMMotionActivityManager authorizationStatus] == CMAuthorizationStatusDenied) {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Activity status denied, opening app settings to enable"]];
NSString* errorDescription = NSLocalizedStringFromTable(@"activity-permission-problem", @"DCLocalizable", nil);
if (inBackground) {
[LocalNotificationManager showNotificationAfterSecs:errorDescription withUserInfo:NULL secsLater:60];
}
[TripDiarySettingsCheck showLaunchSettingsAlert:@"permission-problem-title" withMessage:@"activity-permission-problem" button:@"fix-permission-action-button"];
}
} else {
[LocalNotificationManager addNotification:[NSString stringWithFormat:@"Activity detection unsupported, all trips will be UNKNOWN"]];
NSString* title = NSLocalizedStringFromTable(@"activity-detection-unsupported", @"DCLocalizable", nil);
NSString* message = NSLocalizedStringFromTable(@"travel-mode-unknown", @"DCLocalizable", nil);

UIAlertController* alert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:defaultAction];
[TripDiarySettingsCheck showSettingsAlert:alert];
}
}

+(void)showLaunchSettingsAlert:(NSString*)titleTag withMessage:(NSString*)messageTag button:(NSString*)buttonTag {
NSString* title = NSLocalizedStringFromTable(titleTag, @"DCLocalizable", nil);
NSString* message = NSLocalizedStringFromTable(messageTag, @"DCLocalizable", nil);
NSString* errorAction = NSLocalizedStringFromTable(buttonTag, @"DCLocalizable", nil);

UIAlertController* alert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:errorAction style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[TripDiarySettingsCheck openAppSettings];
}];
[alert addAction:defaultAction];
[TripDiarySettingsCheck showSettingsAlert:alert];
}

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

+(void) showSettingsAlert:(UIAlertController*)alert {
CDVAppDelegate *ad = [[UIApplication sharedApplication] delegate];
CDVViewController *vc = ad.viewController;
[vc presentViewController:alert animated:YES completion:nil];
}

@end
8 changes: 1 addition & 7 deletions src/ios/Location/TripDiaryStateMachine.m
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,7 @@ - (id) init {


if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedAlways) {
if ([CLLocationManager instancesRespondToSelector:@selector(requestAlwaysAuthorization)]) {
NSLog(@"Current location authorization = %d, always = %d, requesting always",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
[self.locMgr requestAlwaysAuthorization];
} else {
NSLog(@"Don't need to request authorization, system will automatically prompt for it");
}
[TripDiarySettingsCheck promptForPermission:self.locMgr];
} else {
NSLog(@"Current location authorization = %d, always = %d",
[CLLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways);
Expand Down

0 comments on commit dc48073

Please sign in to comment.