From 32a9b920cf1d13862eb96b40042902f2bc13d2c1 Mon Sep 17 00:00:00 2001 From: Shankari Date: Sun, 14 Apr 2024 23:52:14 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=F0=9F=94=A7=20=F0=9F=AA=A0=20Plumb?= =?UTF-8?q?=20through=20support=20for=20requesting=20bluetooth=20scan=20pe?= =?UTF-8?q?rmissions=20on=20android?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As far as we can tell, iOS does not need any additional permissions to use the iBeacon API since we already have "always" location permissions. However, android requires the BLUETOOTH_SCAN permission https://developer.android.com/develop/connectivity/bluetooth/bt-permissions#declare-android12-or-higher This change includes: - removing the hack popup that shows up when the app is launched - adding check and checkAndFix methods in the javascript -> plugin -> foreground checker - adding a new check method to the list of checks - invoking the method from the background checker so that we can prompt people to provide it if they don't already do so - ensure that the background checker method is only run for fleet configs Testing done: - See screenshots in the related UI PR - Without the fleet check, simulated trip start/end on a non-fleet config and got a notification saying that permissions were incorrect - With the fleet check, retried, and did not get the notification --- src/android/DataCollectionPlugin.java | 16 ++--- .../SensorControlBackgroundChecker.java | 22 ++++++- .../verification/SensorControlChecks.java | 10 ++++ .../SensorControlForegroundDelegate.java | 60 +++++++------------ www/datacollection.js | 15 +++-- 5 files changed, 68 insertions(+), 55 deletions(-) diff --git a/src/android/DataCollectionPlugin.java b/src/android/DataCollectionPlugin.java index 231a680..efb7190 100644 --- a/src/android/DataCollectionPlugin.java +++ b/src/android/DataCollectionPlugin.java @@ -62,10 +62,6 @@ public void pluginInitialize() { new StatsEvent(myActivity, R.string.app_launched)); TripDiaryStateMachineReceiver.initOnUpgrade(myActivity); - - // Ask for bluetooth permissions - // We will change this with future releases, we just ran out of time implementing this into the front end - mControlDelegate.checkAndPromptBluetoothScanPermissions(); } @Override @@ -125,6 +121,14 @@ public boolean execute(String action, JSONArray data, final CallbackContext call Log.d(cordova.getActivity(), TAG, "checking fitness permissions"); mControlDelegate.checkMotionActivityPermissions(callbackContext); return true; + } else if (action.equals("fixBluetoothPermissions")) { + Log.d(cordova.getActivity(), TAG, "fixing bluetooth permissions"); + mControlDelegate.checkAndPromptBluetoothScanPermissions(callbackContext); + return true; + } else if (action.equals("isValidBluetoothPermissions")) { + Log.d(cordova.getActivity(), TAG, "checking bluetooth permissions"); + mControlDelegate.checkBluetoothPermissions(callbackContext); + return true; } else if (action.equals("fixShowNotifications")) { Log.d(cordova.getActivity(), TAG, "fixing notification enable"); mControlDelegate.checkAndPromptShowNotificationsEnabled(callbackContext); @@ -153,10 +157,6 @@ public boolean execute(String action, JSONArray data, final CallbackContext call Log.d(cordova.getActivity(), TAG, "checking ignored battery optimizations"); mControlDelegate.checkIgnoreBatteryOptimizations(callbackContext); return true; - } else if (action.equals("bluetoothScanPermissions")) { - Log.d(cordova.getActivity(), TAG, "requesting bluetooth scan permissions"); - mControlDelegate.checkAndPromptBluetoothScanPermissions(callbackContext); - return true; } else if (action.equals("storeBatteryLevel")) { Context ctxt = cordova.getActivity(); TripDiaryStateMachineReceiver.saveBatteryAndSimulateUser(ctxt); diff --git a/src/android/verification/SensorControlBackgroundChecker.java b/src/android/verification/SensorControlBackgroundChecker.java index b44d777..22007fe 100644 --- a/src/android/verification/SensorControlBackgroundChecker.java +++ b/src/android/verification/SensorControlBackgroundChecker.java @@ -1,6 +1,8 @@ -package edu.berkeley.eecs.emission.cordova.tracker.verification; -// Auto fixed by post-plugin hook +package edu.berkeley.eecs.emission.cordova.tracker.verification; +// Auto fixed by post-plugin hook import edu.berkeley.eecs.emission.R; +// Auto fixed by post-plugin hook + // Auto fixed by post-plugin hook import android.app.PendingIntent; import android.content.Context; @@ -35,6 +37,7 @@ import edu.berkeley.eecs.emission.cordova.tracker.location.actions.LocationTrackingActions; import edu.berkeley.eecs.emission.cordova.unifiedlogger.Log; import edu.berkeley.eecs.emission.cordova.unifiedlogger.NotificationHelper; +import edu.berkeley.eecs.emission.cordova.usercache.UserCacheFactory; @@ -89,6 +92,15 @@ public static void checkAppState(final Context ctxt) { // requests here. Log.i(ctxt, TAG, "All settings are valid, checking current state"); Log.i(ctxt, TAG, "Current location settings are "+response); + boolean isFleet = false; + try { + JSONObject config = (JSONObject) UserCacheFactory.getUserCache(ctxt).getDocument("config/app_ui_config", false); + isFleet = (config != null && config.has("tracking") && config.getJSONObject("tracking").getBoolean("bluetooth_only")); + } catch (JSONException e) { + Log.d(ctxt, TAG, "Error reading config! " + e); + // TODO: Need to figure out what to do about the fleet flag when the config is invalid + // Original implementation by @louisg1337 had isFleet = true in that case (location tracking would not stop) + } // Now that we know that the location settings are correct, we start the permission checks boolean[] allOtherChecks = new boolean[]{ @@ -100,7 +112,11 @@ public static void checkAppState(final Context ctxt) { boolean allOtherChecksPass = true; for (boolean check: allOtherChecks) { allOtherChecksPass = allOtherChecksPass && check; - } + } + if (isFleet) { + allOtherChecksPass = allOtherChecksPass && + SensorControlChecks.checkBluetoothPermissions(ctxt); + } /* Using index-based iteration since we need to start from index 1 instead of 0 and array slices diff --git a/src/android/verification/SensorControlChecks.java b/src/android/verification/SensorControlChecks.java index 1154c0c..be5eecb 100644 --- a/src/android/verification/SensorControlChecks.java +++ b/src/android/verification/SensorControlChecks.java @@ -77,6 +77,16 @@ public static boolean checkMotionActivityPermissions(final Context ctxt) { return version29Check || permCheck; } + // TODO: Figure out how to integrate this with the background code + // https://github.com/e-mission/e-mission-docs/issues/680#issuecomment-953403832 + public static boolean checkBluetoothPermissions(final Context ctxt) { + // apps before version 31 did not need to prompt for bluetooth permissions + boolean version32Check = Build.VERSION.SDK_INT < Build.VERSION_CODES.S; + boolean permCheck = ContextCompat.checkSelfPermission(ctxt, SensorControlConstants.BLUETOOTH_SCAN) == PermissionChecker.PERMISSION_GRANTED; + Log.i(ctxt, TAG, "version32Check "+version32Check+" permCheck "+permCheck+" retVal = "+(version32Check || permCheck)); + return version32Check || permCheck; + } + public static boolean checkNotificationsEnabled(final Context ctxt) { NotificationManagerCompat nMgr = NotificationManagerCompat.from(ctxt); boolean appDisabled = nMgr.areNotificationsEnabled(); diff --git a/src/android/verification/SensorControlForegroundDelegate.java b/src/android/verification/SensorControlForegroundDelegate.java index aa865ce..32badc9 100644 --- a/src/android/verification/SensorControlForegroundDelegate.java +++ b/src/android/verification/SensorControlForegroundDelegate.java @@ -420,17 +420,24 @@ public void checkAndPromptLocationPermissions(CallbackContext cordovaCallback) { } } + public void checkBluetoothPermissions(CallbackContext cordovaCallback) { + boolean validPerms = SensorControlChecks.checkBluetoothPermissions(cordova.getActivity()); + if(validPerms) { + cordovaCallback.success(); + } else { + cordovaCallback.error(cordova.getActivity().getString(R.string.activity_permission_off)); + } + } + /** * Check to see if the user has the ability to scan for bluetooth devices, if not prompt them asking for it. * * @param cordovaCallback */ public void checkAndPromptBluetoothScanPermissions(CallbackContext cordovaCallback) { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S){ - Log.d(cordova.getActivity(), TAG, "Older build version than API 31, return success!"); - cordovaCallback.success(); - } else if (cordova.hasPermission(SensorControlConstants.BLUETOOTH_SCAN)){ - Log.d(cordova.getActivity(), TAG, "User has already enabled bluetooth scan!"); + boolean validPerms = SensorControlChecks.checkBluetoothPermissions(cordova.getActivity()); + if(validPerms) { + SensorControlBackgroundChecker.restartFSMIfStartState(cordova.getActivity()); cordovaCallback.success(); } else { Log.d(cordova.getActivity(), TAG, "User has not enabled bluetooth scan, requesting now..."); @@ -444,25 +451,6 @@ public void checkAndPromptBluetoothScanPermissions(CallbackContext cordovaCallba } } - /** - * Overloaded version of function aboe so we can use on native side. - */ - public void checkAndPromptBluetoothScanPermissions() { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S){ - Log.d(cordova.getActivity(), TAG, "Older build version than API 31, return success!"); - } else if (cordova.hasPermission(SensorControlConstants.BLUETOOTH_SCAN)){ - Log.d(cordova.getActivity(), TAG, "User has already enabled bluetooth scan!"); - } else { - Log.d(cordova.getActivity(), TAG, "User has not enabled bluetooth scan, requesting now..."); - this.permissionChecker = getPermissionChecker( - SensorControlConstants.ENABLE_BLUETOOTH_SCAN, - SensorControlConstants.BLUETOOTH_SCAN, - "Please enable \'Nearby devices\' permission to use the scanner.", - "Please enable \'Nearby devices\' permission to use the scanner."); - this.permissionChecker.requestPermission(); - } - } - public void checkMotionActivityPermissions(CallbackContext cordovaCallback) { boolean validPerms = SensorControlChecks.checkMotionActivityPermissions(cordova.getActivity()); if(validPerms) { @@ -775,6 +763,15 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } permissionChecker = null; break; + case SensorControlConstants.ENABLE_BLUETOOTH_SCAN: + if (SensorControlChecks.checkBluetoothPermissions(cordova.getActivity())) { + SensorControlBackgroundChecker.restartFSMIfStartState(cordova.getActivity()); + cordovaCallback.success(); + } else { + permissionChecker.generateErrorCallback(); + } + permissionChecker = null; + break; case SensorControlConstants.ENABLE_NOTIFICATIONS: Log.d(mAct, TAG, requestCode + " is our code, handling callback"); Log.d(mAct, TAG, "Got notification callback from launching app settings"); @@ -814,21 +811,6 @@ public void run() { } } }); - case SensorControlConstants.ENABLE_BLUETOOTH_SCAN: - if (cordovaCallback == null) { - break; - } - - Log.d(mAct, TAG, requestCode + " is our code, handling callback"); - Log.d(mAct, TAG, "Got bluetooth callback from launching app settings"); - if (cordova.hasPermission(SensorControlConstants.BLUETOOTH_SCAN)) { - Log.d(mAct, TAG, "Bluetooth permissions are allowed after settings page opened!"); - cordovaCallback.success(); - } else { - Log.d(mAct, TAG, "Bluetooth permissions are NOT allowed after settings page opened!"); - cordovaCallback.error("Please enable \'Nearby devices\' permission to use the scanner."); - } - break; default: Log.d(cordova.getActivity(), TAG, "Got unsupported request code " + requestCode + " , ignoring..."); } diff --git a/www/datacollection.js b/www/datacollection.js index a54ae31..a0cfb5d 100644 --- a/www/datacollection.js +++ b/www/datacollection.js @@ -41,11 +41,6 @@ var DataCollection = { exec(resolve, reject, "DataCollection", "isValidLocationPermissions", []); }); }, - bluetoothScanPermissions: function () { - return new Promise(function(resolve, reject) { - exec(resolve, reject, "DataCollection", "bluetoothScanPermissions", []); - }); - }, fixFitnessPermissions: function () { return new Promise(function(resolve, reject) { exec(resolve, reject, "DataCollection", "fixFitnessPermissions", []); @@ -56,6 +51,16 @@ var DataCollection = { exec(resolve, reject, "DataCollection", "isValidFitnessPermissions", []); }); }, + fixBluetoothPermissions: function () { + return new Promise(function(resolve, reject) { + exec(resolve, reject, "DataCollection", "fixBluetoothPermissions", []); + }); + }, + isValidBluetoothPermissions: function () { + return new Promise(function(resolve, reject) { + exec(resolve, reject, "DataCollection", "isValidBluetoothPermissions", []); + }); + }, fixShowNotifications: function () { return new Promise(function(resolve, reject) { exec(resolve, reject, "DataCollection", "fixShowNotifications", []);