Skip to content

[image_picker_android] Adds Android 13 photo picker functionality #3267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/image_picker/image_picker_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ rootProject.allprojects {
apply plugin: 'com.android.library'

android {
compileSdkVersion 31
compileSdkVersion 33

defaultConfig {
minSdkVersion 16
Expand All @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion packages/image_picker/image_picker_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down