From 256d37dd2198b71edeeb4e6fe42ec657045c1516 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 10 Aug 2021 00:05:07 +0200 Subject: [PATCH] [image_picker] fix camera on Android 11 (#3194) --- .../image_picker/image_picker/CHANGELOG.md | 5 ++ .../imagepicker/ImagePickerDelegate.java | 54 +++++++++---------- .../imagepicker/ImagePickerDelegateTest.java | 16 +++--- .../image_picker/image_picker/pubspec.yaml | 2 +- 4 files changed, 37 insertions(+), 40 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index f9c7640183d5..9d89389cb105 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.3+2 + +* Fix using Camera as image source on Android 11+ + ## 0.8.3+1 * Fixed README Example. @@ -26,6 +30,7 @@ * Fix image picker causing a crash when the cache directory is deleted. ## 0.8.1+2 + * Update the example app to support the multi-image feature. ## 0.8.1+1 diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 8b904f5d769d..dbd0f70af936 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -6,6 +6,7 @@ import android.Manifest; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -88,7 +89,6 @@ public class ImagePickerDelegate private final ImageResizer imageResizer; private final ImagePickerCache cache; private final PermissionManager permissionManager; - private final IntentResolver intentResolver; private final FileUriResolver fileUriResolver; private final FileUtils fileUtils; private CameraDevice cameraDevice; @@ -101,10 +101,6 @@ interface PermissionManager { boolean needRequestCameraPermission(); } - interface IntentResolver { - boolean resolveActivity(Intent intent); - } - interface FileUriResolver { Uri resolveFileProviderUriForFile(String fileProviderName, File imageFile); @@ -148,12 +144,6 @@ public boolean needRequestCameraPermission() { return ImagePickerUtils.needRequestCameraPermission(activity); } }, - new IntentResolver() { - @Override - public boolean resolveActivity(Intent intent) { - return intent.resolveActivity(activity.getPackageManager()) != null; - } - }, new FileUriResolver() { @Override public Uri resolveFileProviderUriForFile(String fileProviderName, File file) { @@ -190,7 +180,6 @@ public void onScanCompleted(String path, Uri uri) { final MethodCall methodCall, final ImagePickerCache cache, final PermissionManager permissionManager, - final IntentResolver intentResolver, final FileUriResolver fileUriResolver, final FileUtils fileUtils) { this.activity = activity; @@ -200,7 +189,6 @@ public void onScanCompleted(String path, Uri uri) { this.pendingResult = result; this.methodCall = methodCall; this.permissionManager = permissionManager; - this.intentResolver = intentResolver; this.fileUriResolver = fileUriResolver; this.fileUtils = fileUtils; this.cache = cache; @@ -291,13 +279,6 @@ private void launchTakeVideoWithCameraIntent() { useFrontCamera(intent); } - boolean canTakePhotos = intentResolver.resolveActivity(intent); - - if (!canTakePhotos) { - finishWithError("no_available_camera", "No cameras available for taking pictures."); - return; - } - File videoFile = createTemporaryWritableVideoFile(); pendingCameraMediaUri = Uri.parse("file:" + videoFile.getAbsolutePath()); @@ -305,7 +286,18 @@ private void launchTakeVideoWithCameraIntent() { intent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri); grantUriPermissions(intent, videoUri); - activity.startActivityForResult(intent, REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA); + try { + activity.startActivityForResult(intent, REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA); + } catch (ActivityNotFoundException e) { + try { + // If we can't delete the file again here, there's not really anything we can do about it. + //noinspection ResultOfMethodCallIgnored + videoFile.delete(); + } catch (SecurityException exception) { + exception.printStackTrace(); + } + finishWithError("no_available_camera", "No cameras available for taking pictures."); + } } public void chooseImageFromGallery(MethodCall methodCall, MethodChannel.Result result) { @@ -371,13 +363,6 @@ private void launchTakeImageWithCameraIntent() { useFrontCamera(intent); } - boolean canTakePhotos = intentResolver.resolveActivity(intent); - - if (!canTakePhotos) { - finishWithError("no_available_camera", "No cameras available for taking pictures."); - return; - } - File imageFile = createTemporaryWritableImageFile(); pendingCameraMediaUri = Uri.parse("file:" + imageFile.getAbsolutePath()); @@ -385,7 +370,18 @@ private void launchTakeImageWithCameraIntent() { intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); grantUriPermissions(intent, imageUri); - activity.startActivityForResult(intent, REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA); + try { + activity.startActivityForResult(intent, REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA); + } catch (ActivityNotFoundException e) { + try { + // If we can't delete the file again here, there's not really anything we can do about it. + //noinspection ResultOfMethodCallIgnored + imageFile.delete(); + } catch (SecurityException exception) { + exception.printStackTrace(); + } + finishWithError("no_available_camera", "No cameras available for taking pictures."); + } } private File createTemporaryWritableImageFile() { diff --git a/packages/image_picker/image_picker/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 1b55a7569eac..ebd58d05fee4 100644 --- a/packages/image_picker/image_picker/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -7,7 +7,9 @@ import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -16,6 +18,7 @@ import android.Manifest; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -42,7 +45,6 @@ public class ImagePickerDelegateTest { @Mock MethodCall mockMethodCall; @Mock MethodChannel.Result mockResult; @Mock ImagePickerDelegate.PermissionManager mockPermissionManager; - @Mock ImagePickerDelegate.IntentResolver mockIntentResolver; @Mock FileUtils mockFileUtils; @Mock Intent mockIntent; @Mock ImagePickerCache cache; @@ -164,7 +166,6 @@ public void takeImageWithCamera_WhenHasNoCameraPermission_RequestsForPermission( @Test public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermission() { when(mockPermissionManager.needRequestCameraPermission()).thenReturn(false); - when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); @@ -178,7 +179,6 @@ public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermis public void takeImageWithCamera_WhenHasCameraPermission_AndAnActivityCanHandleCameraIntent_LaunchesTakeWithCameraIntent() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); - when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); @@ -192,8 +192,9 @@ public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermis public void takeImageWithCamera_WhenHasCameraPermission_AndNoActivityToHandleCameraIntent_FinishesWithNoCamerasAvailableError() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); - when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(false); - + doThrow(ActivityNotFoundException.class) + .when(mockActivity) + .startActivityForResult(any(Intent.class), anyInt()); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); @@ -205,7 +206,6 @@ public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermis @Test public void takeImageWithCamera_WritesImageToCacheDirectory() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); - when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); @@ -231,7 +231,6 @@ public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithEr @Test public void onRequestTakeVideoPermissionsResult_WhenCameraPermissionGranted_LaunchesTakeVideoWithCameraIntent() { - when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onRequestPermissionsResult( @@ -247,7 +246,6 @@ public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithEr @Test public void onRequestTakeImagePermissionsResult_WhenCameraPermissionGranted_LaunchesTakeWithCameraIntent() { - when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onRequestPermissionsResult( @@ -379,7 +377,6 @@ private ImagePickerDelegate createDelegate() { null, cache, mockPermissionManager, - mockIntentResolver, mockFileUriResolver, mockFileUtils); } @@ -393,7 +390,6 @@ private ImagePickerDelegate createDelegateWithPendingResultAndMethodCall() { mockMethodCall, cache, mockPermissionManager, - mockIntentResolver, mockFileUriResolver, mockFileUtils); } diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index e67e79fbba14..e167d8ab891c 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.3+1 +version: 0.8.3+2 environment: sdk: ">=2.12.0 <3.0.0"