API 30: prep to request access by directory, not volume
This commit is contained in:
parent
ffc989d9a3
commit
51fb36bb70
5 changed files with 37 additions and 67 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, Object> argMap = (Map<String, Object>) 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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Object> 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<AvesImageEntry> 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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue