From 51fb36bb7075bdf8c12f11a2f7ff38f4ceb7f5b1 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 19 Jul 2020 22:00:59 +0900 Subject: [PATCH] API 30: prep to request access by directory, not volume --- .../deckers/thibault/aves/MainActivity.java | 4 +- .../streams/StorageAccessStreamHandler.java | 8 +-- .../aves/model/provider/ImageProvider.java | 15 ----- .../provider/MediaStoreImageProvider.java | 21 ------- .../aves/utils/PermissionManager.java | 56 ++++++++++--------- 5 files changed, 37 insertions(+), 67 deletions(-) diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index b34a006fe..e7296457b 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -100,7 +100,7 @@ public class MainActivity extends FlutterActivity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PermissionManager.VOLUME_ROOT_PERMISSION_REQUEST_CODE) { if (resultCode != RESULT_OK || data.getData() == null) { - PermissionManager.onPermissionResult(this, requestCode, false, null); + PermissionManager.onPermissionResult(this, requestCode, null); return; } @@ -113,7 +113,7 @@ public class MainActivity extends FlutterActivity { getContentResolver().takePersistableUriPermission(treeUri, takeFlags); // resume pending action - PermissionManager.onPermissionResult(this, requestCode, true, treeUri); + PermissionManager.onPermissionResult(this, requestCode, treeUri); } } } diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java index c1d529dd6..810decc4f 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java @@ -17,14 +17,14 @@ public class StorageAccessStreamHandler implements EventChannel.StreamHandler { private Activity activity; private EventChannel.EventSink eventSink; private Handler handler; - private String volumePath; + private String path; @SuppressWarnings("unchecked") public StorageAccessStreamHandler(Activity activity, Object arguments) { this.activity = activity; if (arguments instanceof Map) { Map argMap = (Map) arguments; - this.volumePath = (String) argMap.get("path"); + this.path = (String) argMap.get("path"); } } @@ -32,9 +32,9 @@ public class StorageAccessStreamHandler implements EventChannel.StreamHandler { public void onListen(Object o, final EventChannel.EventSink eventSink) { this.eventSink = eventSink; this.handler = new Handler(Looper.getMainLooper()); - Runnable onGranted = () -> success(!PermissionManager.requireVolumeAccessDialog(activity, volumePath)); + Runnable onGranted = () -> success(!PermissionManager.requireVolumeAccessDialog(activity, path)); Runnable onDenied = () -> success(false); - PermissionManager.requestVolumeAccess(activity, volumePath, onGranted, onDenied); + PermissionManager.requestVolumeAccess(activity, path, onGranted, onDenied); } @Override diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java index 391d1ca3c..34b912559 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java @@ -9,8 +9,6 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.MediaScannerConnection; import android.net.Uri; -import android.os.Handler; -import android.os.Looper; import android.provider.MediaStore; import android.util.Log; @@ -32,7 +30,6 @@ import java.util.Map; import deckers.thibault.aves.model.AvesImageEntry; import deckers.thibault.aves.utils.MetadataHelper; import deckers.thibault.aves.utils.MimeTypes; -import deckers.thibault.aves.utils.PermissionManager; import deckers.thibault.aves.utils.StorageUtils; import deckers.thibault.aves.utils.Utils; @@ -74,12 +71,6 @@ public abstract class ImageProvider { return; } - if (PermissionManager.requireVolumeAccessDialog(activity, oldPath)) { - Runnable runnable = () -> rename(activity, oldPath, oldMediaUri, mimeType, newFilename, callback); - new Handler(Looper.getMainLooper()).post(() -> PermissionManager.showVolumeAccessDialog(activity, oldPath, runnable)); - return; - } - DocumentFileCompat df = StorageUtils.getDocumentFile(activity, oldPath, oldMediaUri); try { boolean renamed = df != null && df.renameTo(newFilename); @@ -97,12 +88,6 @@ public abstract class ImageProvider { } public void rotate(final Activity activity, final String path, final Uri uri, final String mimeType, final boolean clockwise, final ImageOpCallback callback) { - if (PermissionManager.requireVolumeAccessDialog(activity, path)) { - Runnable runnable = () -> rotate(activity, path, uri, mimeType, clockwise, callback); - new Handler(Looper.getMainLooper()).post(() -> PermissionManager.showVolumeAccessDialog(activity, path, runnable)); - return; - } - switch (mimeType) { case MimeTypes.JPEG: rotateJpeg(activity, path, uri, clockwise, callback); diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java index 5a5f0fae8..cc7c15401 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java @@ -8,8 +8,6 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.provider.MediaStore; @@ -35,7 +33,6 @@ import java.util.stream.Stream; import deckers.thibault.aves.model.AvesImageEntry; import deckers.thibault.aves.model.SourceImageEntry; import deckers.thibault.aves.utils.MimeTypes; -import deckers.thibault.aves.utils.PermissionManager; import deckers.thibault.aves.utils.StorageUtils; import deckers.thibault.aves.utils.Utils; @@ -217,18 +214,6 @@ public class MediaStoreImageProvider extends ImageProvider { SettableFuture future = SettableFuture.create(); if (StorageUtils.requireAccessPermission(path)) { - if (PermissionManager.getVolumeTreeUri(activity, path) == null) { - Runnable runnable = () -> { - try { - future.set(delete(activity, path, mediaUri).get()); - } catch (Exception e) { - future.setException(e); - } - }; - new Handler(Looper.getMainLooper()).post(() -> PermissionManager.showVolumeAccessDialog(activity, path, runnable)); - return future; - } - // if the file is on SD card, calling the content resolver delete() removes the entry from the Media Store // but it doesn't delete the file, even if the app has the permission try { @@ -276,12 +261,6 @@ public class MediaStoreImageProvider extends ImageProvider { @Override public void moveMultiple(final Activity activity, final Boolean copy, final String destinationDir, final List entries, @NonNull final ImageOpCallback callback) { - if (PermissionManager.requireVolumeAccessDialog(activity, destinationDir)) { - Runnable runnable = () -> moveMultiple(activity, copy, destinationDir, entries, callback); - new Handler(Looper.getMainLooper()).post(() -> PermissionManager.showVolumeAccessDialog(activity, destinationDir, runnable)); - return; - } - DocumentFileCompat destinationDirDocFile = StorageUtils.createDirectoryIfAbsent(activity, destinationDir); if (destinationDirDocFile == null) { callback.onFailure(new Exception("failed to create directory at path=" + destinationDir)); diff --git a/android/app/src/main/java/deckers/thibault/aves/utils/PermissionManager.java b/android/app/src/main/java/deckers/thibault/aves/utils/PermissionManager.java index 4ab6c92d1..cbbf9beac 100644 --- a/android/app/src/main/java/deckers/thibault/aves/utils/PermissionManager.java +++ b/android/app/src/main/java/deckers/thibault/aves/utils/PermissionManager.java @@ -41,26 +41,15 @@ public class PermissionManager { return uriPermissionOptional.map(UriPermission::getUri).orElse(null); } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public static void showVolumeAccessDialog(final Activity activity, @NonNull String anyPath, final Runnable pendingRunnable) { - String volumePath = StorageUtils.getVolumePath(activity, anyPath).orElse(null); - // TODO TLAD show volume name/ID in the message - new AlertDialog.Builder(activity) - .setTitle("Storage Volume Access") - .setMessage("Please select the root directory of the storage volume in the next screen, so that this app has permission to access it and complete your request.") - .setPositiveButton(android.R.string.ok, (dialog, button) -> requestVolumeAccess(activity, volumePath, pendingRunnable, null)) - .show(); - } - - public static void requestVolumeAccess(Activity activity, String volumePath, Runnable onGranted, Runnable onDenied) { - Log.i(LOG_TAG, "request user to select and grant access permission to volume=" + volumePath); - pendingPermissionMap.put(VOLUME_ROOT_PERMISSION_REQUEST_CODE, new PendingPermissionHandler(volumePath, onGranted, onDenied)); + public static void requestVolumeAccess(@NonNull Activity activity, @NonNull String path, @NonNull Runnable onGranted, @NonNull Runnable onDenied) { + Log.i(LOG_TAG, "request user to select and grant access permission to volume=" + path); + pendingPermissionMap.put(VOLUME_ROOT_PERMISSION_REQUEST_CODE, new PendingPermissionHandler(path, onGranted, onDenied)); Intent intent = null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && volumePath != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { StorageManager sm = activity.getSystemService(StorageManager.class); if (sm != null) { - StorageVolume volume = sm.getStorageVolume(new File(volumePath)); + StorageVolume volume = sm.getStorageVolume(new File(path)); if (volume != null) { intent = volume.createOpenDocumentTreeIntent(); } @@ -75,25 +64,42 @@ public class PermissionManager { ActivityCompat.startActivityForResult(activity, intent, VOLUME_ROOT_PERMISSION_REQUEST_CODE, null); } - public static void onPermissionResult(Activity activity, int requestCode, boolean granted, Uri treeUri) { - Log.d(LOG_TAG, "onPermissionResult with requestCode=" + requestCode + ", granted=" + granted); + public static void onPermissionResult(Activity activity, int requestCode, @Nullable Uri treeUri) { + Log.d(LOG_TAG, "onPermissionResult with requestCode=" + requestCode + ", treeUri=" + treeUri); + boolean granted = treeUri != null; PendingPermissionHandler handler = pendingPermissionMap.remove(requestCode); if (handler == null) return; - StorageUtils.setVolumeTreeUri(activity, handler.volumePath, treeUri.toString()); + + if (granted) { + String requestedPath = handler.path; + if (isTreeUriPath(requestedPath, treeUri)) { + StorageUtils.setVolumeTreeUri(activity, requestedPath, treeUri.toString()); + } else { + granted = false; + } + } Runnable runnable = granted ? handler.onGranted : handler.onDenied; if (runnable == null) return; runnable.run(); } - static class PendingPermissionHandler { - String volumePath; - Runnable onGranted; - Runnable onDenied; + private static boolean isTreeUriPath(String path, Uri treeUri) { + // TODO TLAD check requestedPath match treeUri + // e.g. OK match for path=/storage/emulated/0/, treeUri=content://com.android.externalstorage.documents/tree/primary%3A + // e.g. NO match for path=/storage/10F9-3F13/, treeUri=content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures + Log.d(LOG_TAG, "isTreeUriPath path=" + path + ", treeUri=" + treeUri); + return true; + } - PendingPermissionHandler(String volumePath, Runnable onGranted, Runnable onDenied) { - this.volumePath = volumePath; + static class PendingPermissionHandler { + final String path; + final Runnable onGranted; + final Runnable onDenied; + + PendingPermissionHandler(@NonNull String path, @NonNull Runnable onGranted, @NonNull Runnable onDenied) { + this.path = path; this.onGranted = onGranted; this.onDenied = onDenied; }