diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 6a75d640524..99781bd917b 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.12+1 + +* Fixes another app crash case on Android 12+, and refactors getting of paths from intents. + ## 0.8.12 * Fixes app crashes on Android 12+ caused by selecting images with size 0. diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 880c51c5cf7..c0fe90327b9 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -638,24 +638,57 @@ public boolean onActivityResult( return true; } - private void handleChooseImageResult(int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && data != null) { - Uri uri = data.getData(); - // On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could be null. - if (uri == null) { - ClipData clipData = data.getClipData(); - if (clipData != null && clipData.getItemCount() == 1) { - uri = clipData.getItemAt(0).getUri(); + @Nullable + private ArrayList getPathsFromIntent(@NonNull Intent data, boolean includeMimeType) { + ArrayList paths = new ArrayList<>(); + + Uri uri = data.getData(); + // On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could + // be null. + if (uri == null) { + ClipData clipData = data.getClipData(); + + // If data.getData() and data.getClipData() are both null, we are in an error state. By + // convention we return null from here, and then finish with an error from the corresponding + // handler. + if (clipData == null) { + return null; + } + + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + uri = data.getClipData().getItemAt(i).getUri(); + // Same error state as above. + if (uri == null) { + return null; + } + String path = fileUtils.getPathFromUri(activity, uri); + // Again, same error state as above. + if (path == null) { + return null; } + String mimeType = includeMimeType ? activity.getContentResolver().getType(uri) : null; + paths.add(new MediaPath(path, mimeType)); + } + } else { + String path = fileUtils.getPathFromUri(activity, uri); + if (path == null) { + return null; } + paths.add(new MediaPath(path, null)); + } + return paths; + } + + private void handleChooseImageResult(int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && data != null) { + ArrayList paths = getPathsFromIntent(data, false); // If there's no valid Uri, return an error - if (uri == null) { + if (paths == null) { finishWithError("no_valid_image_uri", "Cannot find the selected image."); return; } - String path = fileUtils.getPathFromUri(activity, uri); - handleImageResult(path, false); + handleMediaResult(paths); return; } @@ -683,20 +716,13 @@ public MediaPath(@NonNull String path, @Nullable String mimeType) { private void handleChooseMediaResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK && intent != null) { - ArrayList paths = new ArrayList<>(); - if (intent.getClipData() != null) { - for (int i = 0; i < intent.getClipData().getItemCount(); i++) { - Uri uri = intent.getClipData().getItemAt(i).getUri(); - String path = fileUtils.getPathFromUri(activity, uri); - String mimeType = activity.getContentResolver().getType(uri); - paths.add(new MediaPath(path, mimeType)); - } - } else { - Uri uri = intent.getData(); - if (uri != null) { - paths.add(new MediaPath(fileUtils.getPathFromUri(activity, uri), null)); - } + ArrayList paths = getPathsFromIntent(intent, true); + // If there's no valid Uri, return an error + if (paths == null) { + finishWithError("no_valid_media_uri", "Cannot find the selected media."); + return; } + handleMediaResult(paths); return; } @@ -707,17 +733,14 @@ private void handleChooseMediaResult(int resultCode, Intent intent) { private void handleChooseMultiImageResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK && intent != null) { - ArrayList paths = new ArrayList<>(); - if (intent.getClipData() != null) { - for (int i = 0; i < intent.getClipData().getItemCount(); i++) { - paths.add( - new MediaPath( - fileUtils.getPathFromUri(activity, intent.getClipData().getItemAt(i).getUri()), - null)); - } - } else { - paths.add(new MediaPath(fileUtils.getPathFromUri(activity, intent.getData()), null)); + ArrayList paths = getPathsFromIntent(intent, false); + // If there's no valid Uri, return an error + if (paths == null) { + finishWithError( + "missing_valid_image_uri", "Cannot find at least one of the selected images."); + return; } + handleMediaResult(paths); return; } @@ -728,22 +751,14 @@ private void handleChooseMultiImageResult(int resultCode, Intent intent) { private void handleChooseVideoResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { - Uri uri = data.getData(); - // On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could be null. - if (uri == null) { - ClipData clipData = data.getClipData(); - if (clipData != null && clipData.getItemCount() == 1) { - uri = clipData.getItemAt(0).getUri(); - } - } + ArrayList paths = getPathsFromIntent(data, false); // If there's no valid Uri, return an error - if (uri == null) { + if (paths == null || paths.size() < 1) { finishWithError("no_valid_video_uri", "Cannot find the selected video."); return; } - String path = fileUtils.getPathFromUri(activity, uri); - handleVideoResult(path); + finishWithSuccess(paths.get(0).path); return; } @@ -774,7 +789,7 @@ private void handleCaptureVideoResult(int resultCode) { localPendingCameraMediaUrl != null ? localPendingCameraMediaUrl : Uri.parse(cache.retrievePendingCameraMediaUriPath()), - this::handleVideoResult); + this::finishWithSuccess); return; } @@ -837,10 +852,6 @@ private void handleMediaResult(@NonNull ArrayList paths) { } } - private void handleVideoResult(String path) { - finishWithSuccess(path); - } - private boolean setPendingOptionsAndResult( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index fdee1c9b67e..bf2f4af6cb4 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -815,8 +815,31 @@ public void onActivityResult_withUnknownRequest_returnsFalse() { } @Test - public void - onActivityResult_whenImagePickedFromGallery_finishesWithEmptyListIfIntentDataIsNull() { + public void onActivityResult_whenImagePickedFromGallery_finishesWithErrorIfClipDataIsNull() { + when(mockIntent.getData()).thenReturn(null); + when(mockIntent.getClipData()).thenReturn(null); + + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); + ImagePickerDelegate delegate = + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + + ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); + verify(mockResult).error(errorCaptor.capture()); + assertEquals("no_valid_media_uri", errorCaptor.getValue().code); + assertEquals("Cannot find the selected media.", errorCaptor.getValue().getMessage()); + } + + @Test + public void onActivityResult_whenImagePickedFromGallery_finishesWithErrorIfClipDataUriIsNull() { setupMockClipDataNullUri(); when(mockIntent.getData()).thenReturn(null); when(mockIntent.getClipData()).thenReturn(null); @@ -834,11 +857,10 @@ public void onActivityResult_withUnknownRequest_returnsFalse() { delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals(0, pathListCapture.getValue().size()); - verifyNoMoreInteractions(mockResult); + ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); + verify(mockResult).error(errorCaptor.capture()); + assertEquals("no_valid_media_uri", errorCaptor.getValue().code); + assertEquals("Cannot find the selected media.", errorCaptor.getValue().getMessage()); } private ImagePickerDelegate createDelegate() { diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index ec276759069..3e15b4366c2 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.12 +version: 0.8.12+1 environment: sdk: ^3.3.0