From 8cdeb552714aa0baacf9f9ddc599da94ab7d7c2a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 17 Jun 2024 17:02:17 +0500 Subject: [PATCH] Changed|Fixed: Always request `MANAGE_EXTERNAL_STORAGE if on Android `>= 11` when running `termux-setup-storage` Requesting `MANAGE_EXTERNAL_STORAGE` should additionally grant access to unreliable/removable volumes like USB OTG devices under the `/mnt/media_rw/XXXX-XXXX` paths on `Android >= 12`, so request that if possible. Check https://github.com/termux/termux-app/issues/71#issuecomment-1869222653 for more info. Fixes issue on Android `14`, where using `targetSdkVersion=28`, that requests the legacy `WRITE_EXTERNAL_STORAGE` will actually request the `photos, music, video, and other files` permissions (`READ_MEDIA_AUDIO`/`READ_MEDIA_IMAGES`/`READ_MEDIA_VIDEO`) and apparently access to full external storage `/sdcard` is not available for some users, maybe because `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` permissions are not granted for those device automatically in addition to `READ_MEDIA_*` permission. The issue is not reproducible on Android `13-15` avd. To solve this, we request the singular `MANAGE_EXTERNAL_STORAGE` permission instead so that full access is always available. Related: https://github.com/termux/termux-app/issues/3647#issuecomment-2137266012 See also: - https://developer.android.com/training/data-storage/shared/media#access-other-apps-files - https://developer.android.com/reference/android/Manifest.permission#READ_MEDIA_IMAGES --- .../java/com/termux/app/TermuxActivity.java | 2 +- .../shared/android/PermissionUtils.java | 49 +++++++++++++++---- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 308d1f0b..96b2dfa5 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -785,7 +785,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo // If permission is granted, then also setup storage symlinks. if(PermissionUtils.checkAndRequestLegacyOrManageExternalStoragePermission( - TermuxActivity.this, requestCode, !isPermissionCallback)) { + TermuxActivity.this, requestCode, true, !isPermissionCallback)) { if (isPermissionCallback) Logger.logInfoAndShowToast(TermuxActivity.this, LOG_TAG, getString(com.termux.shared.R.string.msg_storage_permission_granted_on_request)); diff --git a/termux-shared/src/main/java/com/termux/shared/android/PermissionUtils.java b/termux-shared/src/main/java/com/termux/shared/android/PermissionUtils.java index 4a15649d..d0b2e972 100644 --- a/termux-shared/src/main/java/com/termux/shared/android/PermissionUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/android/PermissionUtils.java @@ -209,31 +209,44 @@ public class PermissionUtils { /** If path is under primary external storage directory and storage permission is missing, * then legacy or manage external storage permission will be requested from the user via a call - * to {@link #checkAndRequestLegacyOrManageExternalStoragePermission(Context, int, boolean)}. + * to {@link #checkAndRequestLegacyOrManageExternalStoragePermission(Context, int, boolean, boolean)}. * * @param context The context for operations. * @param filePath The path to check. * @param requestCode The request code to use while asking for permission. + * @param prioritizeManageExternalStoragePermission If {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * permission should be requested if on + * Android `>= 11` instead of getting legacy + * storage permission. * @param showErrorMessage If an error message toast should be shown if permission is not granted. * @return Returns {@code true} if permission is granted, otherwise {@code false}. */ @SuppressLint("SdCardPath") public static boolean checkAndRequestLegacyOrManageExternalStoragePermissionIfPathOnPrimaryExternalStorage( - @NonNull Context context, String filePath, int requestCode, boolean showErrorMessage) { + @NonNull Context context, String filePath, int requestCode, + boolean prioritizeManageExternalStoragePermission, boolean showErrorMessage) { // If path is under primary external storage directory, then check for missing permissions. if (!FileUtils.isPathInDirPaths(filePath, Arrays.asList(Environment.getExternalStorageDirectory().getAbsolutePath(), "/sdcard"), true)) return true; - return checkAndRequestLegacyOrManageExternalStoragePermission(context, requestCode, showErrorMessage); + return checkAndRequestLegacyOrManageExternalStoragePermission(context, requestCode, prioritizeManageExternalStoragePermission, showErrorMessage); } /** - * Check if legacy or manage external storage permissions has been granted. If - * {@link #isLegacyExternalStoragePossible(Context)} returns {@code true}, them it will be - * checked if app has has been granted {@link Manifest.permission#READ_EXTERNAL_STORAGE} and - * {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions, otherwise it will be checked - * if app has been granted the {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission. + * Check if legacy or manage external storage permissions has been granted. + * + * - If `prioritizeManageExternalStoragePermission` is `true and running on Android `>= 11`, then + * it will be checked if app has been granted the + * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}. + * - If `prioritizeManageExternalStoragePermission` is `false` and running on Android `>= 11`, then + * if {@link #isLegacyExternalStoragePossible(Context)} returns `true`, them it will be + * checked if app has has been granted {@link Manifest.permission#READ_EXTERNAL_STORAGE} and + * {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions, otherwise it will be checked + * if app has been granted the {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission. + * - If running on Android `< 11`, then it will only be checked if app has been granted + * {@link Manifest.permission#READ_EXTERNAL_STORAGE} and + * {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions. * * If storage permission is missing, it will be requested from the user if {@code context} is an * instance of {@link Activity} or {@link AppCompatActivity} and {@code requestCode} @@ -256,16 +269,34 @@ public class PermissionUtils { *} * @param context The context for operations. * @param requestCode The request code to use while asking for permission. + * @param prioritizeManageExternalStoragePermission If {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * permission should be requested if on + * Android `>= 11` instead of getting legacy + * storage permission. * @param showErrorMessage If an error message toast should be shown if permission is not granted. * @return Returns {@code true} if permission is granted, otherwise {@code false}. */ public static boolean checkAndRequestLegacyOrManageExternalStoragePermission(@NonNull Context context, int requestCode, + boolean prioritizeManageExternalStoragePermission, boolean showErrorMessage) { + Logger.logVerbose(LOG_TAG, "Checking storage permission"); + String errmsg; - boolean requestLegacyStoragePermission = isLegacyExternalStoragePossible(context); + Boolean requestLegacyStoragePermission = null; + + if (prioritizeManageExternalStoragePermission && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + requestLegacyStoragePermission = false; + + if (requestLegacyStoragePermission == null) + requestLegacyStoragePermission = isLegacyExternalStoragePossible(context); + boolean checkIfHasRequestedLegacyExternalStorage = checkIfHasRequestedLegacyExternalStorage(context); + Logger.logVerbose(LOG_TAG, "prioritizeManageExternalStoragePermission=" + prioritizeManageExternalStoragePermission + + ", requestLegacyStoragePermission=" + requestLegacyStoragePermission + + ", checkIfHasRequestedLegacyExternalStorage=" + checkIfHasRequestedLegacyExternalStorage); + if (requestLegacyStoragePermission && checkIfHasRequestedLegacyExternalStorage) { // Check if requestLegacyExternalStorage is set to true in app manifest if (!hasRequestedLegacyExternalStorage(context, showErrorMessage))