From 32dd7eab03ad252c5669b64529e39807088f607c Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 21 Oct 2021 23:10:00 +0500 Subject: [PATCH] Changed|Fixed: Add java docs to PermissionUtils and fix permission checking ContextCompat.checkSelfPermission() may return true for permissions not even requested so it now checked if permissions are even requested in app manifest before checking if they are granted and before asking for permission to be granted. Also some general improvements in code quality, including using ActivityUtils to request non-standard permissions and added support for AppCompatActivity instances to request permissions in addition to Activity instances. --- .../shared/packages/PermissionUtils.java | 232 +++++++++++++++--- termux-shared/src/main/res/values/strings.xml | 6 +- 2 files changed, 205 insertions(+), 33 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java b/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java index 8752a0a9..e422ff42 100644 --- a/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java @@ -1,21 +1,32 @@ package com.termux.shared.packages; +import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.PowerManager; import android.provider.Settings; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import com.google.common.base.Joiner; import com.termux.shared.R; import com.termux.shared.logger.Logger; +import com.termux.shared.models.errors.Error; +import com.termux.shared.models.errors.FunctionErrno; +import com.termux.shared.view.ActivityUtils; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; public class PermissionUtils { @@ -27,66 +38,206 @@ public class PermissionUtils { private static final String LOG_TAG = "PermissionUtils"; - public static boolean checkPermission(Context context, String permission) { - if (permission == null) return false; + /** + * Check if app has been granted the required permission. + * + * @param context The context for operations. + * @param permission The {@link String} name for permission to check. + * @return Returns {@code true} if permission is granted, otherwise {@code false}. + */ + public static boolean checkPermission(@NonNull Context context, @NonNull String permission) { return checkPermissions(context, new String[]{permission}); } - public static boolean checkPermissions(Context context, String[] permissions) { - if (permissions == null) return false; + /** + * Check if app has been granted the required permissions. + * + * @param context The context for operations. + * @param permissions The {@link String[]} names for permissions to check. + * @return Returns {@code true} if permissions are granted, otherwise {@code false}. + */ + public static boolean checkPermissions(@NonNull Context context, @NonNull String[] permissions) { + // checkSelfPermission may return true for permissions not even requested + List permissionsNotRequested = getPermissionsNotRequested(context, permissions); + if (permissionsNotRequested.size() > 0) { + Logger.logError(LOG_TAG, + context.getString(R.string.error_attempted_to_check_for_permissions_not_requested, + Joiner.on(", ").join(permissionsNotRequested))); + return false; + } int result; - for (String p:permissions) { - result = ContextCompat.checkSelfPermission(context,p); + for (String permission : permissions) { + result = ContextCompat.checkSelfPermission(context, permission); if (result != PackageManager.PERMISSION_GRANTED) { return false; } } + return true; } - public static void requestPermission(Activity activity, String permission, int requestCode) { - if (permission == null) return; - requestPermissions(activity, new String[]{permission}, requestCode); + /** + * Request user to grant required permissions to the app. + * + * @param context The context for operations. It must be an instance of {@link Activity} or + * {@link AppCompatActivity}. + * @param permission The {@link String} name for permission to request. + * @param requestCode The request code to use while asking for permission. It must be `>=0` or + * will fail silently and will log an exception. + * @return Returns {@code true} if requesting the permission was successful, otherwise {@code false}. + */ + public static boolean requestPermission(@NonNull Context context, @NonNull String permission, + int requestCode) { + return requestPermissions(context, new String[]{permission}, requestCode); } - public static void requestPermissions(Activity activity, String[] permissions, int requestCode) { - if (activity == null || permissions == null) return; + /** + * Request user to grant required permissions to the app. + * + * On sdk 30 (android 11), Activity.onRequestPermissionsResult() will pass + * {@link PackageManager#PERMISSION_DENIED} (-1) without asking the user for the permission + * if user previously denied the permission prompt. On sdk 29 (android 10), + * Activity.onRequestPermissionsResult() will pass {@link PackageManager#PERMISSION_DENIED} (-1) + * without asking the user for the permission if user previously selected "Deny & don't ask again" + * option in prompt. The user will have to manually enable permission in app info in Android + * settings. If user grants and then denies in settings, then next time prompt will shown. + * + * @param context The context for operations. It must be an instance of {@link Activity} or + * {@link AppCompatActivity}. + * @param permissions The {@link String[]} names for permissions to request. + * @param requestCode The request code to use while asking for permissions. It must be `>=0` or + * will fail silently and will log an exception. + * @return Returns {@code true} if requesting the permissions was successful, otherwise {@code false}. + */ + public static boolean requestPermissions(@NonNull Context context, @NonNull String[] permissions, + int requestCode) { + List permissionsNotRequested = getPermissionsNotRequested(context, permissions); + if (permissionsNotRequested.size() > 0) { + Logger.logErrorAndShowToast(context, LOG_TAG, + context.getString(R.string.error_attempted_to_ask_for_permissions_not_requested, + Joiner.on(", ").join(permissionsNotRequested))); + return false; + } - int result; - Logger.showToast(activity, activity.getString(R.string.message_sudo_please_grant_permissions), true); - try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} - - for (String permission:permissions) { - result = ContextCompat.checkSelfPermission(activity, permission); + for (String permission : permissions) { + int result = ContextCompat.checkSelfPermission(context, permission); + // If at least one permission not granted if (result != PackageManager.PERMISSION_GRANTED) { - Logger.logDebug(LOG_TAG, "Requesting Permissions: " + Arrays.toString(permissions)); + Logger.logInfo(LOG_TAG, "Requesting Permissions: " + Arrays.toString(permissions)); + try { - activity.requestPermissions(new String[]{permission}, requestCode); + if (context instanceof AppCompatActivity) + ((AppCompatActivity) context).requestPermissions(new String[]{permission}, requestCode); + else if (context instanceof Activity) + ((Activity) context).requestPermissions(new String[]{permission}, requestCode); + else { + Error.logErrorAndShowToast(context, LOG_TAG, + FunctionErrno.ERRNO_PARAMETER_NOT_INSTANCE_OF.getError("context", "requestPermissions", "Activity or AppCompatActivity")); + return false; + } } catch (Exception e) { - Logger.logStackTraceWithMessage(LOG_TAG, "Failed to request permissions with request code " + requestCode + ": " + Arrays.toString(permissions), e); + String errmsg = context.getString(R.string.error_failed_to_request_permissions, requestCode, Arrays.toString(permissions)); + Logger.logStackTraceWithMessage(LOG_TAG, errmsg, e); + Logger.showToast(context, errmsg + "\n" + e.getMessage(), true); + return false; } } } + + return true; } - public static boolean checkDisplayOverOtherAppsPermission(Context context) { + + /** + * Check if app has requested the required permission in the manifest. + * + * @param context The context for operations. + * @param permission The {@link String} name for permission to check. + * @return Returns {@code true} if permission has been requested, otherwise {@code false}. + */ + public static boolean isPermissionRequested(@NonNull Context context, @NonNull String permission) { + return getPermissionsNotRequested(context, new String[]{permission}).size() == 0; + } + + /** + * Check if app has requested the required permissions or not in the manifest. + * + * @param context The context for operations. + * @param permissions The {@link String[]} names for permissions to check. + * @return Returns {@link List} of permissions that have not been requested. It will have + * size 0 if all permissions have been requested. + */ + @NonNull + public static List getPermissionsNotRequested(@NonNull Context context, @NonNull String[] permissions) { + List permissionsNotRequested = new ArrayList<>(); + Collections.addAll(permissionsNotRequested, permissions); + + PackageInfo packageInfo = PackageUtils.getPackageInfoForPackage(context, PackageManager.GET_PERMISSIONS); + if (packageInfo == null) { + return permissionsNotRequested; + } + + // If no permissions are requested, then nothing to check + if (packageInfo.requestedPermissions == null || packageInfo.requestedPermissions.length == 0) + return permissionsNotRequested; + + List requestedPermissionsList = Arrays.asList(packageInfo.requestedPermissions); + for (String permission : permissions) { + if (requestedPermissionsList.contains(permission)) { + permissionsNotRequested.remove(permission); + } + } + + return permissionsNotRequested; + } + + + + + + /** + * Check if {@link Manifest.permission#SYSTEM_ALERT_WINDOW} permission has been granted. + * + * @param context The context for operations. + * @return Returns {@code true} if permission is granted, otherwise {@code false}. + */ + public static boolean checkDisplayOverOtherAppsPermission(@NonNull Context context) { return Settings.canDrawOverlays(context); } - public static void requestDisplayOverOtherAppsPermission(Activity context, int requestCode) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName())); - context.startActivityForResult(intent, requestCode); + /** + * Request user to grant {@link Manifest.permission#SYSTEM_ALERT_WINDOW} permission to the app. + * + * @param context The context for operations. It must be an instance of {@link Activity} or + * {@link AppCompatActivity}. + * @param requestCode The request code to use while asking for permission. It must be `>=0` or + * will fail silently and will log an exception. + * @return Returns {@code true} if requesting the permission was successful, otherwise {@code false}. + */ + public static boolean requestDisplayOverOtherAppsPermission(@NonNull Context context, int requestCode) { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + intent.setData(Uri.parse("package:" + context.getPackageName())); + return ActivityUtils.startActivityForResult(context, requestCode, intent); } - public static boolean validateDisplayOverOtherAppsPermissionForPostAndroid10(Context context, boolean logResults) { + /** + * Check if running on sdk 29 (android 10) or higher and {@link Manifest.permission#SYSTEM_ALERT_WINDOW} + * permission has been granted or not. + * + * @param context The context for operations. + * @param logResults If it should be logged that permission has been granted or not. + * @return Returns {@code true} if permission is granted, otherwise {@code false}. + */ + public static boolean validateDisplayOverOtherAppsPermissionForPostAndroid10(@NonNull Context context, + boolean logResults) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return true; - - if (!PermissionUtils.checkDisplayOverOtherAppsPermission(context)) { + + if (!checkDisplayOverOtherAppsPermission(context)) { if (logResults) Logger.logWarn(LOG_TAG, context.getPackageName() + " does not have Display over other apps (SYSTEM_ALERT_WINDOW) permission"); return false; @@ -99,16 +250,35 @@ public class PermissionUtils { - public static boolean checkIfBatteryOptimizationsDisabled(Context context) { + + + /** + * Check if {@link Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS} permission has been + * granted. + * + * @param context The context for operations. + * @return Returns {@code true} if permission is granted, otherwise {@code false}. + */ + public static boolean checkIfBatteryOptimizationsDisabled(@NonNull Context context) { PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); return powerManager.isIgnoringBatteryOptimizations(context.getPackageName()); } + /** + * Request user to grant {@link Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS} + * permission to the app. + * + * @param context The context for operations. It must be an instance of {@link Activity} or + * {@link AppCompatActivity}. + * @param requestCode The request code to use while asking for permission. It must be `>=0` or + * will fail silently and will log an exception. + * @return Returns {@code true} if requesting the permission was successful, otherwise {@code false}. + */ @SuppressLint("BatteryLife") - public static void requestDisableBatteryOptimizations(Activity activity, int requestCode) { + public static boolean requestDisableBatteryOptimizations(@NonNull Context context, int requestCode) { Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + activity.getPackageName())); - activity.startActivityForResult(intent, requestCode); + intent.setData(Uri.parse("package:" + context.getPackageName())); + return ActivityUtils.startActivityForResult(context, requestCode, intent); } } diff --git a/termux-shared/src/main/res/values/strings.xml b/termux-shared/src/main/res/values/strings.xml index 074ce959..0c351a5d 100644 --- a/termux-shared/src/main/res/values/strings.xml +++ b/termux-shared/src/main/res/values/strings.xml @@ -36,9 +36,11 @@ - - Please grant permissions on next screen + Please grant requested permission(s) + Failed to request permissions with request code %1$d: %2$s + Attempted to check for permissions that have not been requested in app manifest: %1$s + Attempted to ask for permissions that have not been requested in app manifest: %1$s