[image_picker] Reverted removal of camera permission request (#4021)

diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index e83ab72..b913bbf 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.0+3
+
+* Readded request for camera permissions.
+
 ## 0.8.0+2
 
 * Fix a rotation problem where when camera is chosen as a source and additional parameters are added.
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 41df851..c934b54 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
@@ -4,6 +4,7 @@
 
 package io.flutter.plugins.imagepicker;
 
+import android.Manifest;
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -14,6 +15,7 @@
 import android.os.Build;
 import android.provider.MediaStore;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.app.ActivityCompat;
 import androidx.core.content.FileProvider;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
@@ -40,7 +42,19 @@
  * means that the chooseImageFromGallery() or takeImageWithCamera() method was called at least
  * twice. In this case, stop executing and finish with an error.
  *
- * <p>2. Launch the gallery or camera for picking the image, depending on whether
+ * <p>2. Check that a required runtime permission has been granted. The takeImageWithCamera() method
+ * checks that {@link Manifest.permission#CAMERA} has been granted.
+ *
+ * <p>The permission check can end up in two different outcomes:
+ *
+ * <p>A) If the permission has already been granted, continue with picking the image from gallery or
+ * camera.
+ *
+ * <p>B) If the permission hasn't already been granted, ask for the permission from the user. If the
+ * user grants the permission, proceed with step #3. If the user denies the permission, stop doing
+ * anything else and finish with a null result.
+ *
+ * <p>3. Launch the gallery or camera for picking the image, depending on whether
  * chooseImageFromGallery() or takeImageWithCamera() was called.
  *
  * <p>This can end up in three different outcomes:
@@ -55,11 +69,15 @@
  *
  * <p>C) User cancels picking an image. Finish with null result.
  */
-public class ImagePickerDelegate implements PluginRegistry.ActivityResultListener {
+public class ImagePickerDelegate
+    implements PluginRegistry.ActivityResultListener,
+        PluginRegistry.RequestPermissionsResultListener {
   @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_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_CAMERA_VIDEO_PERMISSION = 2355;
 
   @VisibleForTesting final String fileProviderName;
 
@@ -67,11 +85,20 @@
   @VisibleForTesting final File externalFilesDirectory;
   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;
 
+  interface PermissionManager {
+    boolean isPermissionGranted(String permissionName);
+
+    void askForPermission(String permissionName, int requestCode);
+
+    boolean needRequestCameraPermission();
+  }
+
   interface IntentResolver {
     boolean resolveActivity(Intent intent);
   }
@@ -102,6 +129,23 @@
         null,
         null,
         cache,
+        new PermissionManager() {
+          @Override
+          public boolean isPermissionGranted(String permissionName) {
+            return ActivityCompat.checkSelfPermission(activity, permissionName)
+                == PackageManager.PERMISSION_GRANTED;
+          }
+
+          @Override
+          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
           public boolean resolveActivity(Intent intent) {
@@ -143,6 +187,7 @@
       final MethodChannel.Result result,
       final MethodCall methodCall,
       final ImagePickerCache cache,
+      final PermissionManager permissionManager,
       final IntentResolver intentResolver,
       final FileUriResolver fileUriResolver,
       final FileUtils fileUtils) {
@@ -152,6 +197,7 @@
     this.fileProviderName = activity.getPackageName() + ".flutter.image_provider";
     this.pendingResult = result;
     this.methodCall = methodCall;
+    this.permissionManager = permissionManager;
     this.intentResolver = intentResolver;
     this.fileUriResolver = fileUriResolver;
     this.fileUtils = fileUtils;
@@ -223,6 +269,13 @@
       return;
     }
 
+    if (needRequestCameraPermission()
+        && !permissionManager.isPermissionGranted(Manifest.permission.CAMERA)) {
+      permissionManager.askForPermission(
+          Manifest.permission.CAMERA, REQUEST_CAMERA_VIDEO_PERMISSION);
+      return;
+    }
+
     launchTakeVideoWithCameraIntent();
   }
 
@@ -275,9 +328,22 @@
       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);
     if (cameraDevice == CameraDevice.FRONT) {
@@ -336,6 +402,39 @@
   }
 
   @Override
+  public boolean onRequestPermissionsResult(
+      int requestCode, String[] permissions, int[] grantResults) {
+    boolean permissionGranted =
+        grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+
+    switch (requestCode) {
+      case REQUEST_CAMERA_IMAGE_PERMISSION:
+        if (permissionGranted) {
+          launchTakeImageWithCameraIntent();
+        }
+        break;
+      case REQUEST_CAMERA_VIDEO_PERMISSION:
+        if (permissionGranted) {
+          launchTakeVideoWithCameraIntent();
+        }
+        break;
+      default:
+        return false;
+    }
+
+    if (!permissionGranted) {
+      switch (requestCode) {
+        case REQUEST_CAMERA_IMAGE_PERMISSION:
+        case REQUEST_CAMERA_VIDEO_PERMISSION:
+          finishWithError("camera_access_denied", "The user did not allow camera access.");
+          break;
+      }
+    }
+
+    return true;
+  }
+
+  @Override
   public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
     switch (requestCode) {
       case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY:
diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
index b4e7e8a..bffc903 100644
--- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
+++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
@@ -192,9 +192,11 @@
       // V1 embedding setup for activity listeners.
       application.registerActivityLifecycleCallbacks(observer);
       registrar.addActivityResultListener(delegate);
+      registrar.addRequestPermissionsResultListener(delegate);
     } else {
       // V2 embedding setup for activity listeners.
       activityBinding.addActivityResultListener(delegate);
+      activityBinding.addRequestPermissionsResultListener(delegate);
       lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding);
       lifecycle.addObserver(observer);
     }
@@ -202,6 +204,7 @@
 
   private void tearDown() {
     activityBinding.removeActivityResultListener(delegate);
+    activityBinding.removeRequestPermissionsResultListener(delegate);
     activityBinding = null;
     lifecycle.removeObserver(observer);
     lifecycle = null;
diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
index 5b66814..da53b10 100644
--- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
+++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
@@ -14,6 +14,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -39,6 +40,7 @@
   @Mock ImageResizer mockImageResizer;
   @Mock MethodCall mockMethodCall;
   @Mock MethodChannel.Result mockResult;
+  @Mock ImagePickerDelegate.PermissionManager mockPermissionManager;
   @Mock ImagePickerDelegate.IntentResolver mockIntentResolver;
   @Mock FileUtils mockFileUtils;
   @Mock Intent mockIntent;
@@ -102,7 +104,11 @@
   }
 
   @Test
-  public void chooseImageFromGallery_LaunchesChooseFromGalleryIntent() {
+  public void
+      chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() {
+    when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE))
+        .thenReturn(true);
+
     ImagePickerDelegate delegate = createDelegate();
     delegate.chooseImageFromGallery(mockMethodCall, mockResult);
 
@@ -122,30 +128,49 @@
   }
 
   @Test
-  public void
-      takeImageWithCamera_WhenAnActivityCanHandleCameraIntent_LaunchesTakeWithCameraIntent() {
+  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);
 
-    MockedStatic<File> mockStaticFile = Mockito.mockStatic(File.class);
-    mockStaticFile
-        .when(() -> File.createTempFile(any(), any(), any()))
-        .thenReturn(new File("/tmpfile"));
+    ImagePickerDelegate delegate = createDelegate();
+    delegate.takeImageWithCamera(mockMethodCall, mockResult);
 
-    try {
-      ImagePickerDelegate delegate = createDelegate();
-      delegate.takeImageWithCamera(mockMethodCall, mockResult);
-
-      verify(mockActivity)
-          .startActivityForResult(
-              any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
-    } finally {
-      mockStaticFile.close();
-    }
+    verify(mockActivity)
+        .startActivityForResult(
+            any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA));
   }
 
   @Test
   public void
-      takeImageWithCamera_WhenNoActivityToHandleCameraIntent_FinishesWithNoCamerasAvailableError() {
+      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();
@@ -158,6 +183,7 @@
 
   @Test
   public void takeImageWithCamera_WritesImageToCacheDirectory() {
+    when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true);
     when(mockIntentResolver.resolveActivity(any(Intent.class))).thenReturn(true);
 
     MockedStatic<File> mockStaticFile = Mockito.mockStatic(File.class);
@@ -165,16 +191,57 @@
         .when(() -> File.createTempFile(any(), any(), any()))
         .thenReturn(new File("/tmpfile"));
 
-    try {
-      ImagePickerDelegate delegate = createDelegate();
-      delegate.takeImageWithCamera(mockMethodCall, mockResult);
+    ImagePickerDelegate delegate = createDelegate();
+    delegate.takeImageWithCamera(mockMethodCall, mockResult);
 
-      mockStaticFile.verify(
-          () -> File.createTempFile(any(), eq(".jpg"), eq(new File("/image_picker_cache"))),
-          times(1));
-    } finally {
-      mockStaticFile.close();
-    }
+    mockStaticFile.verify(
+        () -> File.createTempFile(any(), eq(".jpg"), eq(new File("/image_picker_cache"))),
+        times(1));
+  }
+
+  @Test
+  public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithError() {
+    ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
+
+    delegate.onRequestPermissionsResult(
+        ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION,
+        new String[] {Manifest.permission.CAMERA},
+        new int[] {PackageManager.PERMISSION_DENIED});
+
+    verify(mockResult).error("camera_access_denied", "The user did not allow camera access.", 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
@@ -295,6 +362,7 @@
         null,
         null,
         cache,
+        mockPermissionManager,
         mockIntentResolver,
         mockFileUriResolver,
         mockFileUtils);
@@ -303,11 +371,12 @@
   private ImagePickerDelegate createDelegateWithPendingResultAndMethodCall() {
     return new ImagePickerDelegate(
         mockActivity,
-        new File("/image_picker_cache"),
+        null,
         mockImageResizer,
         mockResult,
         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 4ca29b4..bf42015 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -3,7 +3,7 @@
   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.0+2
+version: 0.8.0+3
 
 environment:
   sdk: ">=2.12.0 <3.0.0"