diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 58c4951a2d4f..ef31fcb25ada 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 0.8.6 +* Adds Android 13 photo picker functionality if SDK version is at least 33. +* Bumps compileSdkVersion from 31 to 33 * Updates minimum Flutter version to 3.0. ## 0.8.5+5 diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index aed1ad5174ea..2fc60dab8fee 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 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..bfb9611c455d 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 @@ -75,10 +75,20 @@ enum CameraDevice { public class ImagePickerDelegate implements PluginRegistry.ActivityResultListener, PluginRegistry.RequestPermissionsResultListener { + @VisibleForTesting + static final int REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY_USING_PHOTO_PICKER = 2341; + @VisibleForTesting static final int REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY = 2342; @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_MULTI_IMAGE_FROM_GALLERY_USING_PHOTO_PICKER = 2347; + + @VisibleForTesting + static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY_USING_PHOTO_PICKER = 2351; + @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,10 +263,18 @@ public void chooseVideoFromGallery(MethodCall methodCall, MethodChannel.Result r } private void launchPickVideoFromGalleryIntent() { - Intent pickVideoIntent = new Intent(Intent.ACTION_GET_CONTENT); + boolean isPhotoPickerAvailable = ImagePickerUtils.isPhotoPickerAvailable(); + + Intent pickVideoIntent = + new Intent( + isPhotoPickerAvailable ? MediaStore.ACTION_PICK_IMAGES : Intent.ACTION_GET_CONTENT); pickVideoIntent.setType("video/*"); + int requestCode = + isPhotoPickerAvailable + ? REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY_USING_PHOTO_PICKER + : REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY; - activity.startActivityForResult(pickVideoIntent, REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY); + activity.startActivityForResult(pickVideoIntent, requestCode); } public void takeVideoWithCamera(MethodCall methodCall, MethodChannel.Result result) { @@ -325,20 +343,38 @@ public void chooseMultiImageFromGallery(MethodCall methodCall, MethodChannel.Res } private void launchPickImageFromGalleryIntent() { - Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + boolean isPhotoPickerAvailable = ImagePickerUtils.isPhotoPickerAvailable(); + Intent pickImageIntent = + new Intent( + isPhotoPickerAvailable ? MediaStore.ACTION_PICK_IMAGES : Intent.ACTION_GET_CONTENT); + int requestCode = + isPhotoPickerAvailable + ? REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY_USING_PHOTO_PICKER + : REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY; pickImageIntent.setType("image/*"); - - activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY); + activity.startActivityForResult(pickImageIntent, requestCode); } private void launchMultiPickImageFromGalleryIntent() { - Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + boolean isPhotoPickerAvailable = ImagePickerUtils.isPhotoPickerAvailable(); + Intent pickImageIntent = + new Intent( + isPhotoPickerAvailable ? MediaStore.ACTION_PICK_IMAGES : Intent.ACTION_GET_CONTENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + if (isPhotoPickerAvailable) { + pickImageIntent.putExtra( + MediaStore.EXTRA_PICK_IMAGES_MAX, ImagePickerUtils.getPickImagesMaxLimit()); + } else { + pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } } pickImageIntent.setType("image/*"); - - activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY); + int requestCode = + isPhotoPickerAvailable + ? REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY_USING_PHOTO_PICKER + : REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY; + activity.startActivityForResult(pickImageIntent, requestCode); } public void takeImageWithCamera(MethodCall methodCall, MethodChannel.Result result) { diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java index ba9878925575..b9143c46d2fb 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java @@ -4,11 +4,14 @@ package io.flutter.plugins.imagepicker; +import static android.os.ext.SdkExtensions.getExtensionVersion; + import android.Manifest; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; +import android.provider.MediaStore; import java.util.Arrays; final class ImagePickerUtils { @@ -27,6 +30,25 @@ private static boolean isPermissionPresentInManifest(Context context, String per } } + static boolean isPhotoPickerAvailable() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return getExtensionVersion(Build.VERSION_CODES.R) >= 2; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return true; + } else { + return false; + } + } + + static int getPickImagesMaxLimit() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return MediaStore.getPickImagesMaxLimit(); + } else { + return 0; + } + } + /** * Camera permission need request if it present in manifest, because for M or great for take Photo * ar Video by intent need it permission, even if the camera permission is not used. 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..01272031fd90 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,87 @@ 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_USING_PHOTO_PICKER)); + } + + @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_USING_PHOTO_PICKER)); + } + + @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_USING_PHOTO_PICKER)); + } + @Test public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); @@ -350,6 +437,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 +450,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 +464,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 +478,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 31d8c82a0a9d..81e210bc3570 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 7aa1a2258645..ddb888b35309 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/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+5 +version: 0.8.6 environment: sdk: ">=2.14.0 <3.0.0"