From f1892ea1d5228e996a01f2d2292d1d89bb5b6bde Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Tue, 21 Mar 2023 11:18:42 +0100 Subject: [PATCH 01/10] Offloads result handling to separate thread --- .../imagepicker/ImagePickerDelegate.java | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) 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 eaee6e84ae9..88aa7b082e0 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 @@ -32,6 +32,10 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * A delegate class doing the heavy lifting for the plugin. @@ -112,6 +116,7 @@ private PendingCallState( private final PermissionManager permissionManager; private final FileUriResolver fileUriResolver; private final FileUtils fileUtils; + private final ExecutorService executor; private CameraDevice cameraDevice; interface PermissionManager { @@ -216,6 +221,7 @@ public void onScanCompleted(String path, Uri uri) { this.fileUriResolver = fileUriResolver; this.fileUtils = fileUtils; this.cache = cache; + this.executor = new ThreadPoolExecutor(0, 1, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); } void setCameraDevice(CameraDevice device) { @@ -537,28 +543,33 @@ public boolean onRequestPermissionsResult( } @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: - handleChooseImageResult(resultCode, data); - break; - case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: - handleChooseMultiImageResult(resultCode, data); - break; - case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: - handleCaptureImageResult(resultCode); - break; - case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: - handleChooseVideoResult(resultCode, data); - break; - case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: - handleCaptureVideoResult(resultCode); - break; - default: - return false; - } + public boolean onActivityResult(final int requestCode, final int resultCode, final Intent data) { + // Off-load result handling to a separate thread as it almost always involves I/O operations. + executor.execute(() -> { + switch (requestCode) { + case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: + handleChooseImageResult(resultCode, data); + break; + case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: + handleChooseMultiImageResult(resultCode, data); + break; + case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: + handleCaptureImageResult(resultCode); + break; + case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: + handleChooseVideoResult(resultCode, data); + break; + case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: + handleCaptureVideoResult(resultCode); + break; + } + }); - return true; + return requestCode == REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY || + requestCode == REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY || + requestCode == REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA || + requestCode == REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY || + requestCode == REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA; } private void handleChooseImageResult(int resultCode, Intent data) { From aeff575ec55bb63c0621cd37b7718752a54afee1 Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Tue, 21 Mar 2023 15:58:47 +0100 Subject: [PATCH 02/10] Add tests --- .../imagepicker/ImagePickerDelegate.java | 8 +- .../imagepicker/ImagePickerDelegateTest.java | 147 +++++++++++++++--- 2 files changed, 132 insertions(+), 23 deletions(-) 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 88aa7b082e0..52a32f61eec 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 @@ -190,7 +190,8 @@ public void onScanCompleted(String path, Uri uri) { }); } }, - new FileUtils()); + new FileUtils(), + new ThreadPoolExecutor(0, 1, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>())); } /** @@ -208,7 +209,8 @@ public void onScanCompleted(String path, Uri uri) { final ImagePickerCache cache, final PermissionManager permissionManager, final FileUriResolver fileUriResolver, - final FileUtils fileUtils) { + final FileUtils fileUtils, + final ExecutorService executor) { this.activity = activity; this.externalFilesDirectory = externalFilesDirectory; this.imageResizer = imageResizer; @@ -221,7 +223,7 @@ public void onScanCompleted(String path, Uri uri) { this.fileUriResolver = fileUriResolver; this.fileUtils = fileUtils; this.cache = cache; - this.executor = new ThreadPoolExecutor(0, 1, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + this.executor = executor; } void setCameraDevice(CameraDevice device) { 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 77a34b452b0..9afca839ff1 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 @@ -7,6 +7,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -32,6 +34,8 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -64,6 +68,7 @@ public class ImagePickerDelegateTest { @Mock FileUtils mockFileUtils; @Mock Intent mockIntent; @Mock ImagePickerCache cache; + @Mock ExecutorService mockExecutor; ImagePickerDelegate.FileUriResolver mockFileUriResolver; MockedStatic mockStaticFile; @@ -349,11 +354,15 @@ public void onRequestPermissionsResult_whenCameraPermissionDenied_finishesWithEr @Test public void onActivityResult_whenPickFromGalleryCanceled_finishesWithEmptyList() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -364,10 +373,14 @@ public void onActivityResult_whenPickFromGalleryCanceled_finishesWithEmptyList() @Test public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegate(); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); verify(cache, never()).saveResult(any(), any(), any()); } @@ -375,8 +388,12 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() @Test public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_finishesWithImagePath() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @@ -390,10 +407,14 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() @Test public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_storesImageInCache() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegate(); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(ArrayList.class); @@ -404,8 +425,13 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores @Test public void onActivityResult_whenImagePickedFromGallery_andResizeNeeded_finishesWithScaledImagePath() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @@ -418,11 +444,16 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores @Test public void - onActivityResult_whenVideoPickedFromGallery_andResizeParametersSupplied_finishesWithFilePath() { + onActivityResult_whenVideoPickedFromGallery_andResizeParametersSupplied_finishesWithFilePath() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -433,11 +464,15 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores @Test public void onActivityResult_whenTakeImageWithCameraCanceled_finishesWithEmptyList() { + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null); + ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -448,9 +483,13 @@ public void onActivityResult_whenTakeImageWithCameraCanceled_finishesWithEmptyLi @Test public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishesWithImagePath() { + when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); - when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); @@ -466,9 +505,13 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes public void onActivityResult_whenImageTakenWithCamera_andResizeNeeded_finishesWithScaledImagePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); @@ -481,13 +524,17 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes @Test public void - onActivityResult_whenVideoTakenWithCamera_andResizeParametersSupplied_finishesWithFilePath() { + onActivityResult_whenVideoTakenWithCamera_andResizeParametersSupplied_finishesWithFilePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -500,10 +547,14 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes public void onActivityResult_whenVideoTakenWithCamera_andMaxDurationParametersSupplied_finishesWithFilePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - + Mockito.doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mockExecutor).execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegateWithPendingResultAndOptions( null, new VideoSelectionOptions.Builder().setMaxDurationSeconds(MAX_DURATION).build()); + delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); @@ -514,6 +565,60 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes verifyNoMoreInteractions(mockResult); } + @Test + public void onActivityResult_whenImagePickedFromGallery_returnsTrue() { + ImagePickerDelegate delegate = createDelegate(); + + boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + + assertTrue(isHandled); + } + + @Test + public void onActivityResult_whenMultipleImagesPickedFromGallery_returnsTrue() { + ImagePickerDelegate delegate = createDelegate(); + + boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + + assertTrue(isHandled); + } + + @Test + public void onActivityResult_whenVideoPickerFromGallery_returnsTrue() { + ImagePickerDelegate delegate = createDelegate(); + + boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + + assertTrue(isHandled); + } + + @Test + public void onActivityResult_whenImageTakenWithCamera_returnsTrue() { + ImagePickerDelegate delegate = createDelegate(); + + boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + + assertTrue(isHandled); + } + + @Test + public void onActivityResult_whenVideoTakenWithCamera_returnsTrue() { + ImagePickerDelegate delegate = createDelegate(); + + boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + + assertTrue(isHandled); + } + + @Test + public void onActivityResult_withUnknownRequest_returnsFalse() { + ImagePickerDelegate delegate = createDelegate(); + + boolean isHandled = delegate.onActivityResult(314, Activity.RESULT_OK, mockIntent); + + assertFalse(isHandled); + } + private ImagePickerDelegate createDelegate() { return new ImagePickerDelegate( mockActivity, @@ -525,7 +630,8 @@ private ImagePickerDelegate createDelegate() { cache, mockPermissionManager, mockFileUriResolver, - mockFileUtils); + mockFileUtils, + mockExecutor); } private ImagePickerDelegate createDelegateWithPendingResultAndOptions( @@ -540,7 +646,8 @@ private ImagePickerDelegate createDelegateWithPendingResultAndOptions( cache, mockPermissionManager, mockFileUriResolver, - mockFileUtils); + mockFileUtils, + mockExecutor); } private void verifyFinishedWithAlreadyActiveError() { From 7cfc713dcc287e8c5ae3d442fb34634f18ab1531 Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Tue, 21 Mar 2023 16:02:00 +0100 Subject: [PATCH 03/10] Update version and changelog --- packages/image_picker/image_picker_android/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 44902aa169f..90ae0f48296 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.6+10 + +* Offloads picker result handling to separate thread. + ## 0.8.6+9 * Fixes compatibility with AGP versions older than 4.2. From 06983b489f40afb4bec8fe17bab38a3f2dcf9d4a Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Tue, 21 Mar 2023 16:36:08 +0100 Subject: [PATCH 04/10] Format files --- .../imagepicker/ImagePickerDelegate.java | 49 ++--- .../imagepicker/ImagePickerDelegateTest.java | 180 +++++++++++------- 2 files changed, 141 insertions(+), 88 deletions(-) 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 52a32f61eec..d8e96741f45 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 @@ -547,31 +547,32 @@ public boolean onRequestPermissionsResult( @Override public boolean onActivityResult(final int requestCode, final int resultCode, final Intent data) { // Off-load result handling to a separate thread as it almost always involves I/O operations. - executor.execute(() -> { - switch (requestCode) { - case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: - handleChooseImageResult(resultCode, data); - break; - case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: - handleChooseMultiImageResult(resultCode, data); - break; - case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: - handleCaptureImageResult(resultCode); - break; - case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: - handleChooseVideoResult(resultCode, data); - break; - case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: - handleCaptureVideoResult(resultCode); - break; - } - }); + executor.execute( + () -> { + switch (requestCode) { + case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: + handleChooseImageResult(resultCode, data); + break; + case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: + handleChooseMultiImageResult(resultCode, data); + break; + case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: + handleCaptureImageResult(resultCode); + break; + case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: + handleChooseVideoResult(resultCode, data); + break; + case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: + handleCaptureVideoResult(resultCode); + break; + } + }); - return requestCode == REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY || - requestCode == REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY || - requestCode == REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA || - requestCode == REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY || - requestCode == REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA; + return requestCode == REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY + || requestCode == REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY + || requestCode == REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA + || requestCode == REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY + || requestCode == REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA; } private void handleChooseImageResult(int resultCode, Intent data) { 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 9afca839ff1..4648e6d4505 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 @@ -35,7 +35,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -354,15 +353,18 @@ public void onRequestPermissionsResult_whenCameraPermissionDenied_finishesWithEr @Test public void onActivityResult_whenPickFromGalleryCanceled_finishesWithEmptyList() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -373,14 +375,17 @@ public void onActivityResult_whenPickFromGalleryCanceled_finishesWithEmptyList() @Test public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegate(); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); verify(cache, never()).saveResult(any(), any(), any()); } @@ -388,12 +393,15 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() @Test public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_finishesWithImagePath() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @@ -407,14 +415,17 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() @Test public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_storesImageInCache() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegate(); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(ArrayList.class); @@ -425,12 +436,15 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores @Test public void onActivityResult_whenImagePickedFromGallery_andResizeNeeded_finishesWithScaledImagePath() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @@ -444,16 +458,19 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores @Test public void - onActivityResult_whenVideoPickedFromGallery_andResizeParametersSupplied_finishesWithFilePath() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + onActivityResult_whenVideoPickedFromGallery_andResizeParametersSupplied_finishesWithFilePath() { + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -464,15 +481,18 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores @Test public void onActivityResult_whenTakeImageWithCameraCanceled_finishesWithEmptyList() { - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null); + ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -484,10 +504,13 @@ public void onActivityResult_whenTakeImageWithCameraCanceled_finishesWithEmptyLi @Test public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishesWithImagePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); @@ -505,10 +528,13 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes public void onActivityResult_whenImageTakenWithCamera_andResizeNeeded_finishesWithScaledImagePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); @@ -524,17 +550,20 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes @Test public void - onActivityResult_whenVideoTakenWithCamera_andResizeParametersSupplied_finishesWithFilePath() { + onActivityResult_whenVideoTakenWithCamera_andResizeParametersSupplied_finishesWithFilePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); delegate.onActivityResult( - ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); @SuppressWarnings("unchecked") ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); @@ -547,10 +576,13 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes public void onActivityResult_whenVideoTakenWithCamera_andMaxDurationParametersSupplied_finishesWithFilePath() { when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); - Mockito.doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mockExecutor).execute(any(Runnable.class)); + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); ImagePickerDelegate delegate = createDelegateWithPendingResultAndOptions( null, new VideoSelectionOptions.Builder().setMaxDurationSeconds(MAX_DURATION).build()); @@ -569,7 +601,11 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes public void onActivityResult_whenImagePickedFromGallery_returnsTrue() { ImagePickerDelegate delegate = createDelegate(); - boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + boolean isHandled = + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, + Activity.RESULT_OK, + mockIntent); assertTrue(isHandled); } @@ -578,7 +614,11 @@ public void onActivityResult_whenImagePickedFromGallery_returnsTrue() { public void onActivityResult_whenMultipleImagesPickedFromGallery_returnsTrue() { ImagePickerDelegate delegate = createDelegate(); - boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + boolean isHandled = + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY, + Activity.RESULT_OK, + mockIntent); assertTrue(isHandled); } @@ -587,7 +627,11 @@ public void onActivityResult_whenMultipleImagesPickedFromGallery_returnsTrue() { public void onActivityResult_whenVideoPickerFromGallery_returnsTrue() { ImagePickerDelegate delegate = createDelegate(); - boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + boolean isHandled = + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, + Activity.RESULT_OK, + mockIntent); assertTrue(isHandled); } @@ -596,7 +640,11 @@ public void onActivityResult_whenVideoPickerFromGallery_returnsTrue() { public void onActivityResult_whenImageTakenWithCamera_returnsTrue() { ImagePickerDelegate delegate = createDelegate(); - boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + boolean isHandled = + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, + Activity.RESULT_OK, + mockIntent); assertTrue(isHandled); } @@ -605,7 +653,11 @@ public void onActivityResult_whenImageTakenWithCamera_returnsTrue() { public void onActivityResult_whenVideoTakenWithCamera_returnsTrue() { ImagePickerDelegate delegate = createDelegate(); - boolean isHandled = delegate.onActivityResult(ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + boolean isHandled = + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, + Activity.RESULT_OK, + mockIntent); assertTrue(isHandled); } From 2cba265b644e26448272b09db2bc72acda79432d Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Mon, 3 Apr 2023 16:17:26 +0200 Subject: [PATCH 05/10] Restructure onActivityResult logic --- .../imagepicker/ImagePickerDelegate.java | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) 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 d8e96741f45..e94bcee60ac 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 @@ -546,33 +546,31 @@ public boolean onRequestPermissionsResult( @Override public boolean onActivityResult(final int requestCode, final int resultCode, final Intent data) { - // Off-load result handling to a separate thread as it almost always involves I/O operations. - executor.execute( - () -> { - switch (requestCode) { - case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: - handleChooseImageResult(resultCode, data); - break; - case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: - handleChooseMultiImageResult(resultCode, data); - break; - case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: - handleCaptureImageResult(resultCode); - break; - case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: - handleChooseVideoResult(resultCode, data); - break; - case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: - handleCaptureVideoResult(resultCode); - break; - } - }); + Runnable handlerRunnable; - return requestCode == REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY - || requestCode == REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY - || requestCode == REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA - || requestCode == REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY - || requestCode == REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA; + switch (requestCode) { + case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: + handlerRunnable = () -> handleChooseImageResult(resultCode, data); + break; + case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: + handlerRunnable = () -> handleChooseMultiImageResult(resultCode, data); + break; + case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: + handlerRunnable = () -> handleCaptureImageResult(resultCode); + break; + case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: + handlerRunnable = () -> handleChooseVideoResult(resultCode, data); + break; + case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: + handlerRunnable = () -> handleCaptureVideoResult(resultCode); + break; + default: + return false; + } + + executor.execute(handlerRunnable); + + return true; } private void handleChooseImageResult(int resultCode, Intent data) { From 7fa110109afdb3f769a858d61f92a11ca43236b9 Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Wed, 5 Apr 2023 11:52:45 +0200 Subject: [PATCH 06/10] Synchronize `setPendingOptionsAndResult` --- .../io/flutter/plugins/imagepicker/ImagePickerDelegate.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 e94bcee60ac..b2563800e9a 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 @@ -33,6 +33,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -191,7 +192,7 @@ public void onScanCompleted(String path, Uri uri) { } }, new FileUtils(), - new ThreadPoolExecutor(0, 1, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>())); + Executors.newSingleThreadExecutor()); } /** @@ -697,7 +698,7 @@ private void handleVideoResult(String path) { finishWithSuccess(path); } - private boolean setPendingOptionsAndResult( + private synchronized boolean setPendingOptionsAndResult( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, @NonNull Messages.Result> result) { From b3e32b136aa3cefa208f64f05f2e5325a136cd94 Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Wed, 5 Apr 2023 12:15:42 +0200 Subject: [PATCH 07/10] Remove unused imports --- .../io/flutter/plugins/imagepicker/ImagePickerDelegate.java | 3 --- 1 file changed, 3 deletions(-) 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 b2563800e9a..9e4f5510074 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 @@ -34,9 +34,6 @@ import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; /** * A delegate class doing the heavy lifting for the plugin. From 38e67c1d968057f77f07dc46db4e85c03e3a92fc Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Wed, 12 Apr 2023 12:12:12 +0200 Subject: [PATCH 08/10] Synchronize thread-unsafe code --- .../imagepicker/ImagePickerDelegate.java | 126 +++++++++++++----- 1 file changed, 90 insertions(+), 36 deletions(-) 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 9e4f5510074..4bb8e8b6fe2 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 @@ -137,6 +137,7 @@ interface OnPathReadyListener { private Uri pendingCameraMediaUri; private @Nullable PendingCallState pendingCallState; + private final Object pendingCallStateLock = new Object(); public ImagePickerDelegate( final Activity activity, @@ -230,19 +231,25 @@ void setCameraDevice(CameraDevice device) { // Save the state of the image picker so it can be retrieved with `retrieveLostImage`. void saveStateBeforeResult() { - if (pendingCallState == null) { - return; + ImageSelectionOptions localImageOptions; + synchronized (pendingCallStateLock) { + if (pendingCallState == null) { + return; + } + localImageOptions = pendingCallState.imageOptions; } cache.saveType( - pendingCallState.imageOptions != null + localImageOptions != null ? ImagePickerCache.CacheType.IMAGE : ImagePickerCache.CacheType.VIDEO); - if (pendingCallState.imageOptions != null) { - cache.saveDimensionWithOutputOptions(pendingCallState.imageOptions); + if (localImageOptions != null) { + cache.saveDimensionWithOutputOptions(localImageOptions); } - if (pendingCameraMediaUri != null) { - cache.savePendingCameraMediaUriPath(pendingCameraMediaUri); + + final Uri localPendingCameraMediaUri = pendingCameraMediaUri; + if (localPendingCameraMediaUri != null) { + cache.savePendingCameraMediaUriPath(localPendingCameraMediaUri); } } @@ -329,10 +336,17 @@ public void takeVideoWithCamera( private void launchTakeVideoWithCameraIntent() { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); - if (pendingCallState != null - && pendingCallState.videoOptions != null - && pendingCallState.videoOptions.getMaxDurationSeconds() != null) { - int maxSeconds = pendingCallState.videoOptions.getMaxDurationSeconds().intValue(); + + VideoSelectionOptions localVideoOptions = null; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + localVideoOptions = pendingCallState.videoOptions; + } + } + + if (localVideoOptions != null + && localVideoOptions.getMaxDurationSeconds() != null) { + int maxSeconds = localVideoOptions.getMaxDurationSeconds().intValue(); intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, maxSeconds); } if (cameraDevice == CameraDevice.FRONT) { @@ -613,9 +627,11 @@ private void handleChooseVideoResult(int resultCode, Intent data) { private void handleCaptureImageResult(int resultCode) { if (resultCode == Activity.RESULT_OK) { + final Uri localPendingCameraMediaUri = pendingCameraMediaUri; + fileUriResolver.getFullImagePath( - pendingCameraMediaUri != null - ? pendingCameraMediaUri + localPendingCameraMediaUri != null + ? localPendingCameraMediaUri : Uri.parse(cache.retrievePendingCameraMediaUriPath()), new OnPathReadyListener() { @Override @@ -632,9 +648,10 @@ public void onPathReady(String path) { private void handleCaptureVideoResult(int resultCode) { if (resultCode == Activity.RESULT_OK) { + final Uri localPendingCameraMediaUrl = pendingCameraMediaUri; fileUriResolver.getFullImagePath( - pendingCameraMediaUri != null - ? pendingCameraMediaUri + localPendingCameraMediaUrl != null + ? localPendingCameraMediaUrl : Uri.parse(cache.retrievePendingCameraMediaUriPath()), new OnPathReadyListener() { @Override @@ -651,10 +668,17 @@ public void onPathReady(String path) { private void handleMultiImageResult( ArrayList paths, boolean shouldDeleteOriginalIfScaled) { - if (pendingCallState != null && pendingCallState.imageOptions != null) { + ImageSelectionOptions localImageOptions = null; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + localImageOptions = pendingCallState.imageOptions; + } + } + + if (localImageOptions != null) { ArrayList finalPath = new ArrayList<>(); for (int i = 0; i < paths.size(); i++) { - String finalImagePath = getResizedImagePath(paths.get(i), pendingCallState.imageOptions); + String finalImagePath = getResizedImagePath(paths.get(i), localImageOptions); //delete original file if scaled if (finalImagePath != null @@ -671,8 +695,15 @@ private void handleMultiImageResult( } private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { - if (pendingCallState != null && pendingCallState.imageOptions != null) { - String finalImagePath = getResizedImagePath(path, pendingCallState.imageOptions); + ImageSelectionOptions localImageOptions = null; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + localImageOptions = pendingCallState.imageOptions; + } + } + + if (localImageOptions != null) { + String finalImagePath = getResizedImagePath(path, localImageOptions); //delete original file if scaled if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) { new File(path).delete(); @@ -695,16 +726,17 @@ private void handleVideoResult(String path) { finishWithSuccess(path); } - private synchronized boolean setPendingOptionsAndResult( + private boolean setPendingOptionsAndResult( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, @NonNull Messages.Result> result) { - if (pendingCallState != null) { - return false; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + return false; + } + pendingCallState = new PendingCallState(imageOptions, videoOptions, result); } - pendingCallState = new PendingCallState(imageOptions, videoOptions, result); - // Clean up cache if a new image picker is launched. cache.clear(); @@ -720,24 +752,39 @@ private void finishWithSuccess(@Nullable String imagePath) { if (imagePath != null) { pathList.add(imagePath); } - if (pendingCallState == null) { + + Messages.Result> localResult = null; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + localResult = pendingCallState.result; + } + pendingCallState = null; + } + + if (localResult == null) { // Only save data for later retrieval if something was actually selected. if (!pathList.isEmpty()) { cache.saveResult(pathList, null, null); } - return; + } else { + localResult.success(pathList); } - pendingCallState.result.success(pathList); - pendingCallState = null; } private void finishWithListSuccess(ArrayList imagePaths) { - if (pendingCallState == null) { + Messages.Result> localResult = null; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + localResult = pendingCallState.result; + } + pendingCallState = null; + } + + if (localResult == null) { cache.saveResult(imagePaths, null, null); - return; + } else { + localResult.success(imagePaths); } - pendingCallState.result.success(imagePaths); - pendingCallState = null; } private void finishWithAlreadyActiveError(Messages.Result> result) { @@ -745,12 +792,19 @@ private void finishWithAlreadyActiveError(Messages.Result> result) } private void finishWithError(String errorCode, String errorMessage) { - if (pendingCallState == null) { + Messages.Result> localResult = null; + synchronized (pendingCallStateLock) { + if (pendingCallState != null) { + localResult = pendingCallState.result; + } + pendingCallState = null; + } + + if (localResult == null) { cache.saveResult(null, errorCode, errorMessage); - return; + } else { + localResult.error(new FlutterError(errorCode, errorMessage, null)); } - pendingCallState.result.error(new FlutterError(errorCode, errorMessage, null)); - pendingCallState = null; } private void useFrontCamera(Intent intent) { From f42d307c1312ea63d7d73d00b5f3bc5d3cdee4a8 Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Wed, 12 Apr 2023 12:16:51 +0200 Subject: [PATCH 09/10] Format `ImagePickerDelegate.java` --- .../io/flutter/plugins/imagepicker/ImagePickerDelegate.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 4bb8e8b6fe2..6aa1181ce90 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 @@ -240,7 +240,7 @@ void saveStateBeforeResult() { } cache.saveType( - localImageOptions != null + localImageOptions != null ? ImagePickerCache.CacheType.IMAGE : ImagePickerCache.CacheType.VIDEO); if (localImageOptions != null) { @@ -344,8 +344,7 @@ private void launchTakeVideoWithCameraIntent() { } } - if (localVideoOptions != null - && localVideoOptions.getMaxDurationSeconds() != null) { + if (localVideoOptions != null && localVideoOptions.getMaxDurationSeconds() != null) { int maxSeconds = localVideoOptions.getMaxDurationSeconds().intValue(); intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, maxSeconds); } From 557bf022d4836a9869ff5c68d3a5cc9e9725bbf5 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 28 Apr 2023 06:56:36 -0400 Subject: [PATCH 10/10] Re-bump version --- packages/image_picker/image_picker_android/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index e95d14c84c9..ec6a0b3b767 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ 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.6+9 +version: 0.8.6+10 environment: sdk: ">=2.18.0 <4.0.0"