[image_picker] Switch Android to internal method channel (#5958)

diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md
index a5cc613..664be36 100644
--- a/packages/image_picker/image_picker_android/CHANGELOG.md
+++ b/packages/image_picker/image_picker_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.5+1
+
+* Switches to an internal method channel implementation.
+
 ## 0.8.5
 
 * Updates gradle to 7.1.2.
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
index 311ef19..8336a14 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
@@ -183,7 +183,7 @@
   private static final String METHOD_CALL_RETRIEVE = "retrieve";
   private static final int CAMERA_DEVICE_FRONT = 1;
   private static final int CAMERA_DEVICE_REAR = 0;
-  private static final String CHANNEL = "plugins.flutter.io/image_picker";
+  private static final String CHANNEL = "plugins.flutter.io/image_picker_android";
 
   private static final int SOURCE_CAMERA = 0;
   private static final int SOURCE_GALLERY = 1;
diff --git a/packages/image_picker/image_picker_android/lib/image_picker_android.dart b/packages/image_picker/image_picker_android/lib/image_picker_android.dart
new file mode 100644
index 0000000..b6073c7
--- /dev/null
+++ b/packages/image_picker/image_picker_android/lib/image_picker_android.dart
@@ -0,0 +1,282 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+
+const MethodChannel _channel =
+    MethodChannel('plugins.flutter.io/image_picker_android');
+
+/// An Android implementation of [ImagePickerPlatform].
+class ImagePickerAndroid extends ImagePickerPlatform {
+  /// The MethodChannel that is being used by this implementation of the plugin.
+  @visibleForTesting
+  MethodChannel get channel => _channel;
+
+  /// Registers this class as the default platform implementation.
+  static void registerWith() {
+    ImagePickerPlatform.instance = ImagePickerAndroid();
+  }
+
+  @override
+  Future<PickedFile?> pickImage({
+    required ImageSource source,
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+  }) async {
+    final String? path = await _getImagePath(
+      source: source,
+      maxWidth: maxWidth,
+      maxHeight: maxHeight,
+      imageQuality: imageQuality,
+      preferredCameraDevice: preferredCameraDevice,
+    );
+    return path != null ? PickedFile(path) : null;
+  }
+
+  @override
+  Future<List<PickedFile>?> pickMultiImage({
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
+  }) async {
+    final List<dynamic>? paths = await _getMultiImagePath(
+      maxWidth: maxWidth,
+      maxHeight: maxHeight,
+      imageQuality: imageQuality,
+    );
+    if (paths == null) {
+      return null;
+    }
+
+    return paths.map((dynamic path) => PickedFile(path as String)).toList();
+  }
+
+  Future<List<dynamic>?> _getMultiImagePath({
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
+  }) {
+    if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
+      throw ArgumentError.value(
+          imageQuality, 'imageQuality', 'must be between 0 and 100');
+    }
+
+    if (maxWidth != null && maxWidth < 0) {
+      throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
+    }
+
+    if (maxHeight != null && maxHeight < 0) {
+      throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
+    }
+
+    return _channel.invokeMethod<List<dynamic>?>(
+      'pickMultiImage',
+      <String, dynamic>{
+        'maxWidth': maxWidth,
+        'maxHeight': maxHeight,
+        'imageQuality': imageQuality,
+      },
+    );
+  }
+
+  Future<String?> _getImagePath({
+    required ImageSource source,
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+    bool requestFullMetadata = true,
+  }) {
+    if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
+      throw ArgumentError.value(
+          imageQuality, 'imageQuality', 'must be between 0 and 100');
+    }
+
+    if (maxWidth != null && maxWidth < 0) {
+      throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
+    }
+
+    if (maxHeight != null && maxHeight < 0) {
+      throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
+    }
+
+    return _channel.invokeMethod<String>(
+      'pickImage',
+      <String, dynamic>{
+        'source': source.index,
+        'maxWidth': maxWidth,
+        'maxHeight': maxHeight,
+        'imageQuality': imageQuality,
+        'cameraDevice': preferredCameraDevice.index,
+        'requestFullMetadata': requestFullMetadata,
+      },
+    );
+  }
+
+  @override
+  Future<PickedFile?> pickVideo({
+    required ImageSource source,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+    Duration? maxDuration,
+  }) async {
+    final String? path = await _getVideoPath(
+      source: source,
+      maxDuration: maxDuration,
+      preferredCameraDevice: preferredCameraDevice,
+    );
+    return path != null ? PickedFile(path) : null;
+  }
+
+  Future<String?> _getVideoPath({
+    required ImageSource source,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+    Duration? maxDuration,
+  }) {
+    return _channel.invokeMethod<String>(
+      'pickVideo',
+      <String, dynamic>{
+        'source': source.index,
+        'maxDuration': maxDuration?.inSeconds,
+        'cameraDevice': preferredCameraDevice.index
+      },
+    );
+  }
+
+  @override
+  Future<XFile?> getImage({
+    required ImageSource source,
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+  }) async {
+    final String? path = await _getImagePath(
+      source: source,
+      maxWidth: maxWidth,
+      maxHeight: maxHeight,
+      imageQuality: imageQuality,
+      preferredCameraDevice: preferredCameraDevice,
+    );
+    return path != null ? XFile(path) : null;
+  }
+
+  @override
+  Future<XFile?> getImageFromSource({
+    required ImageSource source,
+    ImagePickerOptions options = const ImagePickerOptions(),
+  }) async {
+    final String? path = await _getImagePath(
+      source: source,
+      maxHeight: options.maxHeight,
+      maxWidth: options.maxWidth,
+      imageQuality: options.imageQuality,
+      preferredCameraDevice: options.preferredCameraDevice,
+      requestFullMetadata: options.requestFullMetadata,
+    );
+    return path != null ? XFile(path) : null;
+  }
+
+  @override
+  Future<List<XFile>?> getMultiImage({
+    double? maxWidth,
+    double? maxHeight,
+    int? imageQuality,
+  }) async {
+    final List<dynamic>? paths = await _getMultiImagePath(
+      maxWidth: maxWidth,
+      maxHeight: maxHeight,
+      imageQuality: imageQuality,
+    );
+    if (paths == null) {
+      return null;
+    }
+
+    return paths.map((dynamic path) => XFile(path as String)).toList();
+  }
+
+  @override
+  Future<XFile?> getVideo({
+    required ImageSource source,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+    Duration? maxDuration,
+  }) async {
+    final String? path = await _getVideoPath(
+      source: source,
+      maxDuration: maxDuration,
+      preferredCameraDevice: preferredCameraDevice,
+    );
+    return path != null ? XFile(path) : null;
+  }
+
+  @override
+  Future<LostData> retrieveLostData() async {
+    final LostDataResponse result = await getLostData();
+
+    if (result.isEmpty) {
+      return LostData.empty();
+    }
+
+    return LostData(
+      file: result.file != null ? PickedFile(result.file!.path) : null,
+      exception: result.exception,
+      type: result.type,
+    );
+  }
+
+  @override
+  Future<LostDataResponse> getLostData() async {
+    List<XFile>? pickedFileList;
+
+    final Map<String, dynamic>? result =
+        await _channel.invokeMapMethod<String, dynamic>('retrieve');
+
+    if (result == null) {
+      return LostDataResponse.empty();
+    }
+
+    assert(result.containsKey('path') != result.containsKey('errorCode'));
+
+    final String? type = result['type'] as String?;
+    assert(type == kTypeImage || type == kTypeVideo);
+
+    RetrieveType? retrieveType;
+    if (type == kTypeImage) {
+      retrieveType = RetrieveType.image;
+    } else if (type == kTypeVideo) {
+      retrieveType = RetrieveType.video;
+    }
+
+    PlatformException? exception;
+    if (result.containsKey('errorCode')) {
+      exception = PlatformException(
+          code: result['errorCode']! as String,
+          message: result['errorMessage'] as String?);
+    }
+
+    final String? path = result['path'] as String?;
+
+    final List<String>? pathList =
+        (result['pathList'] as List<dynamic>?)?.cast<String>();
+    if (pathList != null) {
+      pickedFileList = <XFile>[];
+      for (final String path in pathList) {
+        pickedFileList.add(XFile(path));
+      }
+    }
+
+    return LostDataResponse(
+      file: path != null ? XFile(path) : null,
+      exception: exception,
+      type: retrieveType,
+      files: pickedFileList,
+    );
+  }
+}
diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml
index 3d17e8a..8cbaaac 100755
--- a/packages/image_picker/image_picker_android/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the image_picker plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.8.5
+version: 0.8.5+1
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -15,6 +15,7 @@
       android:
         package: io.flutter.plugins.imagepicker
         pluginClass: ImagePickerPlugin
+        dartPluginClass: ImagePickerAndroid
 
 dependencies:
   flutter:
diff --git a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
new file mode 100644
index 0000000..ee1eb79
--- /dev/null
+++ b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
@@ -0,0 +1,1256 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:image_picker_android/image_picker_android.dart';
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  final ImagePickerAndroid picker = ImagePickerAndroid();
+
+  final List<MethodCall> log = <MethodCall>[];
+  dynamic returnValue = '';
+
+  setUp(() {
+    returnValue = '';
+    picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+      log.add(methodCall);
+      return returnValue;
+    });
+
+    log.clear();
+  });
+
+  test('registers instance', () async {
+    ImagePickerAndroid.registerWith();
+    expect(ImagePickerPlatform.instance, isA<ImagePickerAndroid>());
+  });
+
+  group('#pickImage', () {
+    test('passes the image source argument correctly', () async {
+      await picker.pickImage(source: ImageSource.camera);
+      await picker.pickImage(source: ImageSource.gallery);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 1,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      await picker.pickImage(source: ImageSource.camera);
+      await picker.pickImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+      );
+      await picker.pickImage(
+        source: ImageSource.camera,
+        maxHeight: 10.0,
+      );
+      await picker.pickImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+      );
+      await picker.pickImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+        imageQuality: 70,
+      );
+      await picker.pickImage(
+        source: ImageSource.camera,
+        maxHeight: 10.0,
+        imageQuality: 70,
+      );
+      await picker.pickImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+        imageQuality: 70,
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('does not accept an invalid imageQuality argument', () {
+      expect(
+        () => picker.pickImage(imageQuality: -1, source: ImageSource.gallery),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.pickImage(imageQuality: 101, source: ImageSource.gallery),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.pickImage(imageQuality: -1, source: ImageSource.camera),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.pickImage(imageQuality: 101, source: ImageSource.camera),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      expect(
+        () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(await picker.pickImage(source: ImageSource.gallery), isNull);
+      expect(await picker.pickImage(source: ImageSource.camera), isNull);
+    });
+
+    test('camera position defaults to back', () async {
+      await picker.pickImage(source: ImageSource.camera);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('camera position can set to front', () async {
+      await picker.pickImage(
+          source: ImageSource.camera,
+          preferredCameraDevice: CameraDevice.front);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 1,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+  });
+
+  group('#pickMultiImage', () {
+    test('calls the method correctly', () async {
+      returnValue = <dynamic>['0', '1'];
+      await picker.pickMultiImage();
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      returnValue = <dynamic>['0', '1'];
+      await picker.pickMultiImage();
+      await picker.pickMultiImage(
+        maxWidth: 10.0,
+      );
+      await picker.pickMultiImage(
+        maxHeight: 10.0,
+      );
+      await picker.pickMultiImage(
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+      );
+      await picker.pickMultiImage(
+        maxWidth: 10.0,
+        imageQuality: 70,
+      );
+      await picker.pickMultiImage(
+        maxHeight: 10.0,
+        imageQuality: 70,
+      );
+      await picker.pickMultiImage(
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+        imageQuality: 70,
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+          }),
+        ],
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      returnValue = <dynamic>['0', '1'];
+      expect(
+        () => picker.pickMultiImage(maxWidth: -1.0),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.pickMultiImage(maxHeight: -1.0),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept an invalid imageQuality argument', () {
+      returnValue = <dynamic>['0', '1'];
+      expect(
+        () => picker.pickMultiImage(imageQuality: -1),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.pickMultiImage(imageQuality: 101),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(await picker.pickMultiImage(), isNull);
+      expect(await picker.pickMultiImage(), isNull);
+    });
+  });
+
+  group('#pickVideo', () {
+    test('passes the image source argument correctly', () async {
+      await picker.pickVideo(source: ImageSource.camera);
+      await picker.pickVideo(source: ImageSource.gallery);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'cameraDevice': 0,
+            'maxDuration': null,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 1,
+            'cameraDevice': 0,
+            'maxDuration': null,
+          }),
+        ],
+      );
+    });
+
+    test('passes the duration argument correctly', () async {
+      await picker.pickVideo(source: ImageSource.camera);
+      await picker.pickVideo(
+        source: ImageSource.camera,
+        maxDuration: const Duration(seconds: 10),
+      );
+      await picker.pickVideo(
+        source: ImageSource.camera,
+        maxDuration: const Duration(minutes: 1),
+      );
+      await picker.pickVideo(
+        source: ImageSource.camera,
+        maxDuration: const Duration(hours: 1),
+      );
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': null,
+            'cameraDevice': 0,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': 10,
+            'cameraDevice': 0,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': 60,
+            'cameraDevice': 0,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': 3600,
+            'cameraDevice': 0,
+          }),
+        ],
+      );
+    });
+
+    test('handles a null video path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(await picker.pickVideo(source: ImageSource.gallery), isNull);
+      expect(await picker.pickVideo(source: ImageSource.camera), isNull);
+    });
+
+    test('camera position defaults to back', () async {
+      await picker.pickVideo(source: ImageSource.camera);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'cameraDevice': 0,
+            'maxDuration': null,
+          }),
+        ],
+      );
+    });
+
+    test('camera position can set to front', () async {
+      await picker.pickVideo(
+        source: ImageSource.camera,
+        preferredCameraDevice: CameraDevice.front,
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': null,
+            'cameraDevice': 1,
+          }),
+        ],
+      );
+    });
+  });
+
+  group('#retrieveLostData', () {
+    test('retrieveLostData get success response', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, String>{
+          'type': 'image',
+          'path': '/example/path',
+        };
+      });
+      final LostData response = await picker.retrieveLostData();
+      expect(response.type, RetrieveType.image);
+      expect(response.file, isNotNull);
+      expect(response.file!.path, '/example/path');
+    });
+
+    test('retrieveLostData get error response', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, String>{
+          'type': 'video',
+          'errorCode': 'test_error_code',
+          'errorMessage': 'test_error_message',
+        };
+      });
+      final LostData response = await picker.retrieveLostData();
+      expect(response.type, RetrieveType.video);
+      expect(response.exception, isNotNull);
+      expect(response.exception!.code, 'test_error_code');
+      expect(response.exception!.message, 'test_error_message');
+    });
+
+    test('retrieveLostData get null response', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return null;
+      });
+      expect((await picker.retrieveLostData()).isEmpty, true);
+    });
+
+    test('retrieveLostData get both path and error should throw', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, String>{
+          'type': 'video',
+          'errorCode': 'test_error_code',
+          'errorMessage': 'test_error_message',
+          'path': '/example/path',
+        };
+      });
+      expect(picker.retrieveLostData(), throwsAssertionError);
+    });
+  });
+
+  group('#getImage', () {
+    test('passes the image source argument correctly', () async {
+      await picker.getImage(source: ImageSource.camera);
+      await picker.getImage(source: ImageSource.gallery);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 1,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      await picker.getImage(source: ImageSource.camera);
+      await picker.getImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+      );
+      await picker.getImage(
+        source: ImageSource.camera,
+        maxHeight: 10.0,
+      );
+      await picker.getImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+      );
+      await picker.getImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+        imageQuality: 70,
+      );
+      await picker.getImage(
+        source: ImageSource.camera,
+        maxHeight: 10.0,
+        imageQuality: 70,
+      );
+      await picker.getImage(
+        source: ImageSource.camera,
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+        imageQuality: 70,
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('does not accept an invalid imageQuality argument', () {
+      expect(
+        () => picker.getImage(imageQuality: -1, source: ImageSource.gallery),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImage(imageQuality: 101, source: ImageSource.gallery),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImage(imageQuality: -1, source: ImageSource.camera),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImage(imageQuality: 101, source: ImageSource.camera),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      expect(
+        () => picker.getImage(source: ImageSource.camera, maxWidth: -1.0),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImage(source: ImageSource.camera, maxHeight: -1.0),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(await picker.getImage(source: ImageSource.gallery), isNull);
+      expect(await picker.getImage(source: ImageSource.camera), isNull);
+    });
+
+    test('camera position defaults to back', () async {
+      await picker.getImage(source: ImageSource.camera);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('camera position can set to front', () async {
+      await picker.getImage(
+          source: ImageSource.camera,
+          preferredCameraDevice: CameraDevice.front);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 1,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+  });
+
+  group('#getMultiImage', () {
+    test('calls the method correctly', () async {
+      returnValue = <dynamic>['0', '1'];
+      await picker.getMultiImage();
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      returnValue = <dynamic>['0', '1'];
+      await picker.getMultiImage();
+      await picker.getMultiImage(
+        maxWidth: 10.0,
+      );
+      await picker.getMultiImage(
+        maxHeight: 10.0,
+      );
+      await picker.getMultiImage(
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+      );
+      await picker.getMultiImage(
+        maxWidth: 10.0,
+        imageQuality: 70,
+      );
+      await picker.getMultiImage(
+        maxHeight: 10.0,
+        imageQuality: 70,
+      );
+      await picker.getMultiImage(
+        maxWidth: 10.0,
+        maxHeight: 20.0,
+        imageQuality: 70,
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+          }),
+          isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+          }),
+        ],
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      returnValue = <dynamic>['0', '1'];
+      expect(
+        () => picker.getMultiImage(maxWidth: -1.0),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMultiImage(maxHeight: -1.0),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept an invalid imageQuality argument', () {
+      returnValue = <dynamic>['0', '1'];
+      expect(
+        () => picker.getMultiImage(imageQuality: -1),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMultiImage(imageQuality: 101),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(await picker.getMultiImage(), isNull);
+      expect(await picker.getMultiImage(), isNull);
+    });
+  });
+
+  group('#getVideo', () {
+    test('passes the image source argument correctly', () async {
+      await picker.getVideo(source: ImageSource.camera);
+      await picker.getVideo(source: ImageSource.gallery);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'cameraDevice': 0,
+            'maxDuration': null,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 1,
+            'cameraDevice': 0,
+            'maxDuration': null,
+          }),
+        ],
+      );
+    });
+
+    test('passes the duration argument correctly', () async {
+      await picker.getVideo(source: ImageSource.camera);
+      await picker.getVideo(
+        source: ImageSource.camera,
+        maxDuration: const Duration(seconds: 10),
+      );
+      await picker.getVideo(
+        source: ImageSource.camera,
+        maxDuration: const Duration(minutes: 1),
+      );
+      await picker.getVideo(
+        source: ImageSource.camera,
+        maxDuration: const Duration(hours: 1),
+      );
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': null,
+            'cameraDevice': 0,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': 10,
+            'cameraDevice': 0,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': 60,
+            'cameraDevice': 0,
+          }),
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': 3600,
+            'cameraDevice': 0,
+          }),
+        ],
+      );
+    });
+
+    test('handles a null video path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(await picker.getVideo(source: ImageSource.gallery), isNull);
+      expect(await picker.getVideo(source: ImageSource.camera), isNull);
+    });
+
+    test('camera position defaults to back', () async {
+      await picker.getVideo(source: ImageSource.camera);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'cameraDevice': 0,
+            'maxDuration': null,
+          }),
+        ],
+      );
+    });
+
+    test('camera position can set to front', () async {
+      await picker.getVideo(
+        source: ImageSource.camera,
+        preferredCameraDevice: CameraDevice.front,
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickVideo', arguments: <String, dynamic>{
+            'source': 0,
+            'maxDuration': null,
+            'cameraDevice': 1,
+          }),
+        ],
+      );
+    });
+  });
+
+  group('#getLostData', () {
+    test('getLostData get success response', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, String>{
+          'type': 'image',
+          'path': '/example/path',
+        };
+      });
+      final LostDataResponse response = await picker.getLostData();
+      expect(response.type, RetrieveType.image);
+      expect(response.file, isNotNull);
+      expect(response.file!.path, '/example/path');
+    });
+
+    test('getLostData should successfully retrieve multiple files', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, dynamic>{
+          'type': 'image',
+          'path': '/example/path1',
+          'pathList': <dynamic>['/example/path0', '/example/path1'],
+        };
+      });
+      final LostDataResponse response = await picker.getLostData();
+      expect(response.type, RetrieveType.image);
+      expect(response.file, isNotNull);
+      expect(response.file!.path, '/example/path1');
+      expect(response.files!.first.path, '/example/path0');
+      expect(response.files!.length, 2);
+    });
+
+    test('getLostData get error response', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, String>{
+          'type': 'video',
+          'errorCode': 'test_error_code',
+          'errorMessage': 'test_error_message',
+        };
+      });
+      final LostDataResponse response = await picker.getLostData();
+      expect(response.type, RetrieveType.video);
+      expect(response.exception, isNotNull);
+      expect(response.exception!.code, 'test_error_code');
+      expect(response.exception!.message, 'test_error_message');
+    });
+
+    test('getLostData get null response', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return null;
+      });
+      expect((await picker.getLostData()).isEmpty, true);
+    });
+
+    test('getLostData get both path and error should throw', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        return <String, String>{
+          'type': 'video',
+          'errorCode': 'test_error_code',
+          'errorMessage': 'test_error_message',
+          'path': '/example/path',
+        };
+      });
+      expect(picker.getLostData(), throwsAssertionError);
+    });
+  });
+
+  group('#getImageFromSource', () {
+    test('passes the image source argument correctly', () async {
+      await picker.getImageFromSource(source: ImageSource.camera);
+      await picker.getImageFromSource(source: ImageSource.gallery);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 1,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      await picker.getImageFromSource(source: ImageSource.camera);
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(maxWidth: 10.0),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(maxHeight: 10.0),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+        ),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxWidth: 10.0,
+          imageQuality: 70,
+        ),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxHeight: 10.0,
+          imageQuality: 70,
+        ),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+          imageQuality: 70,
+        ),
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('does not accept an invalid imageQuality argument', () {
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.gallery,
+          options: const ImagePickerOptions(imageQuality: -1),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.gallery,
+          options: const ImagePickerOptions(imageQuality: 101),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(imageQuality: -1),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(imageQuality: 101),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(maxWidth: -1.0),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(maxHeight: -1.0),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+      expect(
+          await picker.getImageFromSource(source: ImageSource.gallery), isNull);
+      expect(
+          await picker.getImageFromSource(source: ImageSource.camera), isNull);
+    });
+
+    test('camera position defaults to back', () async {
+      await picker.getImageFromSource(source: ImageSource.camera);
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('camera position can set to front', () async {
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          preferredCameraDevice: CameraDevice.front,
+        ),
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 1,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('passes the full metadata argument correctly', () async {
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(requestFullMetadata: false),
+      );
+
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': 0,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': 0,
+            'requestFullMetadata': false,
+          }),
+        ],
+      );
+    });
+  });
+}