[image_picker] add forceFullMetadata to interface (#4288)

diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
index b170ee3..e1ade78 100644
--- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
+++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
@@ -53,6 +53,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) {
     String? capture = computeCaptureAttribute(source, preferredCameraDevice);
@@ -115,6 +116,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) async {
     String? capture = computeCaptureAttribute(source, preferredCameraDevice);
diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
index 97480e0..3c14e40 100644
--- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
+++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 2.4.0
+
+* Add `forceFullMetadata` option to `pickImage`.
+  * To keep this non-breaking `forceFullMetadata` defaults to `true`, so the plugin tries
+   to get the full image metadata which may require extra permission requests on certain platforms.
+  * If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces
+   permission requests from the platform (e.g on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission).
+
 ## 2.3.0
 
 * Updated `LostDataResponse` to include a `files` property, in case more than one file was recovered.
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
index b02284e..082b403 100644
--- a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
@@ -24,6 +24,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) async {
     String? path = await _getImagePath(
@@ -31,6 +32,7 @@
       maxWidth: maxWidth,
       maxHeight: maxHeight,
       imageQuality: imageQuality,
+      forceFullMetadata: forceFullMetadata,
       preferredCameraDevice: preferredCameraDevice,
     );
     return path != null ? PickedFile(path) : null;
@@ -85,6 +87,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) {
     if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
@@ -107,6 +110,7 @@
         'maxWidth': maxWidth,
         'maxHeight': maxHeight,
         'imageQuality': imageQuality,
+        'forceFullMetadata': forceFullMetadata,
         'cameraDevice': preferredCameraDevice.index
       },
     );
@@ -183,6 +187,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) async {
     String? path = await _getImagePath(
@@ -190,6 +195,7 @@
       maxWidth: maxWidth,
       maxHeight: maxHeight,
       imageQuality: imageQuality,
+      forceFullMetadata: forceFullMetadata,
       preferredCameraDevice: preferredCameraDevice,
     );
     return path != null ? XFile(path) : null;
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
index 5c1c8b6..60fa784 100644
--- a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
@@ -58,6 +58,11 @@
   /// image types such as JPEG. If compression is not supported for the image that is picked,
   /// a warning message will be logged.
   ///
+  /// `forceFullMetadata` defaults to `true`, so the plugin tries to get the full image metadata which may require
+  /// extra permission requests on certain platforms.
+  /// If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces permission requests
+  /// from the platform (e.g. on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission).
+  ///
   /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
   /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
   /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if
@@ -73,6 +78,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) {
     throw UnimplementedError('pickImage() has not been implemented.');
@@ -164,6 +170,11 @@
   /// image types such as JPEG. If compression is not supported for the image that is picked,
   /// a warning message will be logged.
   ///
+  /// `forceFullMetadata` defaults to `true`, so the plugin tries to get the full image metadata which may require
+  /// extra permission requests on certain platforms.
+  /// If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces permission requests
+  /// from the platform (e.g. on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission).
+  ///
   /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
   /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
   /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if
@@ -179,6 +190,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    bool forceFullMetadata = true,
     CameraDevice preferredCameraDevice = CameraDevice.rear,
   }) {
     throw UnimplementedError('getImage() has not been implemented.');
diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml
index 2168ff0..53852de 100644
--- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml
+++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml
@@ -4,7 +4,7 @@
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.3.0
+version: 2.4.0
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart
index 17caa84..a2d9568 100644
--- a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart
+++ b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart
@@ -40,14 +40,16 @@
               'maxWidth': null,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 1,
               'maxWidth': null,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -93,49 +95,56 @@
               'maxWidth': null,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': null,
               'maxHeight': 10.0,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': 20.0,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': null,
               'imageQuality': 70,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': null,
               'maxHeight': 10.0,
               'imageQuality': 70,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': 20.0,
               'imageQuality': 70,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -196,6 +205,7 @@
               'maxHeight': null,
               'imageQuality': null,
               'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -215,6 +225,7 @@
               'maxHeight': null,
               'imageQuality': null,
               'cameraDevice': 1,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -509,14 +520,16 @@
               'maxWidth': null,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 1,
               'maxWidth': null,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -562,49 +575,56 @@
               'maxWidth': null,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': null,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': null,
               'maxHeight': 10.0,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': 20.0,
               'imageQuality': null,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': null,
               'imageQuality': 70,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': null,
               'maxHeight': 10.0,
               'imageQuality': 70,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
             isMethodCall('pickImage', arguments: <String, dynamic>{
               'source': 0,
               'maxWidth': 10.0,
               'maxHeight': 20.0,
               'imageQuality': 70,
-              'cameraDevice': 0
+              'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -664,6 +684,7 @@
               'maxHeight': null,
               'imageQuality': null,
               'cameraDevice': 0,
+              'forceFullMetadata': true,
             }),
           ],
         );
@@ -683,6 +704,7 @@
               'maxHeight': null,
               'imageQuality': null,
               'cameraDevice': 1,
+              'forceFullMetadata': true,
             }),
           ],
         );