diff --git a/res/android/values/dc_strings.xml b/res/android/values/dc_strings.xml index 4b76e9db..ffb0085d 100644 --- a/res/android/values/dc_strings.xml +++ b/res/android/values/dc_strings.xml @@ -24,9 +24,14 @@ Unknown error while reading location, please check your settings In state %1$s - Foreground service killed, Profile -> Email Log for debugging + Foreground service killed, report at Profile -> Upload Log Error reading stored tracking config, reset to defaults - Unrecoverable error in tracking. Profile -> Email Log for further investigation + Unrecoverable error in tracking, report at Profile -> Upload Log + + Ready for your next trip + Yay! You are on a trip, keep going! + Cannot start app, see next pop up + Stopped tracking; remember to re-enable diff --git a/src/android/DataCollectionPlugin.java b/src/android/DataCollectionPlugin.java index 53304015..d399f918 100644 --- a/src/android/DataCollectionPlugin.java +++ b/src/android/DataCollectionPlugin.java @@ -161,8 +161,13 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (data == null) { + Log.d(cordova.getActivity(), TAG, "received onActivityResult(" + requestCode + "," + + resultCode + "," + "null data" + ")"); + } else { Log.d(cordova.getActivity(), TAG, "received onActivityResult("+requestCode+","+ resultCode+","+data.getDataString()+")"); + } // This will be a NOP if we are not handling the correct activity intent mControlDelegate.onActivityResult(requestCode, resultCode, data); /* diff --git a/src/android/location/ForegroundServiceComm.java b/src/android/location/ForegroundServiceComm.java index 3a15c681..bce42e37 100644 --- a/src/android/location/ForegroundServiceComm.java +++ b/src/android/location/ForegroundServiceComm.java @@ -20,27 +20,27 @@ public class ForegroundServiceComm { private TripDiaryStateMachineForegroundService mService; private boolean mBound = false; private int nRecursion = 0; - private String pendingMsg = null; + private String pendingMsgState = null; public ForegroundServiceComm(Context context) { mCtxt = context; mCtxt.bindService(getForegroundServiceIntent(), connection, 0); } - public void setMessage(String msg) { + public void setNewState(String newState) { nRecursion++; if (mBound) { Log.d(mCtxt, TAG, "Service successfully bound, setting state"); - mService.setStateMessage(msg); + mService.setStateMessage(newState); } else { Intent fsi = getForegroundServiceIntent(); if (nRecursion < 5) { Log.d(mCtxt, TAG, "nRecursion = "+nRecursion+" rebinding "); - pendingMsg = msg; + pendingMsgState = newState; mCtxt.bindService(fsi, connection, 0); } else if (nRecursion < 10) { Log.d(mCtxt, TAG, "nRecursion = "+nRecursion+" restarting before rebind "); - pendingMsg = msg; + pendingMsgState = newState; TripDiaryStateMachineForegroundService.startProperly(mCtxt); mCtxt.bindService(fsi, connection, 0); } else { @@ -67,8 +67,8 @@ public void onServiceConnected(ComponentName className, mService = binder.getService(); Log.e(mCtxt, TAG, "Successfully bound to service "+ mService); mBound = true; - if (pendingMsg != null) { - setMessage(pendingMsg); + if (pendingMsgState != null) { + setNewState(pendingMsgState); } } diff --git a/src/android/location/TripDiaryStateMachineForegroundService.java b/src/android/location/TripDiaryStateMachineForegroundService.java index c618ed53..8ce73e9e 100644 --- a/src/android/location/TripDiaryStateMachineForegroundService.java +++ b/src/android/location/TripDiaryStateMachineForegroundService.java @@ -46,14 +46,33 @@ public void onCreate() { Log.d(this, TAG, "onCreate called"); } + /* + * Convert the current state, which is a string (e.g. + * waiting_for_trip_start) into a more human-friendly string (e.g. Ready to + * go). Since the string mappings expect an id, we need to look up the ID + * first using getResources and then the string. + * https://stackoverflow.com/a/19093447/4040267 + * Fallback to the old "In state XXX" if there is no mapping because + * otherwise the app will crash. + */ + public static String humanizeState(Context ctxt, String state) { + try { + int resId = ctxt.getResources().getIdentifier(state, "string", ctxt.getPackageName()); + return ctxt.getString(resId); + } catch (android.content.res.Resources.NotFoundException e) { + Log.e(ctxt, TAG, "ResourcesNotFoundException while humanizing message, falling back to auto-generated"); + return ctxt.getString(R.string.notify_curr_state, state); + } + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(this, TAG, "onStartCommand called with intent = "+intent+ " flags = " + flags + " and startId = " + startId); - String message = this.getString(R.string.notify_curr_state, TripDiaryStateMachineService.getState(this)); + String message = humanizeState(this, TripDiaryStateMachineService.getState(this)); if (intent == null) { SensorControlBackgroundChecker.checkLocationSettingsAndPermissions(this); - message = this.getString(R.string.notify_curr_state, TripDiaryStateMachineService.getState(this)); + message = humanizeState(this, TripDiaryStateMachineService.getState(this)); } handleStart(message, intent, flags, startId); // We want this service to continue running until it is explicitly @@ -103,7 +122,8 @@ public IBinder onBind(Intent intent) { return mBinder; } - public void setStateMessage(String message) { + public void setStateMessage(String newState) { + String message = humanizeState(this, newState); NotificationManager nMgr = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE); nMgr.notify(ONGOING_TRIP_ID, getNotification(message)); } diff --git a/src/android/location/TripDiaryStateMachineService.java b/src/android/location/TripDiaryStateMachineService.java index 5838b0fc..b65ee627 100644 --- a/src/android/location/TripDiaryStateMachineService.java +++ b/src/android/location/TripDiaryStateMachineService.java @@ -110,7 +110,7 @@ public void setNewState(String newState, boolean doChecks) { Log.d(this, TAG, "newState saved in prefManager is "+ PreferenceManager.getDefaultSharedPreferences(this).getString( this.getString(R.string.curr_state_key), "not found")); - mComm.setMessage(this.getString(R.string.notify_curr_state, newState)); + mComm.setNewState(newState); // Let's check the location settings every time we change the state instead of only on failure // This makes the rest of the code much simpler, allows us to catch issues as quickly as possible, // and diff --git a/src/android/location/TripDiaryStateMachineServiceOngoing.java b/src/android/location/TripDiaryStateMachineServiceOngoing.java index 22d2137e..af2019ad 100644 --- a/src/android/location/TripDiaryStateMachineServiceOngoing.java +++ b/src/android/location/TripDiaryStateMachineServiceOngoing.java @@ -114,7 +114,7 @@ public void setNewState(String newState) { Log.d(this, TAG, "newState saved in prefManager is "+ PreferenceManager.getDefaultSharedPreferences(this).getString( this.getString(R.string.curr_state_key), "not found")); - mComm.setMessage(this.getString(R.string.notify_curr_state, newState)); + mComm.setNewState(newState); stopSelf(); } diff --git a/src/android/verification/SensorControlForegroundDelegate.java b/src/android/verification/SensorControlForegroundDelegate.java index b66dffc2..ff317238 100644 --- a/src/android/verification/SensorControlForegroundDelegate.java +++ b/src/android/verification/SensorControlForegroundDelegate.java @@ -23,7 +23,10 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; +import android.provider.Settings; + import com.google.android.gms.location.LocationSettingsStates; @@ -48,10 +51,34 @@ public void checkAndPromptPermissions() { } private void checkAndPromptLocationPermissions() { - if(cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION) && cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION)) { + if(cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION) && + cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION)) { SensorControlBackgroundChecker.restartFSMIfStartState(cordova.getActivity()); return; } + // If this is android 11 (API 30), we want to launch the app settings instead of prompting for permission + // because the default permission prompting does not offer "always" as an option + // https://github.com/e-mission/e-mission-docs/issues/608 + // we don't really care about which level of permission is missing since the prompt doesn't + // do anything anyway. If either permission is missing, we just open the app settings + // Note also that we should actually check for VERSION_CODES.R + // but since we are not targeting API 30 yet, we can't do that + // so we use Q (29) + 1 instead. I think that is more readable than 30 + if ((Build.VERSION.SDK_INT >= (Build.VERSION_CODES.Q + 1)) && + (!cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION) || + !cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION))) { + Activity mAct = cordova.getActivity(); + String msgString = " LOC = "+cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION)+ + " BACKGROUND LOC "+ cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION)+ + " Android R+, so opening app settings anyway"; + Log.i(cordova.getActivity(), TAG, msgString); + // These are to hopefully help us get a callback once the settings are changed + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", mAct.getPackageName(), null)); + cordova.setActivityResultCallback(plugin); + mAct.startActivityForResult(intent, SensorControlConstants.ENABLE_BOTH_PERMISSION); + return; + } if(!cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) && !cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION)) { @@ -174,9 +201,9 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, public void onActivityResult(int requestCode, int resultCode, Intent data) { + Activity mAct = cordova.getActivity(); switch (requestCode) { case SensorControlConstants.ENABLE_LOCATION_SETTINGS: - Activity mAct = cordova.getActivity(); Log.d(mAct, TAG, requestCode + " is our code, handling callback"); cordova.setActivityResultCallback(null); final LocationSettingsStates states = LocationSettingsStates.fromIntent(data); @@ -196,6 +223,20 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e(cordova.getActivity(), TAG, "Unknown result code while enabling location " + resultCode); break; } + case SensorControlConstants.ENABLE_BOTH_PERMISSION: + Log.d(mAct, TAG, requestCode + " is our code, handling callback"); + cordova.setActivityResultCallback(null); + Log.d(mAct, TAG, "Got permission callback from launching app settings"); + if (cordova.hasPermission(SensorControlConstants.LOCATION_PERMISSION)) { + // location permission enabled, cancelling notification + NotificationHelper.cancelNotification(cordova.getActivity(), SensorControlConstants.ENABLE_LOCATION_PERMISSION); + } + if (cordova.hasPermission(SensorControlConstants.BACKGROUND_LOC_PERMISSION)) { + // background location permission enabled, cancelling notification + NotificationHelper.cancelNotification(cordova.getActivity(), SensorControlConstants.ENABLE_BACKGROUND_LOC_PERMISSION); + } + SensorControlBackgroundChecker.restartFSMIfStartState(cordova.getActivity()); + break; default: Log.d(cordova.getActivity(), TAG, "Got unsupported request code " + requestCode + " , ignoring..."); }