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 7f019525d..09407b85c 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 @@ -141,8 +141,20 @@ public abstract class ImageProvider { private void rotateJpeg(final Activity activity, final String path, final Uri uri, boolean clockwise, final ImageOpCallback callback) { final String mimeType = MimeTypes.JPEG; + + final DocumentFileCompat originalDocumentFile = StorageUtils.getDocumentFile(activity, path, uri); + if (originalDocumentFile == null) { + callback.onFailure(new Exception("failed to get document file for path=" + path + ", uri=" + uri)); + return; + } + // copy original file to a temporary file for editing - final String editablePath = StorageUtils.copyFileToTemp(path); + final String editablePath = StorageUtils.copyFileToTemp(originalDocumentFile, path); + if (editablePath == null) { + callback.onFailure(new Exception("failed to create a temporary file for path=" + path)); + return; + } + if (Env.requireAccessPermission(path)) { if (PermissionManager.getSdCardTreeUri(activity) == null) { Runnable runnable = () -> rotate(activity, path, uri, mimeType, clockwise, callback); @@ -171,8 +183,8 @@ public abstract class ImageProvider { exif.setAttribute(ExifInterface.TAG_ORIENTATION, Integer.toString(newOrientationCode)); exif.saveAttributes(); - // copy the edited temporary file to the original DocumentFile - DocumentFileCompat.fromFile(new File(editablePath)).copyTo(DocumentFileCompat.fromSingleUri(activity, uri)); + // copy the edited temporary file back to the original + DocumentFileCompat.fromFile(new File(editablePath)).copyTo(originalDocumentFile); } catch (IOException e) { callback.onFailure(e); return; @@ -205,8 +217,20 @@ public abstract class ImageProvider { private void rotatePng(final Activity activity, final String path, final Uri uri, boolean clockwise, final ImageOpCallback callback) { final String mimeType = MimeTypes.PNG; + + final DocumentFileCompat originalDocumentFile = StorageUtils.getDocumentFile(activity, path, uri); + if (originalDocumentFile == null) { + callback.onFailure(new Exception("failed to get document file for path=" + path + ", uri=" + uri)); + return; + } + // copy original file to a temporary file for editing - final String editablePath = StorageUtils.copyFileToTemp(path); + final String editablePath = StorageUtils.copyFileToTemp(originalDocumentFile, path); + if (editablePath == null) { + callback.onFailure(new Exception("failed to create a temporary file for path=" + path)); + return; + } + if (Env.requireAccessPermission(path)) { if (PermissionManager.getSdCardTreeUri(activity) == null) { Runnable runnable = () -> rotate(activity, path, uri, mimeType, clockwise, callback); @@ -229,8 +253,8 @@ public abstract class ImageProvider { try (FileOutputStream fos = new FileOutputStream(editablePath)) { rotatedImage.compress(Bitmap.CompressFormat.PNG, 100, fos); - // copy the edited temporary file to the original DocumentFile - DocumentFileCompat.fromFile(new File(editablePath)).copyTo(DocumentFileCompat.fromSingleUri(activity, uri)); + // copy the edited temporary file back to the original + DocumentFileCompat.fromFile(new File(editablePath)).copyTo(originalDocumentFile); } catch (IOException e) { callback.onFailure(e); return; diff --git a/android/app/src/main/java/deckers/thibault/aves/utils/StorageUtils.java b/android/app/src/main/java/deckers/thibault/aves/utils/StorageUtils.java index 579e8e4ba..b56714265 100644 --- a/android/app/src/main/java/deckers/thibault/aves/utils/StorageUtils.java +++ b/android/app/src/main/java/deckers/thibault/aves/utils/StorageUtils.java @@ -220,18 +220,22 @@ public class StorageUtils { @Nullable public static DocumentFileCompat getDocumentFile(@NonNull Activity activity, @NonNull String path, @NonNull Uri mediaUri) { if (Env.requireAccessPermission(path)) { + // need a document URI (not a media content URI) to open a `DocumentFile` output stream if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // cleanest API to get it Uri docUri = MediaStore.getDocumentUri(activity, mediaUri); - return DocumentFileCompat.fromSingleUri(activity, docUri); - } else { - Uri sdCardTreeUri = PermissionManager.getSdCardTreeUri(activity); - String[] storageVolumeRoots = Env.getStorageVolumeRoots(activity); - Optional docFile = StorageUtils.getSdCardDocumentFile(activity, sdCardTreeUri, storageVolumeRoots, path); - return docFile.orElse(null); + if (docUri != null) { + return DocumentFileCompat.fromSingleUri(activity, docUri); + } } - } else { - return DocumentFileCompat.fromFile(new File(path)); + // fallback for older APIs + Uri sdCardTreeUri = PermissionManager.getSdCardTreeUri(activity); + String[] storageVolumeRoots = Env.getStorageVolumeRoots(activity); + Optional docFile = StorageUtils.getSdCardDocumentFile(activity, sdCardTreeUri, storageVolumeRoots, path); + return docFile.orElse(null); } + // good old `File` + return DocumentFileCompat.fromFile(new File(path)); } // returns the directory `DocumentFile` (from tree URI when scoped storage is required, `File` otherwise) @@ -277,15 +281,15 @@ public class StorageUtils { } } - public static String copyFileToTemp(String path) { + public static String copyFileToTemp(@NonNull DocumentFileCompat documentFile, @NonNull String path) { + String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(path)).toString()); try { - String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(path)).toString()); File temp = File.createTempFile("aves", '.' + extension); - Utils.copyFile(new File(path), temp); + documentFile.copyTo(DocumentFileCompat.fromFile(temp)); temp.deleteOnExit(); return temp.getPath(); } catch (IOException e) { - Log.w(LOG_TAG, "failed to copy file at path=" + path); + Log.w(LOG_TAG, "failed to copy file from path=" + path); } return null; } diff --git a/pubspec.yaml b/pubspec.yaml index 45ef5cd9c..acaf7e1c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: A new Flutter application. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.4+5 +version: 1.0.5+6 # video_player (as of v0.10.8+2, backed by ExoPlayer): # - does not support content URIs (by default, but trivial by fork)