[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();