diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index b5c105c772c2..48067658e667 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.8.5+8 + +* Adds Android 13 photo picker functionality if SDK version is at least 33. +* Bumps compileSdkVersion from 31 to 33 + ## 0.8.5+7 * Updates links for the merge of flutter/plugins into flutter/packages. diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index e61f3161d0f5..d7fdf141bbe1 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle +++ b/packages/image_picker/image_picker_android/android/build.gradle @@ -22,7 +22,7 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { - compileSdkVersion 31 + compileSdkVersion 33 defaultConfig { minSdkVersion 16 @@ -35,6 +35,7 @@ android { implementation 'androidx.core:core:1.8.0' implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.exifinterface:exifinterface:1.3.3' + implementation 'androidx.activity:activity:1.6.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' diff --git a/packages/image_picker/image_picker_android/android/gradle.properties b/packages/image_picker/image_picker_android/android/gradle.properties new file mode 100644 index 000000000000..15eca541fbad --- /dev/null +++ b/packages/image_picker/image_picker_android/android/gradle.properties @@ -0,0 +1,14 @@ +## For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Fri Jan 27 08:52:19 CST 2023 +org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1536M" diff --git a/packages/image_picker/image_picker_android/android/gradle/wrapper/gradle-wrapper.properties b/packages/image_picker/image_picker_android/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..41dfb87909a8 --- /dev/null +++ b/packages/image_picker/image_picker_android/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists 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 cb4beacf9df4..b95fc69ff6b3 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 @@ -15,6 +15,8 @@ import android.net.Uri; import android.os.Build; import android.provider.MediaStore; +import androidx.activity.result.PickVisualMediaRequest; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.app.ActivityCompat; @@ -79,6 +81,7 @@ public class ImagePickerDelegate @VisibleForTesting static final int REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA = 2343; @VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2345; @VisibleForTesting static final int REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY = 2346; + @VisibleForTesting static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352; @VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353; @VisibleForTesting static final int REQUEST_CAMERA_VIDEO_PERMISSION = 2355; @@ -253,8 +256,19 @@ public void chooseVideoFromGallery(MethodCall methodCall, MethodChannel.Result r } private void launchPickVideoFromGalleryIntent() { - Intent pickVideoIntent = new Intent(Intent.ACTION_GET_CONTENT); - pickVideoIntent.setType("video/*"); + Intent pickVideoIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + pickVideoIntent = + new ActivityResultContracts.PickVisualMedia() + .createIntent( + activity, + new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE) + .build()); + } else { + pickVideoIntent = new Intent(Intent.ACTION_GET_CONTENT); + pickVideoIntent.setType("video/*"); + } activity.startActivityForResult(pickVideoIntent, REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY); } @@ -325,20 +339,42 @@ public void chooseMultiImageFromGallery(MethodCall methodCall, MethodChannel.Res } private void launchPickImageFromGalleryIntent() { - Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); - pickImageIntent.setType("image/*"); + Intent pickImageIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + pickImageIntent = + new ActivityResultContracts.PickVisualMedia() + .createIntent( + activity, + new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE) + .build()); + } else { + pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + pickImageIntent.setType("image/*"); + } activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY); } private void launchMultiPickImageFromGalleryIntent() { - Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + Intent pickMultiImageIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + pickMultiImageIntent = + new ActivityResultContracts.PickMultipleVisualMedia() + .createIntent( + activity, + new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE) + .build()); + } else { + pickMultiImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + pickMultiImageIntent.setType("image/*"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + pickMultiImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } } - pickImageIntent.setType("image/*"); - - activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY); + activity.startActivityForResult( + pickMultiImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY); } public void takeImageWithCamera(MethodCall methodCall, MethodChannel.Result result) { 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 6d1e73c49eb9..3a6fd0c48c01 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,12 +35,16 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +@RunWith(RobolectricTestRunner.class) public class ImagePickerDelegateTest { private static final Double WIDTH = 10.0; private static final Double HEIGHT = 10.0; @@ -134,6 +138,8 @@ public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlre verifyNoMoreInteractions(mockResult); } + @Test + @Config(sdk = 30) public void chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) @@ -147,6 +153,83 @@ public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlre any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY)); } + @Test + @Config(minSdk = 33) + public void + chooseImageFromGallery_WithPhotoPicker_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 + @Config(sdk = 30) + public void + chooseMultiImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) + .thenReturn(true); + + ImagePickerDelegate delegate = createDelegate(); + delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), + eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); + } + + @Test + @Config(minSdk = 33) + public void + chooseMultiImageFromGallery_WithPhotoPicker_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) + .thenReturn(true); + + ImagePickerDelegate delegate = createDelegate(); + delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), + eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); + } + + @Test + @Config(sdk = 30) + public void + chooseVideoFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) + .thenReturn(true); + + ImagePickerDelegate delegate = createDelegate(); + delegate.chooseVideoFromGallery(mockMethodCall, mockResult); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY)); + } + + @Test + @Config(minSdk = 33) + public void + chooseVideoFromGallery_WithPhotoPicker_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) + .thenReturn(true); + + ImagePickerDelegate delegate = createDelegate(); + delegate.chooseVideoFromGallery(mockMethodCall, mockResult); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY)); + } + @Test public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); @@ -350,6 +433,7 @@ public void onActivityResult_WhenTakeImageWithCameraCanceled_FinishesWithNull() @Test public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_FinishesWithImagePath() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); @@ -362,6 +446,7 @@ public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_Finishes public void onActivityResult_WhenImageTakenWithCamera_AndResizeNeeded_FinishesWithScaledImagePath() { when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH); + when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( @@ -375,6 +460,7 @@ public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_Finishes public void onActivityResult_WhenVideoTakenWithCamera_AndResizeParametersSupplied_FinishesWithFilePath() { when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH); + when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( @@ -388,6 +474,7 @@ public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_Finishes public void onActivityResult_WhenVideoTakenWithCamera_AndMaxDurationParametersSupplied_FinishesWithFilePath() { when(mockMethodCall.argument("maxDuration")).thenReturn(MAX_DURATION); + when(cache.retrievePendingCameraMediaUriPath()).thenReturn("testString"); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( diff --git a/packages/image_picker/image_picker_android/example/android/app/build.gradle b/packages/image_picker/image_picker_android/example/android/app/build.gradle index f8487c7959f1..f667a7b625c8 100755 --- a/packages/image_picker/image_picker_android/example/android/app/build.gradle +++ b/packages/image_picker/image_picker_android/example/android/app/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 testOptions.unitTests.includeAndroidResources = true lintOptions { diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index b47be2671c9f..cd606752e31a 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,8 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+7 + +version: 0.8.5+8 environment: sdk: ">=2.14.0 <3.0.0"