Skip to content

Commit

Permalink
[image_picker_android] Adds Android 13 photo picker functionality (#3267
Browse files Browse the repository at this point in the history
)

[image_picker_android] Adds Android 13 photo picker functionality
  • Loading branch information
FXschwartz authored Mar 1, 2023
1 parent 588c5f4 commit 7ec6a77
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 13 deletions.
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

0 comments on commit 7ec6a77

Please sign in to comment.