3232import java .util .List ;
3333import java .util .Map ;
3434import java .util .UUID ;
35+ import java .util .concurrent .ExecutorService ;
36+ import java .util .concurrent .Executors ;
3537
3638/**
3739 * A delegate class doing the heavy lifting for the plugin.
@@ -112,6 +114,7 @@ private PendingCallState(
112114 private final PermissionManager permissionManager ;
113115 private final FileUriResolver fileUriResolver ;
114116 private final FileUtils fileUtils ;
117+ private final ExecutorService executor ;
115118 private CameraDevice cameraDevice ;
116119
117120 interface PermissionManager {
@@ -134,6 +137,7 @@ interface OnPathReadyListener {
134137
135138 private Uri pendingCameraMediaUri ;
136139 private @ Nullable PendingCallState pendingCallState ;
140+ private final Object pendingCallStateLock = new Object ();
137141
138142 public ImagePickerDelegate (
139143 final Activity activity ,
@@ -185,7 +189,8 @@ public void onScanCompleted(String path, Uri uri) {
185189 });
186190 }
187191 },
188- new FileUtils ());
192+ new FileUtils (),
193+ Executors .newSingleThreadExecutor ());
189194 }
190195
191196 /**
@@ -203,7 +208,8 @@ public void onScanCompleted(String path, Uri uri) {
203208 final ImagePickerCache cache ,
204209 final PermissionManager permissionManager ,
205210 final FileUriResolver fileUriResolver ,
206- final FileUtils fileUtils ) {
211+ final FileUtils fileUtils ,
212+ final ExecutorService executor ) {
207213 this .activity = activity ;
208214 this .externalFilesDirectory = externalFilesDirectory ;
209215 this .imageResizer = imageResizer ;
@@ -216,6 +222,7 @@ public void onScanCompleted(String path, Uri uri) {
216222 this .fileUriResolver = fileUriResolver ;
217223 this .fileUtils = fileUtils ;
218224 this .cache = cache ;
225+ this .executor = executor ;
219226 }
220227
221228 void setCameraDevice (CameraDevice device ) {
@@ -224,19 +231,25 @@ void setCameraDevice(CameraDevice device) {
224231
225232 // Save the state of the image picker so it can be retrieved with `retrieveLostImage`.
226233 void saveStateBeforeResult () {
227- if (pendingCallState == null ) {
228- return ;
234+ ImageSelectionOptions localImageOptions ;
235+ synchronized (pendingCallStateLock ) {
236+ if (pendingCallState == null ) {
237+ return ;
238+ }
239+ localImageOptions = pendingCallState .imageOptions ;
229240 }
230241
231242 cache .saveType (
232- pendingCallState . imageOptions != null
243+ localImageOptions != null
233244 ? ImagePickerCache .CacheType .IMAGE
234245 : ImagePickerCache .CacheType .VIDEO );
235- if (pendingCallState . imageOptions != null ) {
236- cache .saveDimensionWithOutputOptions (pendingCallState . imageOptions );
246+ if (localImageOptions != null ) {
247+ cache .saveDimensionWithOutputOptions (localImageOptions );
237248 }
238- if (pendingCameraMediaUri != null ) {
239- cache .savePendingCameraMediaUriPath (pendingCameraMediaUri );
249+
250+ final Uri localPendingCameraMediaUri = pendingCameraMediaUri ;
251+ if (localPendingCameraMediaUri != null ) {
252+ cache .savePendingCameraMediaUriPath (localPendingCameraMediaUri );
240253 }
241254 }
242255
@@ -323,10 +336,16 @@ public void takeVideoWithCamera(
323336
324337 private void launchTakeVideoWithCameraIntent () {
325338 Intent intent = new Intent (MediaStore .ACTION_VIDEO_CAPTURE );
326- if (pendingCallState != null
327- && pendingCallState .videoOptions != null
328- && pendingCallState .videoOptions .getMaxDurationSeconds () != null ) {
329- int maxSeconds = pendingCallState .videoOptions .getMaxDurationSeconds ().intValue ();
339+
340+ VideoSelectionOptions localVideoOptions = null ;
341+ synchronized (pendingCallStateLock ) {
342+ if (pendingCallState != null ) {
343+ localVideoOptions = pendingCallState .videoOptions ;
344+ }
345+ }
346+
347+ if (localVideoOptions != null && localVideoOptions .getMaxDurationSeconds () != null ) {
348+ int maxSeconds = localVideoOptions .getMaxDurationSeconds ().intValue ();
330349 intent .putExtra (MediaStore .EXTRA_DURATION_LIMIT , maxSeconds );
331350 }
332351 if (cameraDevice == CameraDevice .FRONT ) {
@@ -537,27 +556,31 @@ public boolean onRequestPermissionsResult(
537556 }
538557
539558 @ Override
540- public boolean onActivityResult (int requestCode , int resultCode , Intent data ) {
559+ public boolean onActivityResult (final int requestCode , final int resultCode , final Intent data ) {
560+ Runnable handlerRunnable ;
561+
541562 switch (requestCode ) {
542563 case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY :
543- handleChooseImageResult (resultCode , data );
564+ handlerRunnable = () -> handleChooseImageResult (resultCode , data );
544565 break ;
545566 case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY :
546- handleChooseMultiImageResult (resultCode , data );
567+ handlerRunnable = () -> handleChooseMultiImageResult (resultCode , data );
547568 break ;
548569 case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA :
549- handleCaptureImageResult (resultCode );
570+ handlerRunnable = () -> handleCaptureImageResult (resultCode );
550571 break ;
551572 case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY :
552- handleChooseVideoResult (resultCode , data );
573+ handlerRunnable = () -> handleChooseVideoResult (resultCode , data );
553574 break ;
554575 case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA :
555- handleCaptureVideoResult (resultCode );
576+ handlerRunnable = () -> handleCaptureVideoResult (resultCode );
556577 break ;
557578 default :
558579 return false ;
559580 }
560581
582+ executor .execute (handlerRunnable );
583+
561584 return true ;
562585 }
563586
@@ -603,9 +626,11 @@ private void handleChooseVideoResult(int resultCode, Intent data) {
603626
604627 private void handleCaptureImageResult (int resultCode ) {
605628 if (resultCode == Activity .RESULT_OK ) {
629+ final Uri localPendingCameraMediaUri = pendingCameraMediaUri ;
630+
606631 fileUriResolver .getFullImagePath (
607- pendingCameraMediaUri != null
608- ? pendingCameraMediaUri
632+ localPendingCameraMediaUri != null
633+ ? localPendingCameraMediaUri
609634 : Uri .parse (cache .retrievePendingCameraMediaUriPath ()),
610635 new OnPathReadyListener () {
611636 @ Override
@@ -622,9 +647,10 @@ public void onPathReady(String path) {
622647
623648 private void handleCaptureVideoResult (int resultCode ) {
624649 if (resultCode == Activity .RESULT_OK ) {
650+ final Uri localPendingCameraMediaUrl = pendingCameraMediaUri ;
625651 fileUriResolver .getFullImagePath (
626- pendingCameraMediaUri != null
627- ? pendingCameraMediaUri
652+ localPendingCameraMediaUrl != null
653+ ? localPendingCameraMediaUrl
628654 : Uri .parse (cache .retrievePendingCameraMediaUriPath ()),
629655 new OnPathReadyListener () {
630656 @ Override
@@ -641,10 +667,17 @@ public void onPathReady(String path) {
641667
642668 private void handleMultiImageResult (
643669 ArrayList <String > paths , boolean shouldDeleteOriginalIfScaled ) {
644- if (pendingCallState != null && pendingCallState .imageOptions != null ) {
670+ ImageSelectionOptions localImageOptions = null ;
671+ synchronized (pendingCallStateLock ) {
672+ if (pendingCallState != null ) {
673+ localImageOptions = pendingCallState .imageOptions ;
674+ }
675+ }
676+
677+ if (localImageOptions != null ) {
645678 ArrayList <String > finalPath = new ArrayList <>();
646679 for (int i = 0 ; i < paths .size (); i ++) {
647- String finalImagePath = getResizedImagePath (paths .get (i ), pendingCallState . imageOptions );
680+ String finalImagePath = getResizedImagePath (paths .get (i ), localImageOptions );
648681
649682 //delete original file if scaled
650683 if (finalImagePath != null
@@ -661,8 +694,15 @@ private void handleMultiImageResult(
661694 }
662695
663696 private void handleImageResult (String path , boolean shouldDeleteOriginalIfScaled ) {
664- if (pendingCallState != null && pendingCallState .imageOptions != null ) {
665- String finalImagePath = getResizedImagePath (path , pendingCallState .imageOptions );
697+ ImageSelectionOptions localImageOptions = null ;
698+ synchronized (pendingCallStateLock ) {
699+ if (pendingCallState != null ) {
700+ localImageOptions = pendingCallState .imageOptions ;
701+ }
702+ }
703+
704+ if (localImageOptions != null ) {
705+ String finalImagePath = getResizedImagePath (path , localImageOptions );
666706 //delete original file if scaled
667707 if (finalImagePath != null && !finalImagePath .equals (path ) && shouldDeleteOriginalIfScaled ) {
668708 new File (path ).delete ();
@@ -689,12 +729,13 @@ private boolean setPendingOptionsAndResult(
689729 @ Nullable ImageSelectionOptions imageOptions ,
690730 @ Nullable VideoSelectionOptions videoOptions ,
691731 @ NonNull Messages .Result <List <String >> result ) {
692- if (pendingCallState != null ) {
693- return false ;
732+ synchronized (pendingCallStateLock ) {
733+ if (pendingCallState != null ) {
734+ return false ;
735+ }
736+ pendingCallState = new PendingCallState (imageOptions , videoOptions , result );
694737 }
695738
696- pendingCallState = new PendingCallState (imageOptions , videoOptions , result );
697-
698739 // Clean up cache if a new image picker is launched.
699740 cache .clear ();
700741
@@ -710,37 +751,59 @@ private void finishWithSuccess(@Nullable String imagePath) {
710751 if (imagePath != null ) {
711752 pathList .add (imagePath );
712753 }
713- if (pendingCallState == null ) {
754+
755+ Messages .Result <List <String >> localResult = null ;
756+ synchronized (pendingCallStateLock ) {
757+ if (pendingCallState != null ) {
758+ localResult = pendingCallState .result ;
759+ }
760+ pendingCallState = null ;
761+ }
762+
763+ if (localResult == null ) {
714764 // Only save data for later retrieval if something was actually selected.
715765 if (!pathList .isEmpty ()) {
716766 cache .saveResult (pathList , null , null );
717767 }
718- return ;
768+ } else {
769+ localResult .success (pathList );
719770 }
720- pendingCallState .result .success (pathList );
721- pendingCallState = null ;
722771 }
723772
724773 private void finishWithListSuccess (ArrayList <String > imagePaths ) {
725- if (pendingCallState == null ) {
774+ Messages .Result <List <String >> localResult = null ;
775+ synchronized (pendingCallStateLock ) {
776+ if (pendingCallState != null ) {
777+ localResult = pendingCallState .result ;
778+ }
779+ pendingCallState = null ;
780+ }
781+
782+ if (localResult == null ) {
726783 cache .saveResult (imagePaths , null , null );
727- return ;
784+ } else {
785+ localResult .success (imagePaths );
728786 }
729- pendingCallState .result .success (imagePaths );
730- pendingCallState = null ;
731787 }
732788
733789 private void finishWithAlreadyActiveError (Messages .Result <List <String >> result ) {
734790 result .error (new FlutterError ("already_active" , "Image picker is already active" , null ));
735791 }
736792
737793 private void finishWithError (String errorCode , String errorMessage ) {
738- if (pendingCallState == null ) {
794+ Messages .Result <List <String >> localResult = null ;
795+ synchronized (pendingCallStateLock ) {
796+ if (pendingCallState != null ) {
797+ localResult = pendingCallState .result ;
798+ }
799+ pendingCallState = null ;
800+ }
801+
802+ if (localResult == null ) {
739803 cache .saveResult (null , errorCode , errorMessage );
740- return ;
804+ } else {
805+ localResult .error (new FlutterError (errorCode , errorMessage , null ));
741806 }
742- pendingCallState .result .error (new FlutterError (errorCode , errorMessage , null ));
743- pendingCallState = null ;
744807 }
745808
746809 private void useFrontCamera (Intent intent ) {
0 commit comments