ソースを参照

update version

i2edu 4 年 前
コミット
ec20f1963c
30 ファイル変更547 行追加601 行削除
  1. 4 0
      android/build.gradle
  2. 43 41
      android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java
  3. 13 23
      android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java
  4. 1 1
      android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java
  5. 1 1
      android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
  6. 1 1
      android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerFileProvider.java
  7. 2 1
      android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
  8. 1 1
      android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
  9. 1 1
      android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java
  10. 16 0
      example/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_1_0_aar.xml
  11. 1 1
      example/android/app/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java
  12. 403 391
      example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
  13. 3 3
      example/pubspec.yaml
  14. 1 1
      ios/Classes/FLTImagePickerImageUtil.h
  15. 1 1
      ios/Classes/FLTImagePickerImageUtil.m
  16. 1 1
      ios/Classes/FLTImagePickerMetaDataUtil.h
  17. 1 1
      ios/Classes/FLTImagePickerMetaDataUtil.m
  18. 1 1
      ios/Classes/FLTImagePickerPhotoAssetUtil.h
  19. 1 1
      ios/Classes/FLTImagePickerPhotoAssetUtil.m
  20. 1 1
      ios/Classes/FLTImagePickerPlugin.h
  21. 4 4
      ios/Classes/FLTImagePickerPlugin.m
  22. 1 1
      ios/Tests/ImagePickerPluginTests.m
  23. 1 1
      ios/Tests/ImagePickerTestImages.h
  24. 1 1
      ios/Tests/ImagePickerTestImages.m
  25. 1 1
      ios/Tests/ImageUtilTests.m
  26. 1 1
      ios/Tests/MetaDataUtilTests.m
  27. 1 1
      ios/Tests/PhotoAssetUtilTests.m
  28. 1 1
      ios/image_picker.podspec
  29. 26 110
      lib/image_picker.dart
  30. 13 8
      pubspec.yaml

+ 4 - 0
android/build.gradle

@@ -39,3 +39,7 @@ android {
         implementation 'androidx.annotation:annotation:1.0.0'
     }
 }
+
+dependencies {
+    implementation 'androidx.exifinterface:exifinterface:1.1.0'
+}

+ 43 - 41
android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java

@@ -1,55 +1,57 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 package io.flutter.plugins.imagepicker;
 
-import android.media.ExifInterface;
 import android.util.Log;
+
+import androidx.exifinterface.media.ExifInterface;
+
 import java.util.Arrays;
 import java.util.List;
 
 class ExifDataCopier {
-  void copyExif(String filePathOri, String filePathDest) {
-    try {
-      ExifInterface oldExif = new ExifInterface(filePathOri);
-      ExifInterface newExif = new ExifInterface(filePathDest);
-
-      List<String> attributes =
-          Arrays.asList(
-              "FNumber",
-              "ExposureTime",
-              "ISOSpeedRatings",
-              "GPSAltitude",
-              "GPSAltitudeRef",
-              "FocalLength",
-              "GPSDateStamp",
-              "WhiteBalance",
-              "GPSProcessingMethod",
-              "GPSTimeStamp",
-              "DateTime",
-              "Flash",
-              "GPSLatitude",
-              "GPSLatitudeRef",
-              "GPSLongitude",
-              "GPSLongitudeRef",
-              "Make",
-              "Model",
-              "Orientation");
-      for (String attribute : attributes) {
-        setIfNotNull(oldExif, newExif, attribute);
-      }
-
-      newExif.saveAttributes();
-
-    } catch (Exception ex) {
-      Log.e("ExifDataCopier", "Error preserving Exif data on selected image: " + ex);
+    void copyExif(String filePathOri, String filePathDest) {
+        try {
+            ExifInterface oldExif = new ExifInterface(filePathOri);
+            ExifInterface newExif = new ExifInterface(filePathDest);
+
+            List<String> attributes =
+                    Arrays.asList(
+                            "FNumber",
+                            "ExposureTime",
+                            "ISOSpeedRatings",
+                            "GPSAltitude",
+                            "GPSAltitudeRef",
+                            "FocalLength",
+                            "GPSDateStamp",
+                            "WhiteBalance",
+                            "GPSProcessingMethod",
+                            "GPSTimeStamp",
+                            "DateTime",
+                            "Flash",
+                            "GPSLatitude",
+                            "GPSLatitudeRef",
+                            "GPSLongitude",
+                            "GPSLongitudeRef",
+                            "Make",
+                            "Model",
+                            "Orientation");
+            for (String attribute : attributes) {
+                setIfNotNull(oldExif, newExif, attribute);
+            }
+
+            newExif.saveAttributes();
+
+        } catch (Exception ex) {
+            Log.e("ExifDataCopier", "Error preserving Exif data on selected image: " + ex);
+        }
     }
-  }
 
-  private static void setIfNotNull(ExifInterface oldExif, ExifInterface newExif, String property) {
-    if (oldExif.getAttribute(property) != null) {
-      newExif.setAttribute(property, oldExif.getAttribute(property));
+    private static void setIfNotNull(ExifInterface oldExif, ExifInterface newExif, String property) {
+        if (oldExif.getAttribute(property) != null) {
+            newExif.setAttribute(property, oldExif.getAttribute(property));
+        }
     }
-  }
 }

+ 13 - 23
android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -23,11 +23,10 @@
 
 package io.flutter.plugins.imagepicker;
 
+import android.content.ContentResolver;
 import android.content.Context;
-import android.database.Cursor;
 import android.net.Uri;
-import android.provider.MediaStore;
-import android.provider.OpenableColumns;
+import android.webkit.MimeTypeMap;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -37,7 +36,7 @@ import java.io.OutputStream;
 
 class FileUtils {
 
-    String getPathFromUri(final Context context, final Uri uri, final boolean isVideo) {
+    String getPathFromUri(final Context context, final Uri uri, boolean isVideo) {
         File file = null;
         InputStream inputStream = null;
         OutputStream outputStream = null;
@@ -75,10 +74,16 @@ class FileUtils {
      */
     private static String getImageExtension(Context context, Uri uriImage, boolean isVideo) {
         String extension = null;
+
         try {
-            String imagePath = getPath(context, uriImage);
-            if (imagePath != null && imagePath.lastIndexOf(".") != -1) {
-                extension = imagePath.substring(imagePath.lastIndexOf(".") + 1);
+            String imagePath = uriImage.getPath();
+            if (uriImage.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+                final MimeTypeMap mime = MimeTypeMap.getSingleton();
+                extension = mime.getExtensionFromMimeType(context.getContentResolver().getType(uriImage));
+            } else {
+                extension =
+                        MimeTypeMap.getFileExtensionFromUrl(
+                                Uri.fromFile(new File(uriImage.getPath())).toString());
             }
         } catch (Exception e) {
             extension = null;
@@ -96,21 +101,6 @@ class FileUtils {
         return "." + extension;
     }
 
-    private static String getPath(Context context, Uri uriImage) {
-        if ("content".equalsIgnoreCase(uriImage.getScheme())) {
-            if (uriImage == null) return null;
-            Cursor cursor = context.getContentResolver().query(uriImage, null, null, null, null);
-            cursor.moveToFirst();
-            int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
-            String filePath = cursor.getString(columnIndex);
-            cursor.close();
-            return filePath;
-        } else if ("file".equalsIgnoreCase(uriImage.getScheme())) {
-            return uriImage.getPath();
-        }
-        return uriImage.getPath();
-    }
-
     private static void copy(InputStream in, OutputStream out) throws IOException {
         final byte[] buffer = new byte[4 * 1024];
         int bytesRead;

+ 1 - 1
android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerFileProvider.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 2 - 1
android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -111,6 +111,7 @@ public class ImagePickerPlugin
   private Lifecycle lifecycle;
   private LifeCycleObserver observer;
 
+  @SuppressWarnings("deprecation")
   public static void registerWith(PluginRegistry.Registrar registrar) {
     if (registrar.activity() == null) {
       // If a background flutter view tries to register the plugin, there will be no activity from the registrar,

+ 1 - 1
android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 16 - 0
example/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_1_0_aar.xml

@@ -0,0 +1,16 @@
+<component name="libraryTable">
+  <library name="Gradle: androidx.exifinterface:exifinterface:1.1.0@aar">
+    <ANNOTATIONS>
+      <root url="jar://$PROJECT_DIR$/../../../../../android_setting/gradle/caches/transforms-1/files-1.1/exifinterface-1.1.0.aar/d25fc8ebc232f05b2cc609d0ed9163ce/annotations.zip!/" />
+    </ANNOTATIONS>
+    <CLASSES>
+      <root url="file://$PROJECT_DIR$/../../../../../android_setting/gradle/caches/transforms-1/files-1.1/exifinterface-1.1.0.aar/d25fc8ebc232f05b2cc609d0ed9163ce/AndroidManifest.xml" />
+      <root url="jar://$PROJECT_DIR$/../../../../../android_setting/gradle/caches/transforms-1/files-1.1/exifinterface-1.1.0.aar/d25fc8ebc232f05b2cc609d0ed9163ce/jars/classes.jar!/" />
+      <root url="file://$PROJECT_DIR$/../../../../../android_setting/gradle/caches/transforms-1/files-1.1/exifinterface-1.1.0.aar/d25fc8ebc232f05b2cc609d0ed9163ce/res" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="jar://$PROJECT_DIR$/../../../../../android_setting/gradle/caches/modules-2/files-2.1/androidx.exifinterface/exifinterface/1.1.0/bb881ec6735bb5e3a242bc9ee63cf338d665572/exifinterface-1.1.0-sources.jar!/" />
+    </SOURCES>
+  </library>
+</component>

+ 1 - 1
example/android/app/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java

@@ -41,7 +41,7 @@ public class FileUtilTest {
     Uri uri = Uri.parse("content://dummy/dummy.png");
     shadowContentResolver.registerInputStream(
         uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8)));
-    String path = fileUtils.getPathFromUri(context, uri);
+    String path = fileUtils.getPathFromUri(context, uri,false);
     File file = new File(path);
     int size = (int) file.length();
     byte[] bytes = new byte[size];

+ 403 - 391
example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java

@@ -15,407 +15,419 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
+
 import java.io.File;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 public class ImagePickerDelegateTest {
-  private static final Double WIDTH = 10.0;
-  private static final Double HEIGHT = 10.0;
-  private static final Double MAX_DURATION = 10.0;
-  private static final Integer IMAGE_QUALITY = 90;
-
-  @Mock Activity mockActivity;
-  @Mock ImageResizer mockImageResizer;
-  @Mock MethodCall mockMethodCall;
-  @Mock MethodChannel.Result mockResult;
-  @Mock ImagePickerDelegate.PermissionManager mockPermissionManager;
-  @Mock ImagePickerDelegate.IntentResolver mockIntentResolver;
-  @Mock FileUtils mockFileUtils;
-  @Mock Intent mockIntent;
-  @Mock ImagePickerCache cache;
-
-  ImagePickerDelegate.FileUriResolver mockFileUriResolver;
-
-  private static class MockFileUriResolver implements ImagePickerDelegate.FileUriResolver {
-    @Override
-    public Uri resolveFileProviderUriForFile(String fileProviderName, File imageFile) {
-      return null;
+    private static final Double WIDTH = 10.0;
+    private static final Double HEIGHT = 10.0;
+    private static final Double MAX_DURATION = 10.0;
+    private static final Integer IMAGE_QUALITY = 90;
+
+    @Mock
+    Activity mockActivity;
+    @Mock
+    ImageResizer mockImageResizer;
+    @Mock
+    MethodCall mockMethodCall;
+    @Mock
+    MethodChannel.Result mockResult;
+    @Mock
+    ImagePickerDelegate.PermissionManager mockPermissionManager;
+    @Mock
+    ImagePickerDelegate.IntentResolver mockIntentResolver;
+    @Mock
+    FileUtils mockFileUtils;
+    @Mock
+    Intent mockIntent;
+    @Mock
+    ImagePickerCache cache;
+
+    ImagePickerDelegate.FileUriResolver mockFileUriResolver;
+
+    private static class MockFileUriResolver implements ImagePickerDelegate.FileUriResolver {
+        @Override
+        public Uri resolveFileProviderUriForFile(String fileProviderName, File imageFile) {
+            return null;
+        }
+
+        @Override
+        public void getFullImagePath(Uri imageUri, ImagePickerDelegate.OnPathReadyListener listener) {
+            listener.onPathReady("pathFromUri");
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mockActivity.getPackageName()).thenReturn("com.example.test");
+        when(mockActivity.getPackageManager()).thenReturn(mock(PackageManager.class));
+
+        when(mockFileUtils.getPathFromUri(any(Context.class), any(Uri.class), false))
+                .thenReturn("pathFromUri");
+
+        when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, null))
+                .thenReturn("originalPath");
+        when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, IMAGE_QUALITY))
+                .thenReturn("originalPath");
+        when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, null))
+                .thenReturn("scaledPath");
+        when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, null))
+                .thenReturn("scaledPath");
+        when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, null))
+                .thenReturn("scaledPath");
+
+        mockFileUriResolver = new MockFileUriResolver();
+
+        Uri mockUri = mock(Uri.class);
+        when(mockIntent.getData()).thenReturn(mockUri);
+    }
+
+    @Test
+    public void whenConstructed_setsCorrectFileProviderName() {
+        ImagePickerDelegate delegate = createDelegate();
+        assertThat(delegate.fileProviderName, equalTo("com.example.test.flutter.image_provider"));
+    }
+
+    @Test
+    public void chooseImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyActiveError() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.chooseImageFromGallery(mockMethodCall, mockResult);
+
+        verifyFinishedWithAlreadyActiveError();
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() {
+        when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE))
+                .thenReturn(false);
+
+        ImagePickerDelegate delegate = createDelegate();
+        delegate.chooseImageFromGallery(mockMethodCall, mockResult);
+
+        verify(mockPermissionManager)
+                .askForPermission(
+                        Manifest.permission.READ_EXTERNAL_STORAGE,
+                        ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION);
+    }
+
+    @Test
+    public void
+    chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() {
+        when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE))
+                .thenReturn(true);
+
+        ImagePickerDelegate delegate = createDelegate();
+        delegate.chooseImageFromGallery(mockMethodCall, mockResult);
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY));
+    }
+
+    @Test
+    public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.takeImageWithCamera(mockMethodCall, mockResult);
+
+        verifyFinishedWithAlreadyActiveError();
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void takeImageWithCamera_WhenHasNoCameraPermission_RequestsForPermission() {
+        when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(false);
+        when(mockPermissionManager.needRequestCameraPermission()).thenReturn(true);
+
+        ImagePickerDelegate delegate = createDelegate();
+        delegate.takeImageWithCamera(mockMethodCall, mockResult);
+
+        verify(mockPermissionManager)
+                .askForPermission(
+                        Manifest.permission.CAMERA, ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION);
+    }
+
+    @Test
+    public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermission() {
+        when(mockPermissionManager.needRequestCameraPermission()).thenReturn(false);
+        when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
+
+        ImagePickerDelegate delegate = createDelegate();
+        delegate.takeImageWithCamera(mockMethodCall, mockResult);
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
+    }
+
+    @Test
+    public void
+    takeImageWithCamera_WhenHasCameraPermission_AndAnActivityCanHandleCameraIntent_LaunchesTakeWithCameraIntent() {
+        when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true);
+        when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
+
+        ImagePickerDelegate delegate = createDelegate();
+        delegate.takeImageWithCamera(mockMethodCall, mockResult);
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
+    }
+
+    @Test
+    public void
+    takeImageWithCamera_WhenHasCameraPermission_AndNoActivityToHandleCameraIntent_FinishesWithNoCamerasAvailableError() {
+        when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true);
+        when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(false);
+
+        ImagePickerDelegate delegate = createDelegate();
+        delegate.takeImageWithCamera(mockMethodCall, mockResult);
+
+        verify(mockResult)
+                .error("no_available_camera", "No cameras available for taking pictures.", null);
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onRequestPermissionsResult_WhenReadExternalStoragePermissionDenied_FinishesWithError() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onRequestPermissionsResult(
+                ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION,
+                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+                new int[]{PackageManager.PERMISSION_DENIED});
+
+        verify(mockResult).error("photo_access_denied", "The user did not allow photo access.", null);
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onRequestChooseImagePermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseImageFromGalleryIntent() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onRequestPermissionsResult(
+                ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION,
+                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+                new int[]{PackageManager.PERMISSION_GRANTED});
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY));
+    }
+
+    @Test
+    public void
+    onRequestChooseVideoPermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseVideoFromGalleryIntent() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onRequestPermissionsResult(
+                ImagePickerDelegate.REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION,
+                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+                new int[]{PackageManager.PERMISSION_GRANTED});
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY));
+    }
+
+    @Test
+    public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithError() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onRequestPermissionsResult(
+                ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION,
+                new String[]{Manifest.permission.CAMERA},
+                new int[]{PackageManager.PERMISSION_DENIED});
+
+        verify(mockResult).error("camera_access_denied", "The user did not allow camera access.", null);
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onRequestTakeVideoPermissionsResult_WhenCameraPermissionGranted_LaunchesTakeVideoWithCameraIntent() {
+        when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onRequestPermissionsResult(
+                ImagePickerDelegate.REQUEST_CAMERA_VIDEO_PERMISSION,
+                new String[]{Manifest.permission.CAMERA},
+                new int[]{PackageManager.PERMISSION_GRANTED});
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA));
+    }
+
+    @Test
+    public void
+    onRequestTakeImagePermissionsResult_WhenCameraPermissionGranted_LaunchesTakeWithCameraIntent() {
+        when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onRequestPermissionsResult(
+                ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION,
+                new String[]{Manifest.permission.CAMERA},
+                new int[]{PackageManager.PERMISSION_GRANTED});
+
+        verify(mockActivity)
+                .startActivityForResult(
+                        any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
+    }
+
+    @Test
+    public void onActivityResult_WhenPickFromGalleryCanceled_FinishesWithNull() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null);
+
+        verify(mockResult).success(null);
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onActivityResult_WhenImagePickedFromGallery_AndNoResizeNeeded_FinishesWithImagePath() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("originalPath");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onActivityResult_WhenImagePickedFromGallery_AndResizeNeeded_FinishesWithScaledImagePath() {
+        when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("scaledPath");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onActivityResult_WhenVideoPickedFromGallery_AndResizeParametersSupplied_FinishesWithFilePath() {
+        when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("pathFromUri");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void onActivityResult_WhenTakeImageWithCameraCanceled_FinishesWithNull() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null);
+
+        verify(mockResult).success(null);
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_FinishesWithImagePath() {
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("originalPath");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onActivityResult_WhenImageTakenWithCamera_AndResizeNeeded_FinishesWithScaledImagePath() {
+        when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("scaledPath");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onActivityResult_WhenVideoTakenWithCamera_AndResizeParametersSupplied_FinishesWithFilePath() {
+        when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("pathFromUri");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    @Test
+    public void
+    onActivityResult_WhenVideoTakenWithCamera_AndMaxDurationParametersSupplied_FinishesWithFilePath() {
+        when(mockMethodCall.argument("maxDuration")).thenReturn(MAX_DURATION);
+
+        ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+        delegate.onActivityResult(
+                ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
+
+        verify(mockResult).success("pathFromUri");
+        verifyNoMoreInteractions(mockResult);
+    }
+
+    private ImagePickerDelegate createDelegate() {
+        return new ImagePickerDelegate(
+                mockActivity,
+                null,
+                mockImageResizer,
+                null,
+                null,
+                cache,
+                mockPermissionManager,
+                mockIntentResolver,
+                mockFileUriResolver,
+                mockFileUtils);
+    }
+
+    private ImagePickerDelegate createDelegateWithPendingResultAndMethodCall() {
+        return new ImagePickerDelegate(
+                mockActivity,
+                null,
+                mockImageResizer,
+                mockResult,
+                mockMethodCall,
+                cache,
+                mockPermissionManager,
+                mockIntentResolver,
+                mockFileUriResolver,
+                mockFileUtils);
     }
 
-    @Override
-    public void getFullImagePath(Uri imageUri, ImagePickerDelegate.OnPathReadyListener listener) {
-      listener.onPathReady("pathFromUri");
+    private void verifyFinishedWithAlreadyActiveError() {
+        verify(mockResult).error("already_active", "Image picker is already active", null);
     }
-  }
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-
-    when(mockActivity.getPackageName()).thenReturn("com.example.test");
-    when(mockActivity.getPackageManager()).thenReturn(mock(PackageManager.class));
-
-    when(mockFileUtils.getPathFromUri(any(Context.class), any(Uri.class)))
-        .thenReturn("pathFromUri");
-
-    when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, null))
-        .thenReturn("originalPath");
-    when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, IMAGE_QUALITY))
-        .thenReturn("originalPath");
-    when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, null))
-        .thenReturn("scaledPath");
-    when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, null))
-        .thenReturn("scaledPath");
-    when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, null))
-        .thenReturn("scaledPath");
-
-    mockFileUriResolver = new MockFileUriResolver();
-
-    Uri mockUri = mock(Uri.class);
-    when(mockIntent.getData()).thenReturn(mockUri);
-  }
-
-  @Test
-  public void whenConstructed_setsCorrectFileProviderName() {
-    ImagePickerDelegate delegate = createDelegate();
-    assertThat(delegate.fileProviderName, equalTo("com.example.test.flutter.image_provider"));
-  }
-
-  @Test
-  public void chooseImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyActiveError() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.chooseImageFromGallery(mockMethodCall, mockResult);
-
-    verifyFinishedWithAlreadyActiveError();
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() {
-    when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE))
-        .thenReturn(false);
-
-    ImagePickerDelegate delegate = createDelegate();
-    delegate.chooseImageFromGallery(mockMethodCall, mockResult);
-
-    verify(mockPermissionManager)
-        .askForPermission(
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION);
-  }
-
-  @Test
-  public void
-      chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() {
-    when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE))
-        .thenReturn(true);
-
-    ImagePickerDelegate delegate = createDelegate();
-    delegate.chooseImageFromGallery(mockMethodCall, mockResult);
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY));
-  }
-
-  @Test
-  public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.takeImageWithCamera(mockMethodCall, mockResult);
-
-    verifyFinishedWithAlreadyActiveError();
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void takeImageWithCamera_WhenHasNoCameraPermission_RequestsForPermission() {
-    when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(false);
-    when(mockPermissionManager.needRequestCameraPermission()).thenReturn(true);
-
-    ImagePickerDelegate delegate = createDelegate();
-    delegate.takeImageWithCamera(mockMethodCall, mockResult);
-
-    verify(mockPermissionManager)
-        .askForPermission(
-            Manifest.permission.CAMERA, ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION);
-  }
-
-  @Test
-  public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermission() {
-    when(mockPermissionManager.needRequestCameraPermission()).thenReturn(false);
-    when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
-
-    ImagePickerDelegate delegate = createDelegate();
-    delegate.takeImageWithCamera(mockMethodCall, mockResult);
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
-  }
-
-  @Test
-  public void
-      takeImageWithCamera_WhenHasCameraPermission_AndAnActivityCanHandleCameraIntent_LaunchesTakeWithCameraIntent() {
-    when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true);
-    when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
-
-    ImagePickerDelegate delegate = createDelegate();
-    delegate.takeImageWithCamera(mockMethodCall, mockResult);
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
-  }
-
-  @Test
-  public void
-      takeImageWithCamera_WhenHasCameraPermission_AndNoActivityToHandleCameraIntent_FinishesWithNoCamerasAvailableError() {
-    when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true);
-    when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(false);
-
-    ImagePickerDelegate delegate = createDelegate();
-    delegate.takeImageWithCamera(mockMethodCall, mockResult);
-
-    verify(mockResult)
-        .error("no_available_camera", "No cameras available for taking pictures.", null);
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onRequestPermissionsResult_WhenReadExternalStoragePermissionDenied_FinishesWithError() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onRequestPermissionsResult(
-        ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION,
-        new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
-        new int[] {PackageManager.PERMISSION_DENIED});
-
-    verify(mockResult).error("photo_access_denied", "The user did not allow photo access.", null);
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onRequestChooseImagePermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseImageFromGalleryIntent() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onRequestPermissionsResult(
-        ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION,
-        new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
-        new int[] {PackageManager.PERMISSION_GRANTED});
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY));
-  }
-
-  @Test
-  public void
-      onRequestChooseVideoPermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseVideoFromGalleryIntent() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onRequestPermissionsResult(
-        ImagePickerDelegate.REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION,
-        new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
-        new int[] {PackageManager.PERMISSION_GRANTED});
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY));
-  }
-
-  @Test
-  public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithError() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onRequestPermissionsResult(
-        ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION,
-        new String[] {Manifest.permission.CAMERA},
-        new int[] {PackageManager.PERMISSION_DENIED});
-
-    verify(mockResult).error("camera_access_denied", "The user did not allow camera access.", null);
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onRequestTakeVideoPermissionsResult_WhenCameraPermissionGranted_LaunchesTakeVideoWithCameraIntent() {
-    when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onRequestPermissionsResult(
-        ImagePickerDelegate.REQUEST_CAMERA_VIDEO_PERMISSION,
-        new String[] {Manifest.permission.CAMERA},
-        new int[] {PackageManager.PERMISSION_GRANTED});
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA));
-  }
-
-  @Test
-  public void
-      onRequestTakeImagePermissionsResult_WhenCameraPermissionGranted_LaunchesTakeWithCameraIntent() {
-    when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onRequestPermissionsResult(
-        ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION,
-        new String[] {Manifest.permission.CAMERA},
-        new int[] {PackageManager.PERMISSION_GRANTED});
-
-    verify(mockActivity)
-        .startActivityForResult(
-            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
-  }
-
-  @Test
-  public void onActivityResult_WhenPickFromGalleryCanceled_FinishesWithNull() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null);
-
-    verify(mockResult).success(null);
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onActivityResult_WhenImagePickedFromGallery_AndNoResizeNeeded_FinishesWithImagePath() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("originalPath");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onActivityResult_WhenImagePickedFromGallery_AndResizeNeeded_FinishesWithScaledImagePath() {
-    when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("scaledPath");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onActivityResult_WhenVideoPickedFromGallery_AndResizeParametersSupplied_FinishesWithFilePath() {
-    when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("pathFromUri");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void onActivityResult_WhenTakeImageWithCameraCanceled_FinishesWithNull() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null);
-
-    verify(mockResult).success(null);
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_FinishesWithImagePath() {
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("originalPath");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onActivityResult_WhenImageTakenWithCamera_AndResizeNeeded_FinishesWithScaledImagePath() {
-    when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("scaledPath");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onActivityResult_WhenVideoTakenWithCamera_AndResizeParametersSupplied_FinishesWithFilePath() {
-    when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("pathFromUri");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  @Test
-  public void
-      onActivityResult_WhenVideoTakenWithCamera_AndMaxDurationParametersSupplied_FinishesWithFilePath() {
-    when(mockMethodCall.argument("maxDuration")).thenReturn(MAX_DURATION);
-
-    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
-    delegate.onActivityResult(
-        ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent);
-
-    verify(mockResult).success("pathFromUri");
-    verifyNoMoreInteractions(mockResult);
-  }
-
-  private ImagePickerDelegate createDelegate() {
-    return new ImagePickerDelegate(
-        mockActivity,
-        null,
-        mockImageResizer,
-        null,
-        null,
-        cache,
-        mockPermissionManager,
-        mockIntentResolver,
-        mockFileUriResolver,
-        mockFileUtils);
-  }
-
-  private ImagePickerDelegate createDelegateWithPendingResultAndMethodCall() {
-    return new ImagePickerDelegate(
-        mockActivity,
-        null,
-        mockImageResizer,
-        mockResult,
-        mockMethodCall,
-        cache,
-        mockPermissionManager,
-        mockIntentResolver,
-        mockFileUriResolver,
-        mockFileUtils);
-  }
-
-  private void verifyFinishedWithAlreadyActiveError() {
-    verify(mockResult).error("already_active", "Image picker is already active", null);
-  }
 }

+ 3 - 3
example/pubspec.yaml

@@ -6,11 +6,11 @@ dependencies:
   video_player: ^0.10.3
   flutter:
     sdk: flutter
-  flutter_plugin_android_lifecycle: ^1.0.2
+  flutter_plugin_android_lifecycle: ^2.0.1
   image_picker:
     path: ../
-  image_picker_for_web: ^0.1.0
-
+  image_picker_platform_interface: ^2.0.0
+  image_picker_for_web: ^2.0.0
 dev_dependencies:
   flutter_driver:
     sdk: flutter

+ 1 - 1
ios/Classes/FLTImagePickerImageUtil.h

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Classes/FLTImagePickerImageUtil.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Classes/FLTImagePickerMetaDataUtil.h

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Classes/FLTImagePickerMetaDataUtil.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Classes/FLTImagePickerPhotoAssetUtil.h

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Classes/FLTImagePickerPhotoAssetUtil.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Classes/FLTImagePickerPlugin.h

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 4 - 4
ios/Classes/FLTImagePickerPlugin.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -146,10 +146,10 @@ static const int SOURCE_GALLERY = 1;
                                                       animated:YES
                                                     completion:nil];
   } else {
-    [[[UIAlertView alloc] initWithTitle:@"Error"
-                                message:@"Camera not available."
+    [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", nil)
+                                message:NSLocalizedString(@"Camera not available.", nil)
                                delegate:nil
-                      cancelButtonTitle:@"OK"
+                      cancelButtonTitle:NSLocalizedString(@"OK", nil)
                       otherButtonTitles:nil] show];
     self.result(nil);
     self.result = nil;

+ 1 - 1
ios/Tests/ImagePickerPluginTests.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Tests/ImagePickerTestImages.h

@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Tests/ImagePickerTestImages.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Tests/ImageUtilTests.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Tests/MetaDataUtilTests.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/Tests/PhotoAssetUtilTests.m

@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 

+ 1 - 1
ios/image_picker.podspec

@@ -18,7 +18,7 @@ Downloaded by pub (not CocoaPods).
   s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
   s.platform = :ios, '8.0'
-  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'armv7 arm64 x86_64' }
+  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
 
   s.test_spec 'Tests' do |test_spec|
     test_spec.source_files = 'Tests/**/*'

+ 26 - 110
lib/image_picker.dart

@@ -1,11 +1,10 @@
-// Copyright 2019 The Flutter Authors. All rights reserved.
+// Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package
 
 import 'dart:async';
-import 'dart:io';
 
 import 'package:flutter/foundation.dart';
 
@@ -13,61 +12,20 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
 
 export 'package:image_picker_platform_interface/image_picker_platform_interface.dart'
     show
-        kTypeImage,
-        kTypeVideo,
-        ImageSource,
-        CameraDevice,
-        LostData,
-        LostDataResponse,
-        PickedFile,
-        RetrieveType;
+    kTypeImage,
+    kTypeVideo,
+    ImageSource,
+    CameraDevice,
+    LostData,
+    PickedFile,
+    RetrieveType;
 
 /// Provides an easy way to pick an image/video from the image library,
 /// or to take a picture/video with the camera.
 class ImagePicker {
   /// The platform interface that drives this plugin
   @visibleForTesting
-  static ImagePickerPlatform platform = ImagePickerPlatform.instance;
-
-  /// Returns a [File] object pointing to the image that was picked.
-  ///
-  /// The returned [File] is intended to be used within a single APP session. Do not save the file path and use it across sessions.
-  ///
-  /// The `source` argument controls where the image comes from. This can
-  /// be either [ImageSource.camera] or [ImageSource.gallery].
-  ///
-  /// If specified, the image will be at most `maxWidth` wide and
-  /// `maxHeight` tall. Otherwise the image will be returned at it's
-  /// original width and height.
-  /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100
-  /// where 100 is the original/max quality. If `imageQuality` is null, the image with
-  /// the original quality will be returned. Compression is only supportted for certain
-  /// image types such as JPEG. If compression is not supported for the image that is picked,
-  /// an warning message will be logged.
-  ///
-  /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
-  /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
-  /// Defaults to [CameraDevice.rear].
-  ///
-  /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost
-  /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data.
-  @Deprecated('Use imagePicker.getImage() method instead.')
-  static Future<File> pickImage(
-      {@required ImageSource source,
-      double maxWidth,
-      double maxHeight,
-      int imageQuality,
-      CameraDevice preferredCameraDevice = CameraDevice.rear}) async {
-    String path = await platform.pickImagePath(
-      source: source,
-      maxWidth: maxWidth,
-      maxHeight: maxHeight,
-      imageQuality: imageQuality,
-      preferredCameraDevice: preferredCameraDevice,
-    );
-
-    return path == null ? null : File(path);
-  }
+  static ImagePickerPlatform get platform => ImagePickerPlatform.instance;
 
   /// Returns a [PickedFile] object wrapping the image that was picked.
   ///
@@ -76,26 +34,31 @@ class ImagePicker {
   /// The `source` argument controls where the image comes from. This can
   /// be either [ImageSource.camera] or [ImageSource.gallery].
   ///
+  /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used
+  /// in addition to a size modification, of which the usage is explained below.
+  ///
   /// If specified, the image will be at most `maxWidth` wide and
   /// `maxHeight` tall. Otherwise the image will be returned at it's
   /// original width and height.
   /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100
   /// where 100 is the original/max quality. If `imageQuality` is null, the image with
-  /// the original quality will be returned. Compression is only supportted for certain
-  /// image types such as JPEG. If compression is not supported for the image that is picked,
-  /// an warning message will be logged.
+  /// the original quality will be returned. Compression is only supported for certain
+  /// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked,
+  /// a warning message will be logged.
   ///
   /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
   /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
-  /// Defaults to [CameraDevice.rear].
+  /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if
+  /// the front or rear camera should be opened, this function is not guaranteed
+  /// to work on an Android device.
   ///
   /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost
   /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data.
-  Future<PickedFile> getImage({
-    @required ImageSource source,
-    double maxWidth,
-    double maxHeight,
-    int imageQuality,
+  Future<PickedFile?> getImage({
+    required ImageSource source,
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) {
     return platform.pickImage(
@@ -107,36 +70,6 @@ class ImagePicker {
     );
   }
 
-  /// Returns a [File] object pointing to the video that was picked.
-  ///
-  /// The returned [File] is intended to be used within a single APP session. Do not save the file path and use it across sessions.
-  ///
-  /// The [source] argument controls where the video comes from. This can
-  /// be either [ImageSource.camera] or [ImageSource.gallery].
-  ///
-  /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified,
-  /// the maximum duration will be infinite.
-  ///
-  /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
-  /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
-  /// Defaults to [CameraDevice.rear].
-  ///
-  /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost
-  /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data.
-  @Deprecated('Use imagePicker.getVideo() method instead.')
-  static Future<File> pickVideo(
-      {@required ImageSource source,
-      CameraDevice preferredCameraDevice = CameraDevice.rear,
-      Duration maxDuration}) async {
-    String path = await platform.pickVideoPath(
-      source: source,
-      preferredCameraDevice: preferredCameraDevice,
-      maxDuration: maxDuration,
-    );
-
-    return path == null ? null : File(path);
-  }
-
   /// Returns a [PickedFile] object wrapping the video that was picked.
   ///
   /// The returned [PickedFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions.
@@ -153,10 +86,10 @@ class ImagePicker {
   ///
   /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost
   /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data.
-  Future<PickedFile> getVideo({
-    @required ImageSource source,
+  Future<PickedFile?> getVideo({
+    required ImageSource source,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
-    Duration maxDuration,
+    Duration? maxDuration,
   }) {
     return platform.pickVideo(
       source: source,
@@ -165,23 +98,6 @@ class ImagePicker {
     );
   }
 
-  /// Retrieve the lost image file when [pickImage] or [pickVideo] failed because the  MainActivity is destroyed. (Android only)
-  ///
-  /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive.
-  /// Call this method to retrieve the lost data and process the data according to your APP's business logic.
-  ///
-  /// Returns a [LostDataResponse] if successfully retrieved the lost data. The [LostDataResponse] can represent either a
-  /// successful image/video selection, or a failure.
-  ///
-  /// Calling this on a non-Android platform will throw [UnimplementedError] exception.
-  ///
-  /// See also:
-  /// * [LostDataResponse], for what's included in the response.
-  /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction.
-  static Future<LostDataResponse> retrieveLostData() {
-    return platform.retrieveLostDataAsDartIoFile();
-  }
-
   /// Retrieve the lost [PickedFile] when [selectImage] or [selectVideo] failed because the  MainActivity is destroyed. (Android only)
   ///
   /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive.

+ 13 - 8
pubspec.yaml

@@ -2,7 +2,7 @@ name: image_picker
 description: Flutter plugin for selecting images from the Android and iOS image
   library, and taking new pictures with the camera.
 homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker
-version: 0.6.7+2
+version: 0.7.4
 
 flutter:
   plugin:
@@ -12,20 +12,25 @@ flutter:
         pluginClass: ImagePickerPlugin
       ios:
         pluginClass: FLTImagePickerPlugin
+      web:
+        default_package: image_picker_for_web
 
 dependencies:
   flutter:
     sdk: flutter
-  flutter_plugin_android_lifecycle: ^1.0.2
-  image_picker_platform_interface: ^1.1.0
+  flutter_plugin_android_lifecycle: ^2.0.1
+  image_picker_platform_interface: ^2.0.0
+  image_picker_for_web: ^2.0.0
 
 dev_dependencies:
-  video_player: ^0.10.3
   flutter_test:
     sdk: flutter
-  e2e: ^0.2.1
-  pedantic: ^1.8.0
+  integration_test:
+    sdk: flutter
+  mockito: ^5.0.0-nullsafety.7
+  pedantic: ^1.10.0
+  plugin_platform_interface: ^2.0.0
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
-  flutter: ">=1.10.0 <2.0.0"
+  sdk: ">=2.12.0-259.9.beta <3.0.0"
+  flutter: ">=1.10.0"