[image_picker] Android: request camera permission if need (#1519) Android Camera permission need to be requested if it present in manifest with Android M or greater. Even the camera permission is not used. Camera permission may be used and specified in the manifest.xml in another package, for example flutter_barcode_reader. flutter/flutter#29837 flutter/flutter#29662 flutter/flutter#29422 flutter/flutter#29003
diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 9e91014..ff6bffd 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md
@@ -1,3 +1,7 @@ +## 0.5.4+2 + +* Request Camera permission if it present in Manifest on Android >= M. + ## 0.5.4+1 * Bugfix iOS: Cancel button not visible in gallery, if camera was accessed first.
diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 6ddee11..35c303f 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
@@ -69,9 +69,11 @@ @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_EXTERNAL_IMAGE_STORAGE_PERMISSION = 2344; + @VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2345; @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_EXTERNAL_VIDEO_STORAGE_PERMISSION = 2354; + @VisibleForTesting static final int REQUEST_CAMERA_VIDEO_PERMISSION = 2355; @VisibleForTesting final String fileProviderName; @@ -87,6 +89,8 @@ boolean isPermissionGranted(String permissionName); void askForPermission(String permissionName, int requestCode); + + boolean needRequestCameraPermission(); } interface IntentResolver { @@ -126,6 +130,11 @@ public void askForPermission(String permissionName, int requestCode) { ActivityCompat.requestPermissions(activity, new String[] {permissionName}, requestCode); } + + @Override + public boolean needRequestCameraPermission() { + return ImagePickerUtils.needRequestCameraPermission(activity); + } }, new IntentResolver() { @Override @@ -240,6 +249,13 @@ return; } + if (needRequestCameraPermission() + && !permissionManager.isPermissionGranted(Manifest.permission.CAMERA)) { + permissionManager.askForPermission( + Manifest.permission.CAMERA, REQUEST_CAMERA_VIDEO_PERMISSION); + return; + } + launchTakeVideoWithCameraIntent(); } @@ -290,9 +306,23 @@ return; } + if (needRequestCameraPermission() + && !permissionManager.isPermissionGranted(Manifest.permission.CAMERA)) { + permissionManager.askForPermission( + Manifest.permission.CAMERA, REQUEST_CAMERA_IMAGE_PERMISSION); + return; + } + launchTakeImageWithCameraIntent(); } + private boolean needRequestCameraPermission() { + if (permissionManager == null) { + return false; + } + return permissionManager.needRequestCameraPermission(); + } + private void launchTakeImageWithCameraIntent() { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); boolean canTakePhotos = intentResolver.resolveActivity(intent); @@ -363,6 +393,16 @@ launchPickVideoFromGalleryIntent(); } break; + case REQUEST_CAMERA_IMAGE_PERMISSION: + if (permissionGranted) { + launchTakeImageWithCameraIntent(); + } + break; + case REQUEST_CAMERA_VIDEO_PERMISSION: + if (permissionGranted) { + launchTakeVideoWithCameraIntent(); + } + break; default: return false; }
diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java new file mode 100644 index 0000000..da23615 --- /dev/null +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
@@ -0,0 +1,43 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// 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 android.Manifest; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import java.util.Arrays; + +final class ImagePickerUtils { + /** returns true, if permission present in manifest, otherwise false */ + private static boolean isPermissionPresentInManifest(Context context, String permissionName) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = + packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); + + String[] requestedPermissions = packageInfo.requestedPermissions; + return Arrays.asList(requestedPermissions).contains(permissionName); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 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. + * + * <p>Camera permission may be used in another package, as example flutter_barcode_reader. + * https://github.com/flutter/flutter/issues/29837 + * + * @return returns true, if need request camera permission, otherwise false + */ + static boolean needRequestCameraPermission(Context context) { + boolean greatOrEqualM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + return greatOrEqualM && isPermissionPresentInManifest(context, Manifest.permission.CAMERA); + } +}
diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 6a80841..3ca2a37 100644 --- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
@@ -129,6 +129,60 @@ } @Test + public void takeImageWithCamera_WhenHasNoCameraPermission_RequestsForPermission() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(false); + when(mockPermissionManager.needRequestCameraPermission()).thenReturn(true); + + ImagePickerDelegate delegate = createDelegate(); + delegate.takeImageWithCamera(mockMethodCall, mockResult); + + verify(mockPermissionManager) + .askForPermission( + Manifest.permission.CAMERA, ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION); + } + + @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); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA)); + } + + @Test + 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); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA)); + } + + @Test + public void + takeImageWithCamera_WhenHasCameraPermission_AndNoActivityToHandleCameraIntent_FinishesWithNoCamerasAvailableError() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); + when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(false); + + ImagePickerDelegate delegate = createDelegate(); + delegate.takeImageWithCamera(mockMethodCall, mockResult); + + verify(mockResult) + .error("no_available_camera", "No cameras available for taking pictures.", null); + verifyNoMoreInteractions(mockResult); + } + + @Test public void onRequestPermissionsResult_WhenReadExternalStoragePermissionDenied_FinishesWithNull() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); @@ -173,6 +227,51 @@ } @Test + public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithNull() { + ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + + delegate.onRequestPermissionsResult( + ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION, + new String[] {Manifest.permission.CAMERA}, + new int[] {PackageManager.PERMISSION_DENIED}); + + verify(mockResult).success(null); + verifyNoMoreInteractions(mockResult); + } + + @Test + public void + onRequestTakeVideoPermissionsResult_WhenCameraPermissionGranted_LaunchesTakeVideoWithCameraIntent() { + when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); + + ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + delegate.onRequestPermissionsResult( + ImagePickerDelegate.REQUEST_CAMERA_VIDEO_PERMISSION, + new String[] {Manifest.permission.CAMERA}, + new int[] {PackageManager.PERMISSION_GRANTED}); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA)); + } + + @Test + public void + onRequestTakeImagePermissionsResult_WhenCameraPermissionGranted_LaunchesTakeWithCameraIntent() { + when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true); + + ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + delegate.onRequestPermissionsResult( + ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION, + new String[] {Manifest.permission.CAMERA}, + new int[] {PackageManager.PERMISSION_GRANTED}); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA)); + } + + @Test public void onActivityResult_WhenPickFromGalleryCanceled_FinishesWithNull() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();