From c5c4de60268ebfb537d4429f2ed21ae86988ef8d Mon Sep 17 00:00:00 2001 From: christianrowlands Date: Wed, 16 Oct 2024 13:23:11 -0400 Subject: [PATCH] Add additional permission checks for the other paths that turn on CDR logging to improve the UX and prevent crashes --- .../fragments/DashboardFragment.java | 6 +- .../fragments/SettingsFragment.java | 124 ++++++++++++++++++ .../controller/CellularController.java | 9 ++ networksurvey/src/main/res/values/strings.xml | 2 +- 4 files changed, 137 insertions(+), 4 deletions(-) diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/DashboardFragment.java b/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/DashboardFragment.java index a2d10c7c..d7266b61 100644 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/DashboardFragment.java +++ b/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/DashboardFragment.java @@ -66,8 +66,8 @@ public class DashboardFragment extends AServiceDataFragment implements LocationListener, IConnectionStateListener, ILoggingChangeListener, SharedPreferences.OnSharedPreferenceChangeListener { - private static final int ACCESS_REQUIRED_PERMISSION_REQUEST_ID = 20; - private static final int ACCESS_OPTIONAL_PERMISSION_REQUEST_ID = 21; + public static final int ACCESS_REQUIRED_PERMISSION_REQUEST_ID = 20; + public static final int ACCESS_OPTIONAL_PERMISSION_REQUEST_ID = 21; private static final int ACCESS_BLUETOOTH_PERMISSION_REQUEST_ID = 22; private final DecimalFormat locationFormat = new DecimalFormat("###.#####"); @@ -381,7 +381,7 @@ private void showCdrPermissionRationaleAndRequestPermissions() alertBuilder.setCancelable(true); alertBuilder.setTitle(getString(R.string.cdr_required_permissions_rationale_title)); alertBuilder.setMessage(getText(R.string.cdr_required_permissions_rationale)); - alertBuilder.setPositiveButton(android.R.string.ok, (dialog, which) -> requestRequiredCdrPermissions()); + alertBuilder.setPositiveButton(R.string.request, (dialog, which) -> requestRequiredCdrPermissions()); AlertDialog permissionsExplanationDialog = alertBuilder.create(); permissionsExplanationDialog.show(); diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/SettingsFragment.java b/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/SettingsFragment.java index 9d0796ff..4d0c6d68 100644 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/SettingsFragment.java +++ b/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/SettingsFragment.java @@ -1,12 +1,22 @@ package com.craxiom.networksurvey.fragments; +import static com.craxiom.networksurvey.constants.CdrPermissions.CDR_OPTIONAL_PERMISSIONS; +import static com.craxiom.networksurvey.constants.CdrPermissions.CDR_REQUIRED_PERMISSIONS; +import static com.craxiom.networksurvey.fragments.DashboardFragment.ACCESS_OPTIONAL_PERMISSION_REQUEST_ID; +import static com.craxiom.networksurvey.fragments.DashboardFragment.ACCESS_REQUIRED_PERMISSION_REQUEST_ID; + import android.content.Context; import android.content.RestrictionsManager; import android.content.SharedPreferences; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Bundle; import android.text.InputType; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentActivity; import androidx.preference.DropDownPreference; import androidx.preference.EditTextPreference; import androidx.preference.Preference; @@ -72,6 +82,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { int defaultValue = -1; + if (key == null) return; switch (key) { @@ -113,6 +124,15 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin case NetworkSurveyConstants.PROPERTY_DEVICE_STATUS_SCAN_INTERVAL_SECONDS: defaultValue = NetworkSurveyConstants.DEFAULT_DEVICE_STATUS_SCAN_INTERVAL_SECONDS; break; + + case NetworkSurveyConstants.PROPERTY_AUTO_START_CDR_LOGGING: + final boolean autostartCdr = sharedPreferences.getBoolean(key, false); + if (autostartCdr) + { + // Verify the app has the necessary permissions to start CDR logging + showCdrPermissionRationaleAndRequestPermissions(); + } + break; } if (defaultValue != -1) @@ -367,4 +387,108 @@ private void setAppInstanceId() SettingsUtils.setAppInstanceId(context, appInstanceIdPreference); } + + /** + * Check to see if we should show the rationale for any of the CDR permissions. If so, then display a dialog that + * explains what permissions we need for this app to work properly. + *

+ * If we should not show the rationale, then just request the permissions. + */ + private void showCdrPermissionRationaleAndRequestPermissions() + { + final FragmentActivity activity = getActivity(); + if (activity == null) return; + + final Context context = getContext(); + if (context == null) return; + + if (missingAnyPermissions(CDR_REQUIRED_PERMISSIONS)) + { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context); + alertBuilder.setCancelable(true); + alertBuilder.setTitle(getString(R.string.cdr_required_permissions_rationale_title)); + alertBuilder.setMessage(getText(R.string.cdr_required_permissions_rationale)); + alertBuilder.setPositiveButton(R.string.request, (dialog, which) -> requestRequiredCdrPermissions()); + + AlertDialog permissionsExplanationDialog = alertBuilder.create(); + permissionsExplanationDialog.show(); + + // Revert the cdr autostart preference if the permissions have not been granted + final SwitchPreferenceCompat preference = getPreferenceScreen().findPreference(NetworkSurveyConstants.PROPERTY_AUTO_START_CDR_LOGGING); + if (preference != null) preference.setChecked(false); + getPreferenceManager().getSharedPreferences() + .edit() + .putBoolean(NetworkSurveyConstants.PROPERTY_AUTO_START_CDR_LOGGING, false) + .apply(); + + return; + } + + if (missingAnyPermissions(CDR_OPTIONAL_PERMISSIONS)) + { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context); + alertBuilder.setCancelable(true); + alertBuilder.setTitle(getString(R.string.cdr_optional_permissions_rationale_title)); + alertBuilder.setMessage(getText(R.string.cdr_optional_permissions_rationale)); + alertBuilder.setPositiveButton(R.string.request, (dialog, which) -> requestOptionalCdrPermissions()); + alertBuilder.setNegativeButton(R.string.ignore, (dialog, which) -> { + + }); + + AlertDialog permissionsExplanationDialog = alertBuilder.create(); + permissionsExplanationDialog.show(); + } + } + + /** + * @return True if any of the permissions have been denied. False if all the permissions + * have been granted. + */ + private boolean missingAnyPermissions(String[] permissions) + { + final Context context = getContext(); + if (context == null) return true; + for (String permission : permissions) + { + if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) + { + Timber.i("Missing the permission: %s", permission); + return true; + } + } + + return false; + } + + /** + * Request the permissions needed for this app if any of them have not yet been granted. If all of the permissions + * are already granted then don't request anything. + */ + private void requestRequiredCdrPermissions() + { + if (missingAnyPermissions(CDR_REQUIRED_PERMISSIONS)) + { + FragmentActivity activity = getActivity(); + if (activity != null) + { + ActivityCompat.requestPermissions(activity, CDR_REQUIRED_PERMISSIONS, ACCESS_REQUIRED_PERMISSION_REQUEST_ID); + } + } + } + + /** + * Request the optional permissions for this app if any of them have not yet been granted. If all of the permissions + * are already granted then don't request anything. + */ + private void requestOptionalCdrPermissions() + { + if (missingAnyPermissions(CDR_OPTIONAL_PERMISSIONS)) + { + FragmentActivity activity = getActivity(); + if (activity != null) + { + ActivityCompat.requestPermissions(activity, CDR_OPTIONAL_PERMISSIONS, ACCESS_OPTIONAL_PERMISSION_REQUEST_ID); + } + } + } } diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/services/controller/CellularController.java b/networksurvey/src/main/java/com/craxiom/networksurvey/services/controller/CellularController.java index ad52a436..1322af47 100644 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/services/controller/CellularController.java +++ b/networksurvey/src/main/java/com/craxiom/networksurvey/services/controller/CellularController.java @@ -933,6 +933,15 @@ public void startCdrEvents() { if (surveyService == null) return; + if (ActivityCompat.checkSelfPermission(surveyService, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) + { + // Not notifying the user here because if the user toggled this via the UI then we already checked this + // permission. The way we can reach this point without the permission is if CDR was turned on via MDM + // control, in which case a user notification is not necessary. + Timber.e("Unable to get the READ_PHONE_STATE permission. CDR logging won't work."); + return; + } + synchronized (activeSubscriptionInfoListLock) { if (cdrStarted.getAndSet(true)) return; diff --git a/networksurvey/src/main/res/values/strings.xml b/networksurvey/src/main/res/values/strings.xml index dcdda1a6..06855621 100644 --- a/networksurvey/src/main/res/values/strings.xml +++ b/networksurvey/src/main/res/values/strings.xml @@ -16,7 +16,7 @@ \n\n4. Notifications: This permission allows NS to add a notification so you know when a survey is running in the background. \n\nThere is a known bug where this permission dialog keep showing, but the permission request dialog does not show up after hitting OK. You can work around this by uninstalling and reinstalling the app and approving all - the permission the first time, or opening the Android settings and manually approving the required permissions. + the permissions the first time, or opening the Android settings and manually approving the required permissions. CDR Optional Permissions In addition to the required permissions that were listed in a different dialog, CDR logging has an optional permission.