diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 6e4a360aa636..64a9599c8830 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.13+17 + +* Updates plugin to use Android Photo Picker on API 36 and above. + ## 0.8.13+16 * Bumps androidx.core:core from 1.17.0 to 1.18.0. diff --git a/packages/image_picker/image_picker_android/README.md b/packages/image_picker/image_picker_android/README.md index a0bcc70294d6..67087fa88d3e 100755 --- a/packages/image_picker/image_picker_android/README.md +++ b/packages/image_picker/image_picker_android/README.md @@ -15,11 +15,19 @@ should add it to your `pubspec.yaml` as usual. ## Photo Picker -On Android 13 and above this packages uses the Android Photo Picker. +On **Android 16** (API level 36) and above, gallery image, video, and mixed-media +picks always use the Android Photo Picker. [`ImagePickerAndroid.useAndroidPhotoPicker`][4] +cannot be set to `false` to use the legacy `ACTION_GET_CONTENT` flow on those +versions; it would often return no path. See [flutter/flutter#182071][5]. -On Android 12 and below this package has optional Android Photo Picker functionality. +On Android 13 through 15, the default is to use the legacy picker; you can opt in to +the Photo Picker with the flag below. -To use this feature, add the following code to your app before calling any `image_picker` APIs: +On Android 12 and below this package has optional Android Photo Picker functionality +(when the flag is `true` and the system supports it). + +To opt in on versions where it is optional, add the following code to your app before +calling any `image_picker` APIs: ```dart @@ -33,8 +41,13 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface. } ``` -In addition, `ImagePickerAndroid.useAndroidPhotoPicker` must be set to `true` to use the `limit` functionality. It is implemented based on [`ActivityResultContract`][3], so it can only be ensured to take effect on Android 13 or above. Otherwise, it depends on whether the corresponding system app supports it. +In addition, `ImagePickerAndroid.useAndroidPhotoPicker` must be set to `true` to use +the `limit` functionality. It is implemented based on [`ActivityResultContract`][3], so +it can only be ensured to take effect on Android 13 or above. Otherwise, it depends on +whether the corresponding system app supports it. [1]: https://pub.dev/packages/image_picker [2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://developer.android.google.cn/reference/kotlin/androidx/activity/result/contract/ActivityResultContracts.PickMultipleVisualMedia +[4]: https://pub.dev/documentation/image_picker_android/latest/image_picker_android/ImagePickerAndroid/useAndroidPhotoPicker.html +[5]: https://github.com/flutter/flutter/issues/182071 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 043546de2b3e..b5ff2e53fbb1 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 @@ -296,7 +296,7 @@ public void chooseMediaFromGallery( private void launchPickMediaFromGalleryIntent(Messages.GeneralOptions generalOptions) { Intent pickMediaIntent; - if (generalOptions.getUsePhotoPicker()) { + if (ImagePickerUtils.effectiveUsePhotoPicker(generalOptions.getUsePhotoPicker())) { if (generalOptions.getAllowMultiple()) { int limit = ImagePickerUtils.getLimitFromOption(generalOptions); @@ -342,7 +342,7 @@ public void chooseVideoFromGallery( private void launchPickVideoFromGalleryIntent(Boolean usePhotoPicker) { Intent pickVideoIntent; - if (usePhotoPicker) { + if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) { pickVideoIntent = new ActivityResultContracts.PickVisualMedia() .createIntent( @@ -441,7 +441,7 @@ public void chooseMultiImageFromGallery( private void launchPickImageFromGalleryIntent(Boolean usePhotoPicker) { Intent pickImageIntent; - if (usePhotoPicker) { + if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) { pickImageIntent = new ActivityResultContracts.PickVisualMedia() .createIntent( @@ -458,7 +458,7 @@ private void launchPickImageFromGalleryIntent(Boolean usePhotoPicker) { private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker, int limit) { Intent pickMultiImageIntent; - if (usePhotoPicker) { + if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) { pickMultiImageIntent = new ActivityResultContracts.PickMultipleVisualMedia(limit) .createIntent( @@ -490,7 +490,7 @@ public void chooseMultiVideoFromGallery( private void launchMultiPickVideoFromGalleryIntent(Boolean usePhotoPicker, int limit) { Intent pickMultiVideoIntent; - if (usePhotoPicker) { + if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) { pickMultiVideoIntent = new ActivityResultContracts.PickMultipleVisualMedia(limit) .createIntent( 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 1499a860d7d2..97b221ca810f 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 @@ -15,6 +15,8 @@ import java.util.Arrays; final class ImagePickerUtils { + private static final int API_LEVEL_36 = 36; + /** returns true, if permission present in manifest, otherwise false */ private static boolean isPermissionPresentInManifest(Context context, String permissionName) { try { @@ -86,4 +88,22 @@ static int getLimitFromOption(Messages.GeneralOptions generalOptions) { return effectiveLimit; } + + /** + * Returns whether gallery/media selection should use {@link + * androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia} (Android Photo + * Picker) instead of {@link android.content.Intent#ACTION_GET_CONTENT}. + * + *
On Android API 36+, {@code ACTION_GET_CONTENT} for images may be handled by the system + * photo picker's {@code PhotopickerGetContentActivity}. That path combined with {@code + * startActivityForResult} can return {@link android.app.Activity#RESULT_OK} without {@link + * android.content.Intent#getData()} or usable {@link android.content.ClipData}, so the plugin + * would complete with no paths. The {@code PickVisualMedia} contract uses the Activity Result API + * and receives URIs reliably. + * + *
See flutter/flutter#182071. + */ + static boolean effectiveUsePhotoPicker(boolean usePhotoPickerFromDart) { + return Build.VERSION.SDK_INT >= API_LEVEL_36 || usePhotoPickerFromDart; + } } diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerUtilsTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerUtilsTest.java new file mode 100644 index 000000000000..396c84410545 --- /dev/null +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerUtilsTest.java @@ -0,0 +1,31 @@ +// Copyright 2013 The Flutter Authors +// 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +public class ImagePickerUtilsTest { + + @Test + @Config(sdk = 35) + public void effectiveUsePhotoPicker_belowApi36_usesDartPreference() { + assertFalse(ImagePickerUtils.effectiveUsePhotoPicker(false)); + assertTrue(ImagePickerUtils.effectiveUsePhotoPicker(true)); + } + + @Test + @Config(sdk = 36) + public void effectiveUsePhotoPicker_onApi36_alwaysUsesPhotoPicker() { + assertTrue(ImagePickerUtils.effectiveUsePhotoPicker(false)); + assertTrue(ImagePickerUtils.effectiveUsePhotoPicker(true)); + } +} diff --git a/packages/image_picker/image_picker_android/lib/image_picker_android.dart b/packages/image_picker/image_picker_android/lib/image_picker_android.dart index fd7080f536b5..61da45027169 100644 --- a/packages/image_picker/image_picker_android/lib/image_picker_android.dart +++ b/packages/image_picker/image_picker_android/lib/image_picker_android.dart @@ -17,9 +17,21 @@ class ImagePickerAndroid extends ImagePickerPlatform { final ImagePickerApi _hostApi; - /// Sets [ImagePickerAndroid] to use Android 13 Photo Picker. + /// Whether gallery picks use the Android Photo Picker instead of the legacy + /// document picker (`ACTION_GET_CONTENT`). /// - /// Currently defaults to false, but the default is subject to change. + /// Defaults to `false` (the default may change in a future release). When `true`, + /// the plugin uses the Photo Picker on Android versions where it is available for + /// the requested operation. + /// + /// **Android 16** (API level 36) and higher: the plugin always uses the Photo Picker + /// for gallery image, video, and mixed-media selection. This field is **not** + /// respected for opting out on those versions, because the legacy flow can return + /// success without a usable file URI. See + /// https://github.com/flutter/flutter/issues/182071 + /// + /// Must be `true` to use the multi-select `limit` parameter on Android 13 and + /// above, where that feature is implemented via the Photo Picker. bool useAndroidPhotoPicker = false; /// Registers this class as the default platform implementation. diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index a2ed7681559b..af89c5a55559 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/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.13+16 +version: 0.8.13+17 environment: sdk: ^3.9.0