From c814beafc7d67e067c49958a00eff63e08969fad Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 12:13:09 -0400 Subject: [PATCH 1/9] Direct copy of platform interface files --- .../lib/src/method_channel_camera.dart | 587 +++++++++ .../lib/src/type_conversion.dart | 63 + .../camera/camera_android/lib/src/utils.dart | 51 + .../test/method_channel_camera_test.dart | 1089 +++++++++++++++++ .../test/method_channel_mock.dart | 39 + .../test/type_conversion_test.dart | 87 ++ .../camera_android/test/utils_test.dart | 60 + .../lib/src/method_channel_camera.dart | 587 +++++++++ .../lib/src/type_conversion.dart | 63 + .../camera_avfoundation/lib/src/utils.dart | 51 + .../test/method_channel_camera_test.dart | 1089 +++++++++++++++++ .../test/method_channel_mock.dart | 39 + .../test/type_conversion_test.dart | 87 ++ .../camera_avfoundation/test/utils_test.dart | 60 + 14 files changed, 3952 insertions(+) create mode 100644 packages/camera/camera_android/lib/src/method_channel_camera.dart create mode 100644 packages/camera/camera_android/lib/src/type_conversion.dart create mode 100644 packages/camera/camera_android/lib/src/utils.dart create mode 100644 packages/camera/camera_android/test/method_channel_camera_test.dart create mode 100644 packages/camera/camera_android/test/method_channel_mock.dart create mode 100644 packages/camera/camera_android/test/type_conversion_test.dart create mode 100644 packages/camera/camera_android/test/utils_test.dart create mode 100644 packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart create mode 100644 packages/camera/camera_avfoundation/lib/src/type_conversion.dart create mode 100644 packages/camera/camera_avfoundation/lib/src/utils.dart create mode 100644 packages/camera/camera_avfoundation/test/method_channel_camera_test.dart create mode 100644 packages/camera/camera_avfoundation/test/method_channel_mock.dart create mode 100644 packages/camera/camera_avfoundation/test/type_conversion_test.dart create mode 100644 packages/camera/camera_avfoundation/test/utils_test.dart diff --git a/packages/camera/camera_android/lib/src/method_channel_camera.dart b/packages/camera/camera_android/lib/src/method_channel_camera.dart new file mode 100644 index 000000000000..babef144b086 --- /dev/null +++ b/packages/camera/camera_android/lib/src/method_channel_camera.dart @@ -0,0 +1,587 @@ +// 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 'dart:math'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_transform/stream_transform.dart'; + +import 'type_conversion.dart'; + +const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); + +/// An implementation of [CameraPlatform] that uses method channels. +class MethodChannelCamera extends CameraPlatform { + /// Construct a new method channel camera instance. + MethodChannelCamera() { + const MethodChannel channel = + MethodChannel('flutter.io/cameraPlugin/device'); + channel.setMethodCallHandler( + (MethodCall call) => handleDeviceMethodCall(call)); + } + + final Map _channels = {}; + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to camera events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController cameraEventStreamController = + StreamController.broadcast(); + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to general device events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController deviceEventStreamController = + StreamController.broadcast(); + + // The stream to receive frames from the native code. + StreamSubscription? _platformImageStreamSubscription; + + // The stream for vending frames to platform interface clients. + StreamController? _frameStreamController; + + Stream _cameraEvents(int cameraId) => + cameraEventStreamController.stream + .where((CameraEvent event) => event.cameraId == cameraId); + + @override + Future> availableCameras() async { + try { + final List>? cameras = await _channel + .invokeListMethod>('availableCameras'); + + if (cameras == null) { + return []; + } + + return cameras.map((Map camera) { + return CameraDescription( + name: camera['name']! as String, + lensDirection: + parseCameraLensDirection(camera['lensFacing']! as String), + sensorOrientation: camera['sensorOrientation']! as int, + ); + }).toList(); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future createCamera( + CameraDescription cameraDescription, + ResolutionPreset? resolutionPreset, { + bool enableAudio = false, + }) async { + try { + final Map? reply = await _channel + .invokeMapMethod('create', { + 'cameraName': cameraDescription.name, + 'resolutionPreset': resolutionPreset != null + ? _serializeResolutionPreset(resolutionPreset) + : null, + 'enableAudio': enableAudio, + }); + + return reply!['cameraId']! as int; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future initializeCamera( + int cameraId, { + ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, + }) { + _channels.putIfAbsent(cameraId, () { + final MethodChannel channel = + MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); + channel.setMethodCallHandler( + (MethodCall call) => handleCameraMethodCall(call, cameraId)); + return channel; + }); + + final Completer _completer = Completer(); + + onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { + _completer.complete(); + }); + + _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': cameraId, + 'imageFormatGroup': imageFormatGroup.name(), + }, + ).catchError( + (Object error, StackTrace stackTrace) { + if (error is! PlatformException) { + throw error; + } + _completer.completeError( + CameraException(error.code, error.message), + stackTrace, + ); + }, + ); + + return _completer.future; + } + + @override + Future dispose(int cameraId) async { + if (_channels.containsKey(cameraId)) { + final MethodChannel? cameraChannel = _channels[cameraId]; + cameraChannel?.setMethodCallHandler(null); + _channels.remove(cameraId); + } + + await _channel.invokeMethod( + 'dispose', + {'cameraId': cameraId}, + ); + } + + @override + Stream onCameraInitialized(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraResolutionChanged(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraClosing(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraError(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onVideoRecordedEvent(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onDeviceOrientationChanged() { + return deviceEventStreamController.stream + .whereType(); + } + + @override + Future lockCaptureOrientation( + int cameraId, + DeviceOrientation orientation, + ) async { + await _channel.invokeMethod( + 'lockCaptureOrientation', + { + 'cameraId': cameraId, + 'orientation': serializeDeviceOrientation(orientation) + }, + ); + } + + @override + Future unlockCaptureOrientation(int cameraId) async { + await _channel.invokeMethod( + 'unlockCaptureOrientation', + {'cameraId': cameraId}, + ); + } + + @override + Future takePicture(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'takePicture', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future prepareForVideoRecording() => + _channel.invokeMethod('prepareForVideoRecording'); + + @override + Future startVideoRecording(int cameraId, + {Duration? maxVideoDuration}) async { + await _channel.invokeMethod( + 'startVideoRecording', + { + 'cameraId': cameraId, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds, + }, + ); + } + + @override + Future stopVideoRecording(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'stopVideoRecording', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); + + @override + Future resumeVideoRecording(int cameraId) => + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); + + @override + Stream onStreamedFrameAvailable(int cameraId, + {CameraImageStreamOptions? options}) { + _frameStreamController = StreamController( + onListen: _onFrameStreamListen, + onPause: _onFrameStreamPauseResume, + onResume: _onFrameStreamPauseResume, + onCancel: _onFrameStreamCancel, + ); + return _frameStreamController!.stream; + } + + void _onFrameStreamListen() { + _startPlatformStream(); + } + + Future _startPlatformStream() async { + await _channel.invokeMethod('startImageStream'); + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _platformImageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + try { + _channel.invokeMethod('receivedImageStreamData'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + _frameStreamController! + .add(cameraImageFromPlatformData(imageData as Map)); + }); + } + + FutureOr _onFrameStreamCancel() async { + await _channel.invokeMethod('stopImageStream'); + await _platformImageStreamSubscription?.cancel(); + _platformImageStreamSubscription = null; + _frameStreamController = null; + } + + void _onFrameStreamPauseResume() { + throw CameraException('InvalidCall', + 'Pause and resume are not supported for onStreamedFrameAvailable'); + } + + @override + Future setFlashMode(int cameraId, FlashMode mode) => + _channel.invokeMethod( + 'setFlashMode', + { + 'cameraId': cameraId, + 'mode': _serializeFlashMode(mode), + }, + ); + + @override + Future setExposureMode(int cameraId, ExposureMode mode) => + _channel.invokeMethod( + 'setExposureMode', + { + 'cameraId': cameraId, + 'mode': serializeExposureMode(mode), + }, + ); + + @override + Future setExposurePoint(int cameraId, Point? point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + return _channel.invokeMethod( + 'setExposurePoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMinExposureOffset(int cameraId) async { + final double? minExposureOffset = await _channel.invokeMethod( + 'getMinExposureOffset', + {'cameraId': cameraId}, + ); + + return minExposureOffset!; + } + + @override + Future getMaxExposureOffset(int cameraId) async { + final double? maxExposureOffset = await _channel.invokeMethod( + 'getMaxExposureOffset', + {'cameraId': cameraId}, + ); + + return maxExposureOffset!; + } + + @override + Future getExposureOffsetStepSize(int cameraId) async { + final double? stepSize = await _channel.invokeMethod( + 'getExposureOffsetStepSize', + {'cameraId': cameraId}, + ); + + return stepSize!; + } + + @override + Future setExposureOffset(int cameraId, double offset) async { + final double? appliedOffset = await _channel.invokeMethod( + 'setExposureOffset', + { + 'cameraId': cameraId, + 'offset': offset, + }, + ); + + return appliedOffset!; + } + + @override + Future setFocusMode(int cameraId, FocusMode mode) => + _channel.invokeMethod( + 'setFocusMode', + { + 'cameraId': cameraId, + 'mode': serializeFocusMode(mode), + }, + ); + + @override + Future setFocusPoint(int cameraId, Point? point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + return _channel.invokeMethod( + 'setFocusPoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMaxZoomLevel(int cameraId) async { + final double? maxZoomLevel = await _channel.invokeMethod( + 'getMaxZoomLevel', + {'cameraId': cameraId}, + ); + + return maxZoomLevel!; + } + + @override + Future getMinZoomLevel(int cameraId) async { + final double? minZoomLevel = await _channel.invokeMethod( + 'getMinZoomLevel', + {'cameraId': cameraId}, + ); + + return minZoomLevel!; + } + + @override + Future setZoomLevel(int cameraId, double zoom) async { + try { + await _channel.invokeMethod( + 'setZoomLevel', + { + 'cameraId': cameraId, + 'zoom': zoom, + }, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future pausePreview(int cameraId) async { + await _channel.invokeMethod( + 'pausePreview', + {'cameraId': cameraId}, + ); + } + + @override + Future resumePreview(int cameraId) async { + await _channel.invokeMethod( + 'resumePreview', + {'cameraId': cameraId}, + ); + } + + @override + Widget buildPreview(int cameraId) { + return Texture(textureId: cameraId); + } + + /// Returns the flash mode as a String. + String _serializeFlashMode(FlashMode flashMode) { + switch (flashMode) { + case FlashMode.off: + return 'off'; + case FlashMode.auto: + return 'auto'; + case FlashMode.always: + return 'always'; + case FlashMode.torch: + return 'torch'; + default: + throw ArgumentError('Unknown FlashMode value'); + } + } + + /// Returns the resolution preset as a String. + String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + switch (resolutionPreset) { + case ResolutionPreset.max: + return 'max'; + case ResolutionPreset.ultraHigh: + return 'ultraHigh'; + case ResolutionPreset.veryHigh: + return 'veryHigh'; + case ResolutionPreset.high: + return 'high'; + case ResolutionPreset.medium: + return 'medium'; + case ResolutionPreset.low: + return 'low'; + default: + throw ArgumentError('Unknown ResolutionPreset value'); + } + } + + /// Converts messages received from the native platform into device events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + Future handleDeviceMethodCall(MethodCall call) async { + switch (call.method) { + case 'orientation_changed': + deviceEventStreamController.add(DeviceOrientationChangedEvent( + deserializeDeviceOrientation( + call.arguments['orientation']! as String))); + break; + default: + throw MissingPluginException(); + } + } + + /// Converts messages received from the native platform into camera events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + Future handleCameraMethodCall(MethodCall call, int cameraId) async { + switch (call.method) { + case 'initialized': + cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + call.arguments['previewWidth']! as double, + call.arguments['previewHeight']! as double, + deserializeExposureMode(call.arguments['exposureMode']! as String), + call.arguments['exposurePointSupported']! as bool, + deserializeFocusMode(call.arguments['focusMode']! as String), + call.arguments['focusPointSupported']! as bool, + )); + break; + case 'resolution_changed': + cameraEventStreamController.add(CameraResolutionChangedEvent( + cameraId, + call.arguments['captureWidth']! as double, + call.arguments['captureHeight']! as double, + )); + break; + case 'camera_closing': + cameraEventStreamController.add(CameraClosingEvent( + cameraId, + )); + break; + case 'video_recorded': + cameraEventStreamController.add(VideoRecordedEvent( + cameraId, + XFile(call.arguments['path']! as String), + call.arguments['maxVideoDuration'] != null + ? Duration( + milliseconds: call.arguments['maxVideoDuration']! as int) + : null, + )); + break; + case 'error': + cameraEventStreamController.add(CameraErrorEvent( + cameraId, + call.arguments['description']! as String, + )); + break; + default: + throw MissingPluginException(); + } + } +} diff --git a/packages/camera/camera_android/lib/src/type_conversion.dart b/packages/camera/camera_android/lib/src/type_conversion.dart new file mode 100644 index 000000000000..8b360077305c --- /dev/null +++ b/packages/camera/camera_android/lib/src/type_conversion.dart @@ -0,0 +1,63 @@ +// 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. + +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; + +import '../types/types.dart'; + +/// Converts method channel call [data] for `receivedImageStreamData` to a +/// [CameraImageData]. +CameraImageData cameraImageFromPlatformData(Map data) { + return CameraImageData( + format: _cameraImageFormatFromPlatformData(data['format']), + height: data['height'] as int, + width: data['width'] as int, + lensAperture: data['lensAperture'] as double?, + sensorExposureTime: data['sensorExposureTime'] as int?, + sensorSensitivity: data['sensorSensitivity'] as double?, + planes: List.unmodifiable( + (data['planes'] as List).map( + (dynamic planeData) => _cameraImagePlaneFromPlatformData( + planeData as Map)))); +} + +CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { + return CameraImageFormat(_imageFormatGroupFromPlatformData(data), raw: data); +} + +ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { + if (defaultTargetPlatform == TargetPlatform.android) { + switch (data) { + case 35: // android.graphics.ImageFormat.YUV_420_888 + return ImageFormatGroup.yuv420; + case 256: // android.graphics.ImageFormat.JPEG + return ImageFormatGroup.jpeg; + } + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + switch (data) { + case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + return ImageFormatGroup.yuv420; + + case 1111970369: // kCVPixelFormatType_32BGRA + return ImageFormatGroup.bgra8888; + } + } + + return ImageFormatGroup.unknown; +} + +CameraImagePlane _cameraImagePlaneFromPlatformData(Map data) { + return CameraImagePlane( + bytes: data['bytes'] as Uint8List, + bytesPerPixel: data['bytesPerPixel'] as int?, + bytesPerRow: data['bytesPerRow'] as int, + height: data['height'] as int?, + width: data['width'] as int?); +} diff --git a/packages/camera/camera_android/lib/src/utils.dart b/packages/camera/camera_android/lib/src/utils.dart new file mode 100644 index 000000000000..663ec6da7a97 --- /dev/null +++ b/packages/camera/camera_android/lib/src/utils.dart @@ -0,0 +1,51 @@ +// 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:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/services.dart'; + +/// Parses a string into a corresponding CameraLensDirection. +CameraLensDirection parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); +} + +/// Returns the device orientation as a String. +String serializeDeviceOrientation(DeviceOrientation orientation) { + switch (orientation) { + case DeviceOrientation.portraitUp: + return 'portraitUp'; + case DeviceOrientation.portraitDown: + return 'portraitDown'; + case DeviceOrientation.landscapeRight: + return 'landscapeRight'; + case DeviceOrientation.landscapeLeft: + return 'landscapeLeft'; + default: + throw ArgumentError('Unknown DeviceOrientation value'); + } +} + +/// Returns the device orientation for a given String. +DeviceOrientation deserializeDeviceOrientation(String str) { + switch (str) { + case 'portraitUp': + return DeviceOrientation.portraitUp; + case 'portraitDown': + return DeviceOrientation.portraitDown; + case 'landscapeRight': + return DeviceOrientation.landscapeRight; + case 'landscapeLeft': + return DeviceOrientation.landscapeLeft; + default: + throw ArgumentError('"$str" is not a valid DeviceOrientation value'); + } +} diff --git a/packages/camera/camera_android/test/method_channel_camera_test.dart b/packages/camera/camera_android/test/method_channel_camera_test.dart new file mode 100644 index 000000000000..d096f0012c86 --- /dev/null +++ b/packages/camera/camera_android/test/method_channel_camera_test.dart @@ -0,0 +1,1089 @@ +// 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 'dart:math'; + +import 'package:async/async.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/method_channel_mock.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$MethodChannelCamera', () { + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + } + }); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0), + ResolutionPreset.high, + ); + + // Assert + expect(cameraMockChannel.log, [ + isMethodCall( + 'create', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': false + }, + ), + ]); + expect(cameraId, 1); + }); + + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test( + 'Should throw CameraException when initialize throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }, + ); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + expect( + () => camera.initializeCamera(0), + throwsA( + isA() + .having((CameraException e) => e.code, 'code', + 'TESTING_ERROR_CODE') + .having( + (CameraException e) => e.description, + 'description', + 'Mock error message used during testing.', + ), + ), + ); + }, + ); + + test('Should send initialization data', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + 'initialize': null + }); + final MethodChannelCamera camera = MethodChannelCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + + // Act + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null, + 'dispose': {'cameraId': 1} + }); + + final MethodChannelCamera camera = MethodChannelCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Act + await camera.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + anything, + isMethodCall( + 'dispose', + arguments: {'cameraId': 1}, + ), + ]); + }); + }); + + group('Event Tests', () { + late MethodChannelCamera camera; + late int cameraId; + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + }); + + test('Should receive initialized event', () async { + // Act + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraInitializedEvent event = CameraInitializedEvent( + cameraId, + 3840, + 2160, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ); + await camera.handleCameraMethodCall( + MethodCall('initialized', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive resolution changes', () async { + // Act + final Stream resolutionStream = + camera.onCameraResolutionChanged(cameraId); + final StreamQueue streamQueue = + StreamQueue(resolutionStream); + + // Emit test events + final CameraResolutionChangedEvent fhdEvent = + CameraResolutionChangedEvent(cameraId, 1920, 1080); + final CameraResolutionChangedEvent uhdEvent = + CameraResolutionChangedEvent(cameraId, 3840, 2160); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + camera.onCameraClosing(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraClosingEvent event = CameraClosingEvent(cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final Stream errorStream = + camera.onCameraError(cameraId); + final StreamQueue streamQueue = + StreamQueue(errorStream); + + // Emit test events + final CameraErrorEvent event = + CameraErrorEvent(cameraId, 'Error Description'); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive device orientation change events', () async { + // Act + final Stream eventStream = + camera.onDeviceOrientationChanged(); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + const DeviceOrientationChangedEvent event = + DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + + group('Function Tests', () { + late MethodChannelCamera camera; + late int cameraId; + + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add( + CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ), + ); + await initializeFuture; + }); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Test 1', + 'lensFacing': 'front', + 'sensorOrientation': 1 + }, + { + 'name': 'Test 2', + 'lensFacing': 'back', + 'sensorOrientation': 2 + } + ]; + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'availableCameras': returnData}, + ); + + // Act + final List cameras = await camera.availableCameras(); + + // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name']! as String, + lensDirection: parseCameraLensDirection( + returnData[i]['lensFacing']! as String), + sensorOrientation: returnData[i]['sensorOrientation']! as int, + ); + expect(cameras[i], cameraDescription); + } + }); + + test( + 'Should throw CameraException when availableCameras throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + + // Act + expect( + camera.availableCameras, + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test('Should take a picture and return an XFile instance', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'takePicture': '/test/path.jpg'}); + + // Act + final XFile file = await camera.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.jpg'); + }); + + test('Should prepare for video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'prepareForVideoRecording': null}, + ); + + // Act + await camera.prepareForVideoRecording(); + + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); + + test('Should start recording a video', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': null, + }), + ]); + }); + + test('Should pass maxVideoDuration when starting recording a video', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording( + cameraId, + maxVideoDuration: const Duration(seconds: 10), + ); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': 10000 + }), + ]); + }); + + test('Should stop a video recording and return the file', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': '/test/path.mp4'}, + ); + + // Act + final XFile file = await camera.stopVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.mp4'); + }); + + test('Should pause a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pauseVideoRecording': null}, + ); + + // Act + await camera.pauseVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pauseVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should resume a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumeVideoRecording': null}, + ); + + // Act + await camera.resumeVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumeVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the flash mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFlashMode': null}, + ); + + // Act + await camera.setFlashMode(cameraId, FlashMode.torch); + await camera.setFlashMode(cameraId, FlashMode.always); + await camera.setFlashMode(cameraId, FlashMode.auto); + await camera.setFlashMode(cameraId, FlashMode.off); + + // Assert + expect(channel.log, [ + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'torch' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'always' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'auto' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'off' + }), + ]); + }); + + test('Should set the exposure mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureMode': null}, + ); + + // Act + await camera.setExposureMode(cameraId, ExposureMode.auto); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setExposureMode', arguments: { + 'cameraId': cameraId, + 'mode': 'auto' + }), + isMethodCall('setExposureMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); + + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposurePoint': null}, + ); + + // Act + await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); + await camera.setExposurePoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); + + test('Should get the min exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinExposureOffset': 2.0}, + ); + + // Act + final double minExposureOffset = + await camera.getMinExposureOffset(cameraId); + + // Assert + expect(minExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMinExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the max exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxExposureOffset': 2.0}, + ); + + // Act + final double maxExposureOffset = + await camera.getMaxExposureOffset(cameraId); + + // Assert + expect(maxExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMaxExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the exposure offset step size', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getExposureOffsetStepSize': 0.25}, + ); + + // Act + final double stepSize = + await camera.getExposureOffsetStepSize(cameraId); + + // Assert + expect(stepSize, 0.25); + expect(channel.log, [ + isMethodCall('getExposureOffsetStepSize', + arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureOffset': 0.6}, + ); + + // Act + final double actualOffset = + await camera.setExposureOffset(cameraId, 0.5); + + // Assert + expect(actualOffset, 0.6); + expect(channel.log, [ + isMethodCall('setExposureOffset', arguments: { + 'cameraId': cameraId, + 'offset': 0.5, + }), + ]); + }); + + test('Should set the focus mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusMode': null}, + ); + + // Act + await camera.setFocusMode(cameraId, FocusMode.auto); + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusMode', arguments: { + 'cameraId': cameraId, + 'mode': 'auto' + }), + isMethodCall('setFocusMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); + + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusPoint': null}, + ); + + // Act + await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); + await camera.setFocusPoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); + + test('Should build a texture widget as preview widget', () async { + // Act + final Widget widget = camera.buildPreview(cameraId); + + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); + + test('Should throw MissingPluginException when handling unknown method', + () { + final MethodChannelCamera camera = MethodChannelCamera(); + + expect( + () => camera.handleCameraMethodCall( + const MethodCall('unknown_method'), 1), + throwsA(isA())); + }); + + test('Should get the max zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxZoomLevel': 10.0}, + ); + + // Act + final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); + + // Assert + expect(maxZoomLevel, 10.0); + expect(channel.log, [ + isMethodCall('getMaxZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the min zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinZoomLevel': 1.0}, + ); + + // Act + final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); + + // Assert + expect(maxZoomLevel, 1.0); + expect(channel.log, [ + isMethodCall('getMinZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setZoomLevel': null}, + ); + + // Act + await camera.setZoomLevel(cameraId, 2.0); + + // Assert + expect(channel.log, [ + isMethodCall('setZoomLevel', + arguments: {'cameraId': cameraId, 'zoom': 2.0}), + ]); + }); + + test('Should throw CameraException when illegal zoom level is supplied', + () async { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'setZoomLevel': PlatformException( + code: 'ZOOM_ERROR', + message: 'Illegal zoom error', + details: null, + ) + }, + ); + + // Act & assert + expect( + () => camera.setZoomLevel(cameraId, -1.0), + throwsA(isA() + .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') + .having((CameraException e) => e.description, 'description', + 'Illegal zoom error'))); + }); + + test('Should lock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'lockCaptureOrientation': null}, + ); + + // Act + await camera.lockCaptureOrientation( + cameraId, DeviceOrientation.portraitUp); + + // Assert + expect(channel.log, [ + isMethodCall('lockCaptureOrientation', arguments: { + 'cameraId': cameraId, + 'orientation': 'portraitUp' + }), + ]); + }); + + test('Should unlock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'unlockCaptureOrientation': null}, + ); + + // Act + await camera.unlockCaptureOrientation(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('unlockCaptureOrientation', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should pause the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pausePreview': null}, + ); + + // Act + await camera.pausePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pausePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should resume the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumePreview': null}, + ); + + // Act + await camera.resumePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should start streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + ]); + + subscription.cancel(); + }); + + test('Should stop streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + subscription.cancel(); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null), + ]); + }); + }); + }); +} diff --git a/packages/camera/camera_android/test/method_channel_mock.dart b/packages/camera/camera_android/test/method_channel_mock.dart new file mode 100644 index 000000000000..413c10633cc1 --- /dev/null +++ b/packages/camera/camera_android/test/method_channel_mock.dart @@ -0,0 +1,39 @@ +// 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'; + +class MethodChannelMock { + MethodChannelMock({ + required String channelName, + this.delay, + required this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + final Duration? delay; + final MethodChannel methodChannel; + final Map methods; + final List log = []; + + Future _handler(MethodCall methodCall) async { + log.add(methodCall); + + if (!methods.containsKey(methodCall.method)) { + throw MissingPluginException('No implementation found for method ' + '${methodCall.method} on channel ${methodChannel.name}'); + } + + return Future.delayed(delay ?? Duration.zero, () { + final dynamic result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} diff --git a/packages/camera/camera_android/test/type_conversion_test.dart b/packages/camera/camera_android/test/type_conversion_test.dart new file mode 100644 index 000000000000..4818074ec767 --- /dev/null +++ b/packages/camera/camera_android/test/type_conversion_test.dart @@ -0,0 +1,87 @@ +// 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. + +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/type_conversion.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('CameraImageData can be created', () { + final CameraImageData cameraImage = + cameraImageFromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'lensAperture': 1.8, + 'sensorExposureTime': 9991324, + 'sensorSensitivity': 92.0, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.height, 1); + expect(cameraImage.width, 4); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + expect(cameraImage.planes.length, 1); + }); + + test('CameraImageData has ImageFormatGroup.yuv420 for iOS', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final CameraImageData cameraImage = + cameraImageFromPlatformData({ + 'format': 875704438, + 'height': 1, + 'width': 4, + 'lensAperture': 1.8, + 'sensorExposureTime': 9991324, + 'sensorSensitivity': 92.0, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); + + test('CameraImageData has ImageFormatGroup.yuv420 for Android', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + final CameraImageData cameraImage = + cameraImageFromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'lensAperture': 1.8, + 'sensorExposureTime': 9991324, + 'sensorSensitivity': 92.0, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); +} diff --git a/packages/camera/camera_android/test/utils_test.dart b/packages/camera/camera_android/test/utils_test.dart new file mode 100644 index 000000000000..0e4171d73aa6 --- /dev/null +++ b/packages/camera/camera_android/test/utils_test.dart @@ -0,0 +1,60 @@ +// 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:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Utility methods', () { + test( + 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', + () { + expect( + parseCameraLensDirection('back'), + CameraLensDirection.back, + ); + expect( + parseCameraLensDirection('front'), + CameraLensDirection.front, + ); + expect( + parseCameraLensDirection('external'), + CameraLensDirection.external, + ); + }); + + test( + 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', + () { + expect( + () => parseCameraLensDirection('test'), + throwsA(isArgumentError), + ); + }); + + test('serializeDeviceOrientation() should serialize correctly', () { + expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), + 'portraitUp'); + expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), + 'portraitDown'); + expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), + 'landscapeRight'); + expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), + 'landscapeLeft'); + }); + + test('deserializeDeviceOrientation() should deserialize correctly', () { + expect(deserializeDeviceOrientation('portraitUp'), + DeviceOrientation.portraitUp); + expect(deserializeDeviceOrientation('portraitDown'), + DeviceOrientation.portraitDown); + expect(deserializeDeviceOrientation('landscapeRight'), + DeviceOrientation.landscapeRight); + expect(deserializeDeviceOrientation('landscapeLeft'), + DeviceOrientation.landscapeLeft); + }); + }); +} diff --git a/packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart b/packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart new file mode 100644 index 000000000000..babef144b086 --- /dev/null +++ b/packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart @@ -0,0 +1,587 @@ +// 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 'dart:math'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_transform/stream_transform.dart'; + +import 'type_conversion.dart'; + +const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); + +/// An implementation of [CameraPlatform] that uses method channels. +class MethodChannelCamera extends CameraPlatform { + /// Construct a new method channel camera instance. + MethodChannelCamera() { + const MethodChannel channel = + MethodChannel('flutter.io/cameraPlugin/device'); + channel.setMethodCallHandler( + (MethodCall call) => handleDeviceMethodCall(call)); + } + + final Map _channels = {}; + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to camera events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController cameraEventStreamController = + StreamController.broadcast(); + + /// The controller we need to broadcast the different events coming + /// from handleMethodCall, specific to general device events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController deviceEventStreamController = + StreamController.broadcast(); + + // The stream to receive frames from the native code. + StreamSubscription? _platformImageStreamSubscription; + + // The stream for vending frames to platform interface clients. + StreamController? _frameStreamController; + + Stream _cameraEvents(int cameraId) => + cameraEventStreamController.stream + .where((CameraEvent event) => event.cameraId == cameraId); + + @override + Future> availableCameras() async { + try { + final List>? cameras = await _channel + .invokeListMethod>('availableCameras'); + + if (cameras == null) { + return []; + } + + return cameras.map((Map camera) { + return CameraDescription( + name: camera['name']! as String, + lensDirection: + parseCameraLensDirection(camera['lensFacing']! as String), + sensorOrientation: camera['sensorOrientation']! as int, + ); + }).toList(); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future createCamera( + CameraDescription cameraDescription, + ResolutionPreset? resolutionPreset, { + bool enableAudio = false, + }) async { + try { + final Map? reply = await _channel + .invokeMapMethod('create', { + 'cameraName': cameraDescription.name, + 'resolutionPreset': resolutionPreset != null + ? _serializeResolutionPreset(resolutionPreset) + : null, + 'enableAudio': enableAudio, + }); + + return reply!['cameraId']! as int; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future initializeCamera( + int cameraId, { + ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, + }) { + _channels.putIfAbsent(cameraId, () { + final MethodChannel channel = + MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); + channel.setMethodCallHandler( + (MethodCall call) => handleCameraMethodCall(call, cameraId)); + return channel; + }); + + final Completer _completer = Completer(); + + onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { + _completer.complete(); + }); + + _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': cameraId, + 'imageFormatGroup': imageFormatGroup.name(), + }, + ).catchError( + (Object error, StackTrace stackTrace) { + if (error is! PlatformException) { + throw error; + } + _completer.completeError( + CameraException(error.code, error.message), + stackTrace, + ); + }, + ); + + return _completer.future; + } + + @override + Future dispose(int cameraId) async { + if (_channels.containsKey(cameraId)) { + final MethodChannel? cameraChannel = _channels[cameraId]; + cameraChannel?.setMethodCallHandler(null); + _channels.remove(cameraId); + } + + await _channel.invokeMethod( + 'dispose', + {'cameraId': cameraId}, + ); + } + + @override + Stream onCameraInitialized(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraResolutionChanged(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraClosing(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onCameraError(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onVideoRecordedEvent(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + @override + Stream onDeviceOrientationChanged() { + return deviceEventStreamController.stream + .whereType(); + } + + @override + Future lockCaptureOrientation( + int cameraId, + DeviceOrientation orientation, + ) async { + await _channel.invokeMethod( + 'lockCaptureOrientation', + { + 'cameraId': cameraId, + 'orientation': serializeDeviceOrientation(orientation) + }, + ); + } + + @override + Future unlockCaptureOrientation(int cameraId) async { + await _channel.invokeMethod( + 'unlockCaptureOrientation', + {'cameraId': cameraId}, + ); + } + + @override + Future takePicture(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'takePicture', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future prepareForVideoRecording() => + _channel.invokeMethod('prepareForVideoRecording'); + + @override + Future startVideoRecording(int cameraId, + {Duration? maxVideoDuration}) async { + await _channel.invokeMethod( + 'startVideoRecording', + { + 'cameraId': cameraId, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds, + }, + ); + } + + @override + Future stopVideoRecording(int cameraId) async { + final String? path = await _channel.invokeMethod( + 'stopVideoRecording', + {'cameraId': cameraId}, + ); + + if (path == null) { + throw CameraException( + 'INVALID_PATH', + 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', + ); + } + + return XFile(path); + } + + @override + Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); + + @override + Future resumeVideoRecording(int cameraId) => + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); + + @override + Stream onStreamedFrameAvailable(int cameraId, + {CameraImageStreamOptions? options}) { + _frameStreamController = StreamController( + onListen: _onFrameStreamListen, + onPause: _onFrameStreamPauseResume, + onResume: _onFrameStreamPauseResume, + onCancel: _onFrameStreamCancel, + ); + return _frameStreamController!.stream; + } + + void _onFrameStreamListen() { + _startPlatformStream(); + } + + Future _startPlatformStream() async { + await _channel.invokeMethod('startImageStream'); + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _platformImageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + try { + _channel.invokeMethod('receivedImageStreamData'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + _frameStreamController! + .add(cameraImageFromPlatformData(imageData as Map)); + }); + } + + FutureOr _onFrameStreamCancel() async { + await _channel.invokeMethod('stopImageStream'); + await _platformImageStreamSubscription?.cancel(); + _platformImageStreamSubscription = null; + _frameStreamController = null; + } + + void _onFrameStreamPauseResume() { + throw CameraException('InvalidCall', + 'Pause and resume are not supported for onStreamedFrameAvailable'); + } + + @override + Future setFlashMode(int cameraId, FlashMode mode) => + _channel.invokeMethod( + 'setFlashMode', + { + 'cameraId': cameraId, + 'mode': _serializeFlashMode(mode), + }, + ); + + @override + Future setExposureMode(int cameraId, ExposureMode mode) => + _channel.invokeMethod( + 'setExposureMode', + { + 'cameraId': cameraId, + 'mode': serializeExposureMode(mode), + }, + ); + + @override + Future setExposurePoint(int cameraId, Point? point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + return _channel.invokeMethod( + 'setExposurePoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMinExposureOffset(int cameraId) async { + final double? minExposureOffset = await _channel.invokeMethod( + 'getMinExposureOffset', + {'cameraId': cameraId}, + ); + + return minExposureOffset!; + } + + @override + Future getMaxExposureOffset(int cameraId) async { + final double? maxExposureOffset = await _channel.invokeMethod( + 'getMaxExposureOffset', + {'cameraId': cameraId}, + ); + + return maxExposureOffset!; + } + + @override + Future getExposureOffsetStepSize(int cameraId) async { + final double? stepSize = await _channel.invokeMethod( + 'getExposureOffsetStepSize', + {'cameraId': cameraId}, + ); + + return stepSize!; + } + + @override + Future setExposureOffset(int cameraId, double offset) async { + final double? appliedOffset = await _channel.invokeMethod( + 'setExposureOffset', + { + 'cameraId': cameraId, + 'offset': offset, + }, + ); + + return appliedOffset!; + } + + @override + Future setFocusMode(int cameraId, FocusMode mode) => + _channel.invokeMethod( + 'setFocusMode', + { + 'cameraId': cameraId, + 'mode': serializeFocusMode(mode), + }, + ); + + @override + Future setFocusPoint(int cameraId, Point? point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + + return _channel.invokeMethod( + 'setFocusPoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMaxZoomLevel(int cameraId) async { + final double? maxZoomLevel = await _channel.invokeMethod( + 'getMaxZoomLevel', + {'cameraId': cameraId}, + ); + + return maxZoomLevel!; + } + + @override + Future getMinZoomLevel(int cameraId) async { + final double? minZoomLevel = await _channel.invokeMethod( + 'getMinZoomLevel', + {'cameraId': cameraId}, + ); + + return minZoomLevel!; + } + + @override + Future setZoomLevel(int cameraId, double zoom) async { + try { + await _channel.invokeMethod( + 'setZoomLevel', + { + 'cameraId': cameraId, + 'zoom': zoom, + }, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future pausePreview(int cameraId) async { + await _channel.invokeMethod( + 'pausePreview', + {'cameraId': cameraId}, + ); + } + + @override + Future resumePreview(int cameraId) async { + await _channel.invokeMethod( + 'resumePreview', + {'cameraId': cameraId}, + ); + } + + @override + Widget buildPreview(int cameraId) { + return Texture(textureId: cameraId); + } + + /// Returns the flash mode as a String. + String _serializeFlashMode(FlashMode flashMode) { + switch (flashMode) { + case FlashMode.off: + return 'off'; + case FlashMode.auto: + return 'auto'; + case FlashMode.always: + return 'always'; + case FlashMode.torch: + return 'torch'; + default: + throw ArgumentError('Unknown FlashMode value'); + } + } + + /// Returns the resolution preset as a String. + String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + switch (resolutionPreset) { + case ResolutionPreset.max: + return 'max'; + case ResolutionPreset.ultraHigh: + return 'ultraHigh'; + case ResolutionPreset.veryHigh: + return 'veryHigh'; + case ResolutionPreset.high: + return 'high'; + case ResolutionPreset.medium: + return 'medium'; + case ResolutionPreset.low: + return 'low'; + default: + throw ArgumentError('Unknown ResolutionPreset value'); + } + } + + /// Converts messages received from the native platform into device events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + Future handleDeviceMethodCall(MethodCall call) async { + switch (call.method) { + case 'orientation_changed': + deviceEventStreamController.add(DeviceOrientationChangedEvent( + deserializeDeviceOrientation( + call.arguments['orientation']! as String))); + break; + default: + throw MissingPluginException(); + } + } + + /// Converts messages received from the native platform into camera events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + Future handleCameraMethodCall(MethodCall call, int cameraId) async { + switch (call.method) { + case 'initialized': + cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + call.arguments['previewWidth']! as double, + call.arguments['previewHeight']! as double, + deserializeExposureMode(call.arguments['exposureMode']! as String), + call.arguments['exposurePointSupported']! as bool, + deserializeFocusMode(call.arguments['focusMode']! as String), + call.arguments['focusPointSupported']! as bool, + )); + break; + case 'resolution_changed': + cameraEventStreamController.add(CameraResolutionChangedEvent( + cameraId, + call.arguments['captureWidth']! as double, + call.arguments['captureHeight']! as double, + )); + break; + case 'camera_closing': + cameraEventStreamController.add(CameraClosingEvent( + cameraId, + )); + break; + case 'video_recorded': + cameraEventStreamController.add(VideoRecordedEvent( + cameraId, + XFile(call.arguments['path']! as String), + call.arguments['maxVideoDuration'] != null + ? Duration( + milliseconds: call.arguments['maxVideoDuration']! as int) + : null, + )); + break; + case 'error': + cameraEventStreamController.add(CameraErrorEvent( + cameraId, + call.arguments['description']! as String, + )); + break; + default: + throw MissingPluginException(); + } + } +} diff --git a/packages/camera/camera_avfoundation/lib/src/type_conversion.dart b/packages/camera/camera_avfoundation/lib/src/type_conversion.dart new file mode 100644 index 000000000000..8b360077305c --- /dev/null +++ b/packages/camera/camera_avfoundation/lib/src/type_conversion.dart @@ -0,0 +1,63 @@ +// 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. + +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; + +import '../types/types.dart'; + +/// Converts method channel call [data] for `receivedImageStreamData` to a +/// [CameraImageData]. +CameraImageData cameraImageFromPlatformData(Map data) { + return CameraImageData( + format: _cameraImageFormatFromPlatformData(data['format']), + height: data['height'] as int, + width: data['width'] as int, + lensAperture: data['lensAperture'] as double?, + sensorExposureTime: data['sensorExposureTime'] as int?, + sensorSensitivity: data['sensorSensitivity'] as double?, + planes: List.unmodifiable( + (data['planes'] as List).map( + (dynamic planeData) => _cameraImagePlaneFromPlatformData( + planeData as Map)))); +} + +CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { + return CameraImageFormat(_imageFormatGroupFromPlatformData(data), raw: data); +} + +ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { + if (defaultTargetPlatform == TargetPlatform.android) { + switch (data) { + case 35: // android.graphics.ImageFormat.YUV_420_888 + return ImageFormatGroup.yuv420; + case 256: // android.graphics.ImageFormat.JPEG + return ImageFormatGroup.jpeg; + } + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + switch (data) { + case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + return ImageFormatGroup.yuv420; + + case 1111970369: // kCVPixelFormatType_32BGRA + return ImageFormatGroup.bgra8888; + } + } + + return ImageFormatGroup.unknown; +} + +CameraImagePlane _cameraImagePlaneFromPlatformData(Map data) { + return CameraImagePlane( + bytes: data['bytes'] as Uint8List, + bytesPerPixel: data['bytesPerPixel'] as int?, + bytesPerRow: data['bytesPerRow'] as int, + height: data['height'] as int?, + width: data['width'] as int?); +} diff --git a/packages/camera/camera_avfoundation/lib/src/utils.dart b/packages/camera/camera_avfoundation/lib/src/utils.dart new file mode 100644 index 000000000000..663ec6da7a97 --- /dev/null +++ b/packages/camera/camera_avfoundation/lib/src/utils.dart @@ -0,0 +1,51 @@ +// 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:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/services.dart'; + +/// Parses a string into a corresponding CameraLensDirection. +CameraLensDirection parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); +} + +/// Returns the device orientation as a String. +String serializeDeviceOrientation(DeviceOrientation orientation) { + switch (orientation) { + case DeviceOrientation.portraitUp: + return 'portraitUp'; + case DeviceOrientation.portraitDown: + return 'portraitDown'; + case DeviceOrientation.landscapeRight: + return 'landscapeRight'; + case DeviceOrientation.landscapeLeft: + return 'landscapeLeft'; + default: + throw ArgumentError('Unknown DeviceOrientation value'); + } +} + +/// Returns the device orientation for a given String. +DeviceOrientation deserializeDeviceOrientation(String str) { + switch (str) { + case 'portraitUp': + return DeviceOrientation.portraitUp; + case 'portraitDown': + return DeviceOrientation.portraitDown; + case 'landscapeRight': + return DeviceOrientation.landscapeRight; + case 'landscapeLeft': + return DeviceOrientation.landscapeLeft; + default: + throw ArgumentError('"$str" is not a valid DeviceOrientation value'); + } +} diff --git a/packages/camera/camera_avfoundation/test/method_channel_camera_test.dart b/packages/camera/camera_avfoundation/test/method_channel_camera_test.dart new file mode 100644 index 000000000000..d096f0012c86 --- /dev/null +++ b/packages/camera/camera_avfoundation/test/method_channel_camera_test.dart @@ -0,0 +1,1089 @@ +// 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 'dart:math'; + +import 'package:async/async.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/method_channel_mock.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$MethodChannelCamera', () { + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + } + }); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0), + ResolutionPreset.high, + ); + + // Assert + expect(cameraMockChannel.log, [ + isMethodCall( + 'create', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': false + }, + ), + ]); + expect(cameraId, 1); + }); + + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test( + 'Should throw CameraException when initialize throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }, + ); + final MethodChannelCamera camera = MethodChannelCamera(); + + // Act + expect( + () => camera.initializeCamera(0), + throwsA( + isA() + .having((CameraException e) => e.code, 'code', + 'TESTING_ERROR_CODE') + .having( + (CameraException e) => e.description, + 'description', + 'Mock error message used during testing.', + ), + ), + ); + }, + ); + + test('Should send initialization data', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + 'initialize': null + }); + final MethodChannelCamera camera = MethodChannelCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + + // Act + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null, + 'dispose': {'cameraId': 1} + }); + + final MethodChannelCamera camera = MethodChannelCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Act + await camera.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + anything, + isMethodCall( + 'dispose', + arguments: {'cameraId': 1}, + ), + ]); + }); + }); + + group('Event Tests', () { + late MethodChannelCamera camera; + late int cameraId; + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + }); + + test('Should receive initialized event', () async { + // Act + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraInitializedEvent event = CameraInitializedEvent( + cameraId, + 3840, + 2160, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ); + await camera.handleCameraMethodCall( + MethodCall('initialized', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive resolution changes', () async { + // Act + final Stream resolutionStream = + camera.onCameraResolutionChanged(cameraId); + final StreamQueue streamQueue = + StreamQueue(resolutionStream); + + // Emit test events + final CameraResolutionChangedEvent fhdEvent = + CameraResolutionChangedEvent(cameraId, 1920, 1080); + final CameraResolutionChangedEvent uhdEvent = + CameraResolutionChangedEvent(cameraId, 3840, 2160); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + camera.onCameraClosing(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraClosingEvent event = CameraClosingEvent(cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final Stream errorStream = + camera.onCameraError(cameraId); + final StreamQueue streamQueue = + StreamQueue(errorStream); + + // Emit test events + final CameraErrorEvent event = + CameraErrorEvent(cameraId, 'Error Description'); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive device orientation change events', () async { + // Act + final Stream eventStream = + camera.onDeviceOrientationChanged(); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + const DeviceOrientationChangedEvent event = + DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + + group('Function Tests', () { + late MethodChannelCamera camera; + late int cameraId; + + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add( + CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ), + ); + await initializeFuture; + }); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Test 1', + 'lensFacing': 'front', + 'sensorOrientation': 1 + }, + { + 'name': 'Test 2', + 'lensFacing': 'back', + 'sensorOrientation': 2 + } + ]; + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'availableCameras': returnData}, + ); + + // Act + final List cameras = await camera.availableCameras(); + + // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name']! as String, + lensDirection: parseCameraLensDirection( + returnData[i]['lensFacing']! as String), + sensorOrientation: returnData[i]['sensorOrientation']! as int, + ); + expect(cameras[i], cameraDescription); + } + }); + + test( + 'Should throw CameraException when availableCameras throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + + // Act + expect( + camera.availableCameras, + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test('Should take a picture and return an XFile instance', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'takePicture': '/test/path.jpg'}); + + // Act + final XFile file = await camera.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.jpg'); + }); + + test('Should prepare for video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'prepareForVideoRecording': null}, + ); + + // Act + await camera.prepareForVideoRecording(); + + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); + + test('Should start recording a video', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': null, + }), + ]); + }); + + test('Should pass maxVideoDuration when starting recording a video', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording( + cameraId, + maxVideoDuration: const Duration(seconds: 10), + ); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': 10000 + }), + ]); + }); + + test('Should stop a video recording and return the file', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': '/test/path.mp4'}, + ); + + // Act + final XFile file = await camera.stopVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.mp4'); + }); + + test('Should pause a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pauseVideoRecording': null}, + ); + + // Act + await camera.pauseVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pauseVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should resume a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumeVideoRecording': null}, + ); + + // Act + await camera.resumeVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumeVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the flash mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFlashMode': null}, + ); + + // Act + await camera.setFlashMode(cameraId, FlashMode.torch); + await camera.setFlashMode(cameraId, FlashMode.always); + await camera.setFlashMode(cameraId, FlashMode.auto); + await camera.setFlashMode(cameraId, FlashMode.off); + + // Assert + expect(channel.log, [ + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'torch' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'always' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'auto' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'off' + }), + ]); + }); + + test('Should set the exposure mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureMode': null}, + ); + + // Act + await camera.setExposureMode(cameraId, ExposureMode.auto); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setExposureMode', arguments: { + 'cameraId': cameraId, + 'mode': 'auto' + }), + isMethodCall('setExposureMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); + + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposurePoint': null}, + ); + + // Act + await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); + await camera.setExposurePoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); + + test('Should get the min exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinExposureOffset': 2.0}, + ); + + // Act + final double minExposureOffset = + await camera.getMinExposureOffset(cameraId); + + // Assert + expect(minExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMinExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the max exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxExposureOffset': 2.0}, + ); + + // Act + final double maxExposureOffset = + await camera.getMaxExposureOffset(cameraId); + + // Assert + expect(maxExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMaxExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the exposure offset step size', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getExposureOffsetStepSize': 0.25}, + ); + + // Act + final double stepSize = + await camera.getExposureOffsetStepSize(cameraId); + + // Assert + expect(stepSize, 0.25); + expect(channel.log, [ + isMethodCall('getExposureOffsetStepSize', + arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureOffset': 0.6}, + ); + + // Act + final double actualOffset = + await camera.setExposureOffset(cameraId, 0.5); + + // Assert + expect(actualOffset, 0.6); + expect(channel.log, [ + isMethodCall('setExposureOffset', arguments: { + 'cameraId': cameraId, + 'offset': 0.5, + }), + ]); + }); + + test('Should set the focus mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusMode': null}, + ); + + // Act + await camera.setFocusMode(cameraId, FocusMode.auto); + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusMode', arguments: { + 'cameraId': cameraId, + 'mode': 'auto' + }), + isMethodCall('setFocusMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); + + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusPoint': null}, + ); + + // Act + await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); + await camera.setFocusPoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); + + test('Should build a texture widget as preview widget', () async { + // Act + final Widget widget = camera.buildPreview(cameraId); + + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); + + test('Should throw MissingPluginException when handling unknown method', + () { + final MethodChannelCamera camera = MethodChannelCamera(); + + expect( + () => camera.handleCameraMethodCall( + const MethodCall('unknown_method'), 1), + throwsA(isA())); + }); + + test('Should get the max zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxZoomLevel': 10.0}, + ); + + // Act + final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); + + // Assert + expect(maxZoomLevel, 10.0); + expect(channel.log, [ + isMethodCall('getMaxZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the min zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinZoomLevel': 1.0}, + ); + + // Act + final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); + + // Assert + expect(maxZoomLevel, 1.0); + expect(channel.log, [ + isMethodCall('getMinZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setZoomLevel': null}, + ); + + // Act + await camera.setZoomLevel(cameraId, 2.0); + + // Assert + expect(channel.log, [ + isMethodCall('setZoomLevel', + arguments: {'cameraId': cameraId, 'zoom': 2.0}), + ]); + }); + + test('Should throw CameraException when illegal zoom level is supplied', + () async { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'setZoomLevel': PlatformException( + code: 'ZOOM_ERROR', + message: 'Illegal zoom error', + details: null, + ) + }, + ); + + // Act & assert + expect( + () => camera.setZoomLevel(cameraId, -1.0), + throwsA(isA() + .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') + .having((CameraException e) => e.description, 'description', + 'Illegal zoom error'))); + }); + + test('Should lock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'lockCaptureOrientation': null}, + ); + + // Act + await camera.lockCaptureOrientation( + cameraId, DeviceOrientation.portraitUp); + + // Assert + expect(channel.log, [ + isMethodCall('lockCaptureOrientation', arguments: { + 'cameraId': cameraId, + 'orientation': 'portraitUp' + }), + ]); + }); + + test('Should unlock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'unlockCaptureOrientation': null}, + ); + + // Act + await camera.unlockCaptureOrientation(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('unlockCaptureOrientation', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should pause the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pausePreview': null}, + ); + + // Act + await camera.pausePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pausePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should resume the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumePreview': null}, + ); + + // Act + await camera.resumePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should start streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + ]); + + subscription.cancel(); + }); + + test('Should stop streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + subscription.cancel(); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null), + ]); + }); + }); + }); +} diff --git a/packages/camera/camera_avfoundation/test/method_channel_mock.dart b/packages/camera/camera_avfoundation/test/method_channel_mock.dart new file mode 100644 index 000000000000..413c10633cc1 --- /dev/null +++ b/packages/camera/camera_avfoundation/test/method_channel_mock.dart @@ -0,0 +1,39 @@ +// 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'; + +class MethodChannelMock { + MethodChannelMock({ + required String channelName, + this.delay, + required this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + final Duration? delay; + final MethodChannel methodChannel; + final Map methods; + final List log = []; + + Future _handler(MethodCall methodCall) async { + log.add(methodCall); + + if (!methods.containsKey(methodCall.method)) { + throw MissingPluginException('No implementation found for method ' + '${methodCall.method} on channel ${methodChannel.name}'); + } + + return Future.delayed(delay ?? Duration.zero, () { + final dynamic result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} diff --git a/packages/camera/camera_avfoundation/test/type_conversion_test.dart b/packages/camera/camera_avfoundation/test/type_conversion_test.dart new file mode 100644 index 000000000000..4818074ec767 --- /dev/null +++ b/packages/camera/camera_avfoundation/test/type_conversion_test.dart @@ -0,0 +1,87 @@ +// 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. + +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/type_conversion.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('CameraImageData can be created', () { + final CameraImageData cameraImage = + cameraImageFromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'lensAperture': 1.8, + 'sensorExposureTime': 9991324, + 'sensorSensitivity': 92.0, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.height, 1); + expect(cameraImage.width, 4); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + expect(cameraImage.planes.length, 1); + }); + + test('CameraImageData has ImageFormatGroup.yuv420 for iOS', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final CameraImageData cameraImage = + cameraImageFromPlatformData({ + 'format': 875704438, + 'height': 1, + 'width': 4, + 'lensAperture': 1.8, + 'sensorExposureTime': 9991324, + 'sensorSensitivity': 92.0, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); + + test('CameraImageData has ImageFormatGroup.yuv420 for Android', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + final CameraImageData cameraImage = + cameraImageFromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'lensAperture': 1.8, + 'sensorExposureTime': 9991324, + 'sensorSensitivity': 92.0, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); +} diff --git a/packages/camera/camera_avfoundation/test/utils_test.dart b/packages/camera/camera_avfoundation/test/utils_test.dart new file mode 100644 index 000000000000..0e4171d73aa6 --- /dev/null +++ b/packages/camera/camera_avfoundation/test/utils_test.dart @@ -0,0 +1,60 @@ +// 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:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Utility methods', () { + test( + 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', + () { + expect( + parseCameraLensDirection('back'), + CameraLensDirection.back, + ); + expect( + parseCameraLensDirection('front'), + CameraLensDirection.front, + ); + expect( + parseCameraLensDirection('external'), + CameraLensDirection.external, + ); + }); + + test( + 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', + () { + expect( + () => parseCameraLensDirection('test'), + throwsA(isArgumentError), + ); + }); + + test('serializeDeviceOrientation() should serialize correctly', () { + expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), + 'portraitUp'); + expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), + 'portraitDown'); + expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), + 'landscapeRight'); + expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), + 'landscapeLeft'); + }); + + test('deserializeDeviceOrientation() should deserialize correctly', () { + expect(deserializeDeviceOrientation('portraitUp'), + DeviceOrientation.portraitUp); + expect(deserializeDeviceOrientation('portraitDown'), + DeviceOrientation.portraitDown); + expect(deserializeDeviceOrientation('landscapeRight'), + DeviceOrientation.landscapeRight); + expect(deserializeDeviceOrientation('landscapeLeft'), + DeviceOrientation.landscapeLeft); + }); + }); +} From 4ab4362f012742b1232ce3aa9f64d7416c014d45 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 12:28:32 -0400 Subject: [PATCH 2/9] Renaming, fixing up imports --- .../camera_android/lib/camera_android.dart | 5 ++++ ...hannel_camera.dart => android_camera.dart} | 9 +++--- .../lib/src/type_conversion.dart | 3 +- .../test/android_camera_test.dart} | 30 +++++++++---------- .../lib/camera_avfoundation.dart | 5 ++++ ...l_camera.dart => avfoundation_camera.dart} | 8 ++--- .../lib/src/type_conversion.dart | 3 +- .../test/avfoundation_camera_test.dart} | 30 +++++++++---------- 8 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 packages/camera/camera_android/lib/camera_android.dart rename packages/camera/camera_android/lib/src/{method_channel_camera.dart => android_camera.dart} (98%) rename packages/camera/{camera_avfoundation/test/method_channel_camera_test.dart => camera_android/test/android_camera_test.dart} (97%) create mode 100644 packages/camera/camera_avfoundation/lib/camera_avfoundation.dart rename packages/camera/camera_avfoundation/lib/src/{method_channel_camera.dart => avfoundation_camera.dart} (98%) rename packages/camera/{camera_android/test/method_channel_camera_test.dart => camera_avfoundation/test/avfoundation_camera_test.dart} (97%) diff --git a/packages/camera/camera_android/lib/camera_android.dart b/packages/camera/camera_android/lib/camera_android.dart new file mode 100644 index 000000000000..93e3e17290c0 --- /dev/null +++ b/packages/camera/camera_android/lib/camera_android.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/android_camera.dart'; diff --git a/packages/camera/camera_android/lib/src/method_channel_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart similarity index 98% rename from packages/camera/camera_android/lib/src/method_channel_camera.dart rename to packages/camera/camera_android/lib/src/android_camera.dart index babef144b086..0ab2273c53cf 100644 --- a/packages/camera/camera_android/lib/src/method_channel_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -6,20 +6,20 @@ import 'dart:async'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'type_conversion.dart'; +import 'utils.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); -/// An implementation of [CameraPlatform] that uses method channels. -class MethodChannelCamera extends CameraPlatform { +/// The Android implementation of [CameraPlatform] that uses method channels. +class AndroidCamera extends CameraPlatform { /// Construct a new method channel camera instance. - MethodChannelCamera() { + AndroidCamera() { const MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/device'); channel.setMethodCallHandler( @@ -522,6 +522,7 @@ class MethodChannelCamera extends CameraPlatform { /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. + @visibleForTesting Future handleDeviceMethodCall(MethodCall call) async { switch (call.method) { case 'orientation_changed': diff --git a/packages/camera/camera_android/lib/src/type_conversion.dart b/packages/camera/camera_android/lib/src/type_conversion.dart index 8b360077305c..fc33032e2fa1 100644 --- a/packages/camera/camera_android/lib/src/type_conversion.dart +++ b/packages/camera/camera_android/lib/src/type_conversion.dart @@ -6,10 +6,9 @@ // ignore: unnecessary_import import 'dart:typed_data'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; -import '../types/types.dart'; - /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. CameraImageData cameraImageFromPlatformData(Map data) { diff --git a/packages/camera/camera_avfoundation/test/method_channel_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart similarity index 97% rename from packages/camera/camera_avfoundation/test/method_channel_camera_test.dart rename to packages/camera/camera_android/test/android_camera_test.dart index d096f0012c86..88edeaca87cf 100644 --- a/packages/camera/camera_avfoundation/test/method_channel_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -6,19 +6,19 @@ import 'dart:async'; import 'dart:math'; import 'package:async/async.dart'; +import 'package:camera_android/src/android_camera.dart'; +import 'package:camera_android/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; -import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../utils/method_channel_mock.dart'; +import 'method_channel_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$MethodChannelCamera', () { + group('AndroidCamera', () { group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange @@ -30,7 +30,7 @@ void main() { 'imageFormatGroup': 'unknown', } }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); // Act final int cameraId = await camera.createCamera( @@ -67,7 +67,7 @@ void main() { message: 'Mock error message used during testing.', ) }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); // Act expect( @@ -101,7 +101,7 @@ void main() { message: 'Mock error message used during testing.', ) }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); // Act expect( @@ -136,7 +136,7 @@ void main() { ) }, ); - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); // Act expect( @@ -166,7 +166,7 @@ void main() { }, 'initialize': null }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -213,7 +213,7 @@ void main() { 'dispose': {'cameraId': 1} }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -251,7 +251,7 @@ void main() { }); group('Event Tests', () { - late MethodChannelCamera camera; + late AndroidCamera camera; late int cameraId; setUp(() async { MethodChannelMock( @@ -261,7 +261,7 @@ void main() { 'initialize': null }, ); - camera = MethodChannelCamera(); + camera = AndroidCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -420,7 +420,7 @@ void main() { }); group('Function Tests', () { - late MethodChannelCamera camera; + late AndroidCamera camera; late int cameraId; setUp(() async { @@ -431,7 +431,7 @@ void main() { 'initialize': null }, ); - camera = MethodChannelCamera(); + camera = AndroidCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -882,7 +882,7 @@ void main() { test('Should throw MissingPluginException when handling unknown method', () { - final MethodChannelCamera camera = MethodChannelCamera(); + final AndroidCamera camera = AndroidCamera(); expect( () => camera.handleCameraMethodCall( diff --git a/packages/camera/camera_avfoundation/lib/camera_avfoundation.dart b/packages/camera/camera_avfoundation/lib/camera_avfoundation.dart new file mode 100644 index 000000000000..e07a440e84f1 --- /dev/null +++ b/packages/camera/camera_avfoundation/lib/camera_avfoundation.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/avfoundation_camera.dart'; diff --git a/packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart similarity index 98% rename from packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart rename to packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index babef144b086..1e9247b5e1d8 100644 --- a/packages/camera/camera_avfoundation/lib/src/method_channel_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -6,20 +6,20 @@ import 'dart:async'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'type_conversion.dart'; +import 'utils.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); -/// An implementation of [CameraPlatform] that uses method channels. -class MethodChannelCamera extends CameraPlatform { +/// An iOS implementation of [CameraPlatform] based on AVFoundation. +class AVFoundationCamera extends CameraPlatform { /// Construct a new method channel camera instance. - MethodChannelCamera() { + AVFoundationCamera() { const MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/device'); channel.setMethodCallHandler( diff --git a/packages/camera/camera_avfoundation/lib/src/type_conversion.dart b/packages/camera/camera_avfoundation/lib/src/type_conversion.dart index 8b360077305c..fc33032e2fa1 100644 --- a/packages/camera/camera_avfoundation/lib/src/type_conversion.dart +++ b/packages/camera/camera_avfoundation/lib/src/type_conversion.dart @@ -6,10 +6,9 @@ // ignore: unnecessary_import import 'dart:typed_data'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; -import '../types/types.dart'; - /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. CameraImageData cameraImageFromPlatformData(Map data) { diff --git a/packages/camera/camera_android/test/method_channel_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart similarity index 97% rename from packages/camera/camera_android/test/method_channel_camera_test.dart rename to packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index d096f0012c86..31f25e963da9 100644 --- a/packages/camera/camera_android/test/method_channel_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -6,19 +6,19 @@ import 'dart:async'; import 'dart:math'; import 'package:async/async.dart'; +import 'package:camera_avfoundation/src/avfoundation_camera.dart'; +import 'package:camera_avfoundation/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; -import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../utils/method_channel_mock.dart'; +import 'method_channel_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$MethodChannelCamera', () { + group('AVFoundationCamera', () { group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange @@ -30,7 +30,7 @@ void main() { 'imageFormatGroup': 'unknown', } }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); // Act final int cameraId = await camera.createCamera( @@ -67,7 +67,7 @@ void main() { message: 'Mock error message used during testing.', ) }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( @@ -101,7 +101,7 @@ void main() { message: 'Mock error message used during testing.', ) }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( @@ -136,7 +136,7 @@ void main() { ) }, ); - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( @@ -166,7 +166,7 @@ void main() { }, 'initialize': null }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -213,7 +213,7 @@ void main() { 'dispose': {'cameraId': 1} }); - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -251,7 +251,7 @@ void main() { }); group('Event Tests', () { - late MethodChannelCamera camera; + late AVFoundationCamera camera; late int cameraId; setUp(() async { MethodChannelMock( @@ -261,7 +261,7 @@ void main() { 'initialize': null }, ); - camera = MethodChannelCamera(); + camera = AVFoundationCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -420,7 +420,7 @@ void main() { }); group('Function Tests', () { - late MethodChannelCamera camera; + late AVFoundationCamera camera; late int cameraId; setUp(() async { @@ -431,7 +431,7 @@ void main() { 'initialize': null }, ); - camera = MethodChannelCamera(); + camera = AVFoundationCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -882,7 +882,7 @@ void main() { test('Should throw MissingPluginException when handling unknown method', () { - final MethodChannelCamera camera = MethodChannelCamera(); + final AVFoundationCamera camera = AVFoundationCamera(); expect( () => camera.handleCameraMethodCall( From 6f87d8e8ad91aa266cdba98aafaa84dfc988525c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 12:29:50 -0400 Subject: [PATCH 3/9] Remove unnecessary test group (mostly whitespace change) --- .../test/android_camera_test.dart | 1915 ++++++++--------- .../test/avfoundation_camera_test.dart | 1915 ++++++++--------- 2 files changed, 1900 insertions(+), 1930 deletions(-) diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index 88edeaca87cf..de8abca07afe 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -18,212 +18,428 @@ import 'method_channel_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('AndroidCamera', () { - group('Creation, Initialization & Disposal Tests', () { - test('Should send creation data and receive back a camera id', () async { - // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - } - }); - final AndroidCamera camera = AndroidCamera(); + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + } + }); + final AndroidCamera camera = AndroidCamera(); - // Act - final int cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0), - ResolutionPreset.high, - ); + // Act + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0), + ResolutionPreset.high, + ); - // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'high', - 'enableAudio': false - }, - ), - ]); - expect(cameraId, 1); - }); + // Assert + expect(cameraMockChannel.log, [ + isMethodCall( + 'create', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': false + }, + ), + ]); + expect(cameraId, 1); + }); - test( - 'Should throw CameraException when create throws a PlatformException', - () { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); - final AndroidCamera camera = AndroidCamera(); + test('Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final AndroidCamera camera = AndroidCamera(); - // Act - expect( - () => camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, ), - throwsA( - isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test('Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final AndroidCamera camera = AndroidCamera(); + + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, ), - ); - }); + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); - test( - 'Should throw CameraException when create throws a PlatformException', - () { + test( + 'Should throw CameraException when initialize throws a PlatformException', + () { // Arrange MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }, + ); final AndroidCamera camera = AndroidCamera(); // Act expect( - () => camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ), + () => camera.initializeCamera(0), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), + .having( + (CameraException e) => e.description, + 'description', + 'Mock error message used during testing.', + ), ), ); - }); + }, + ); - test( - 'Should throw CameraException when initialize throws a PlatformException', - () { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'initialize': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) + test('Should send initialization data', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', }, - ); - final AndroidCamera camera = AndroidCamera(); - - // Act - expect( - () => camera.initializeCamera(0), - throwsA( - isA() - .having((CameraException e) => e.code, 'code', - 'TESTING_ERROR_CODE') - .having( - (CameraException e) => e.description, - 'description', - 'Mock error message used during testing.', - ), - ), - ); + 'initialize': null + }); + final AndroidCamera camera = AndroidCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + + // Act + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null, + 'dispose': {'cameraId': 1} + }); + + final AndroidCamera camera = AndroidCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Act + await camera.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + anything, + isMethodCall( + 'dispose', + arguments: {'cameraId': 1}, + ), + ]); + }); + }); + + group('Event Tests', () { + late AndroidCamera camera; + late int cameraId; + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null }, ); + camera = AndroidCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + }); - test('Should send initialization data', () async { - // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - 'initialize': null - }); - final AndroidCamera camera = AndroidCamera(); - final int cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); + test('Should receive initialized event', () async { + // Act + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraInitializedEvent event = CameraInitializedEvent( + cameraId, + 3840, + 2160, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ); + await camera.handleCameraMethodCall( + MethodCall('initialized', event.toJson()), cameraId); - // Act - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add(CameraInitializedEvent( - cameraId, - 1920, - 1080, - ExposureMode.auto, - true, - FocusMode.auto, - true, - )); - await initializeFuture; - - // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - isMethodCall( - 'initialize', - arguments: { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - ), - ]); - }); + // Assert + expect(await streamQueue.next, event); - test('Should send a disposal call on dispose', () async { - // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null, - 'dispose': {'cameraId': 1} - }); + // Clean up + await streamQueue.cancel(); + }); - final AndroidCamera camera = AndroidCamera(); - final int cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add(CameraInitializedEvent( + test('Should receive resolution changes', () async { + // Act + final Stream resolutionStream = + camera.onCameraResolutionChanged(cameraId); + final StreamQueue streamQueue = + StreamQueue(resolutionStream); + + // Emit test events + final CameraResolutionChangedEvent fhdEvent = + CameraResolutionChangedEvent(cameraId, 1920, 1080); + final CameraResolutionChangedEvent uhdEvent = + CameraResolutionChangedEvent(cameraId, 3840, 2160); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + camera.onCameraClosing(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraClosingEvent event = CameraClosingEvent(cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final Stream errorStream = + camera.onCameraError(cameraId); + final StreamQueue streamQueue = + StreamQueue(errorStream); + + // Emit test events + final CameraErrorEvent event = + CameraErrorEvent(cameraId, 'Error Description'); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive device orientation change events', () async { + // Act + final Stream eventStream = + camera.onDeviceOrientationChanged(); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + const DeviceOrientationChangedEvent event = + DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + + group('Function Tests', () { + late AndroidCamera camera; + late int cameraId; + + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }, + ); + camera = AndroidCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add( + CameraInitializedEvent( cameraId, 1920, 1080, @@ -231,859 +447,628 @@ void main() { true, FocusMode.auto, true, - )); - await initializeFuture; + ), + ); + await initializeFuture; + }); - // Act - await camera.dispose(cameraId); - - // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - anything, - isMethodCall( - 'dispose', - arguments: {'cameraId': 1}, - ), - ]); - }); + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Test 1', + 'lensFacing': 'front', + 'sensorOrientation': 1 + }, + { + 'name': 'Test 2', + 'lensFacing': 'back', + 'sensorOrientation': 2 + } + ]; + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'availableCameras': returnData}, + ); + + // Act + final List cameras = await camera.availableCameras(); + + // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name']! as String, + lensDirection: + parseCameraLensDirection(returnData[i]['lensFacing']! as String), + sensorOrientation: returnData[i]['sensorOrientation']! as int, + ); + expect(cameras[i], cameraDescription); + } }); - group('Event Tests', () { - late AndroidCamera camera; - late int cameraId; - setUp(() async { - MethodChannelMock( + test( + 'Should throw CameraException when availableCameras throws a PlatformException', + () { + // Arrange + MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = AndroidCamera(); - cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add(CameraInitializedEvent( - cameraId, - 1920, - 1080, - ExposureMode.auto, - true, - FocusMode.auto, - true, - )); - await initializeFuture; - }); + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + + // Act + expect( + camera.availableCameras, + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); - test('Should receive initialized event', () async { - // Act - final Stream eventStream = - camera.onCameraInitialized(cameraId); - final StreamQueue streamQueue = - StreamQueue(eventStream); + test('Should take a picture and return an XFile instance', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'takePicture': '/test/path.jpg'}); + + // Act + final XFile file = await camera.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.jpg'); + }); - // Emit test events - final CameraInitializedEvent event = CameraInitializedEvent( - cameraId, - 3840, - 2160, - ExposureMode.auto, - true, - FocusMode.auto, - true, - ); - await camera.handleCameraMethodCall( - MethodCall('initialized', event.toJson()), cameraId); + test('Should prepare for video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'prepareForVideoRecording': null}, + ); - // Assert - expect(await streamQueue.next, event); + // Act + await camera.prepareForVideoRecording(); - // Clean up - await streamQueue.cancel(); - }); + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); - test('Should receive resolution changes', () async { - // Act - final Stream resolutionStream = - camera.onCameraResolutionChanged(cameraId); - final StreamQueue streamQueue = - StreamQueue(resolutionStream); - - // Emit test events - final CameraResolutionChangedEvent fhdEvent = - CameraResolutionChangedEvent(cameraId, 1920, 1080); - final CameraResolutionChangedEvent uhdEvent = - CameraResolutionChangedEvent(cameraId, 3840, 2160); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, fhdEvent); - expect(await streamQueue.next, uhdEvent); - expect(await streamQueue.next, fhdEvent); - expect(await streamQueue.next, uhdEvent); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive camera closing events', () async { - // Act - final Stream eventStream = - camera.onCameraClosing(cameraId); - final StreamQueue streamQueue = - StreamQueue(eventStream); - - // Emit test events - final CameraClosingEvent event = CameraClosingEvent(cameraId); - await camera.handleCameraMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive camera error events', () async { - // Act - final Stream errorStream = - camera.onCameraError(cameraId); - final StreamQueue streamQueue = - StreamQueue(errorStream); - - // Emit test events - final CameraErrorEvent event = - CameraErrorEvent(cameraId, 'Error Description'); - await camera.handleCameraMethodCall( - MethodCall('error', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('error', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('error', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive device orientation change events', () async { - // Act - final Stream eventStream = - camera.onDeviceOrientationChanged(); - final StreamQueue streamQueue = - StreamQueue(eventStream); - - // Emit test events - const DeviceOrientationChangedEvent event = - DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); - await camera.handleDeviceMethodCall( - MethodCall('orientation_changed', event.toJson())); - await camera.handleDeviceMethodCall( - MethodCall('orientation_changed', event.toJson())); - await camera.handleDeviceMethodCall( - MethodCall('orientation_changed', event.toJson())); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); + test('Should start recording a video', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': null, + }), + ]); }); - group('Function Tests', () { - late AndroidCamera camera; - late int cameraId; + test('Should pass maxVideoDuration when starting recording a video', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); - setUp(() async { - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = AndroidCamera(); - cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add( - CameraInitializedEvent( - cameraId, - 1920, - 1080, - ExposureMode.auto, - true, - FocusMode.auto, - true, - ), - ); - await initializeFuture; - }); + // Act + await camera.startVideoRecording( + cameraId, + maxVideoDuration: const Duration(seconds: 10), + ); - test('Should fetch CameraDescription instances for available cameras', - () async { - // Arrange - final List returnData = [ - { - 'name': 'Test 1', - 'lensFacing': 'front', - 'sensorOrientation': 1 - }, - { - 'name': 'Test 2', - 'lensFacing': 'back', - 'sensorOrientation': 2 - } - ]; - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'availableCameras': returnData}, - ); + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': 10000 + }), + ]); + }); - // Act - final List cameras = await camera.availableCameras(); - - // Assert - expect(channel.log, [ - isMethodCall('availableCameras', arguments: null), - ]); - expect(cameras.length, returnData.length); - for (int i = 0; i < returnData.length; i++) { - final CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name']! as String, - lensDirection: parseCameraLensDirection( - returnData[i]['lensFacing']! as String), - sensorOrientation: returnData[i]['sensorOrientation']! as int, - ); - expect(cameras[i], cameraDescription); - } - }); + test('Should stop a video recording and return the file', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': '/test/path.mp4'}, + ); - test( - 'Should throw CameraException when availableCameras throws a PlatformException', - () { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'availableCameras': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + // Act + final XFile file = await camera.stopVideoRecording(cameraId); - // Act - expect( - camera.availableCameras, - throwsA( - isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), - ), - ); - }); + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.mp4'); + }); - test('Should take a picture and return an XFile instance', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'takePicture': '/test/path.jpg'}); + test('Should pause a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pauseVideoRecording': null}, + ); - // Act - final XFile file = await camera.takePicture(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('takePicture', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.jpg'); - }); - - test('Should prepare for video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'prepareForVideoRecording': null}, - ); + // Act + await camera.pauseVideoRecording(cameraId); - // Act - await camera.prepareForVideoRecording(); + // Assert + expect(channel.log, [ + isMethodCall('pauseVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Assert - expect(channel.log, [ - isMethodCall('prepareForVideoRecording', arguments: null), - ]); - }); + test('Should resume a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumeVideoRecording': null}, + ); - test('Should start recording a video', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': null}, - ); + // Act + await camera.resumeVideoRecording(cameraId); - // Act - await camera.startVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': null, - }), - ]); - }); - - test('Should pass maxVideoDuration when starting recording a video', - () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': null}, - ); + // Assert + expect(channel.log, [ + isMethodCall('resumeVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - await camera.startVideoRecording( - cameraId, - maxVideoDuration: const Duration(seconds: 10), - ); + test('Should set the flash mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFlashMode': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': 10000 - }), - ]); - }); + // Act + await camera.setFlashMode(cameraId, FlashMode.torch); + await camera.setFlashMode(cameraId, FlashMode.always); + await camera.setFlashMode(cameraId, FlashMode.auto); + await camera.setFlashMode(cameraId, FlashMode.off); + + // Assert + expect(channel.log, [ + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'torch' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'always' + }), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'off'}), + ]); + }); - test('Should stop a video recording and return the file', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'stopVideoRecording': '/test/path.mp4'}, - ); + test('Should set the exposure mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureMode': null}, + ); - // Act - final XFile file = await camera.stopVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('stopVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.mp4'); - }); - - test('Should pause a video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'pauseVideoRecording': null}, - ); + // Act + await camera.setExposureMode(cameraId, ExposureMode.auto); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setExposureMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setExposureMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); - // Act - await camera.pauseVideoRecording(cameraId); + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposurePoint': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('pauseVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); + // Act + await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); + await camera.setExposurePoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); - test('Should resume a video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'resumeVideoRecording': null}, - ); + test('Should get the min exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinExposureOffset': 2.0}, + ); - // Act - await camera.resumeVideoRecording(cameraId); + // Act + final double minExposureOffset = + await camera.getMinExposureOffset(cameraId); + + // Assert + expect(minExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMinExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Assert - expect(channel.log, [ - isMethodCall('resumeVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); + test('Should get the max exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxExposureOffset': 2.0}, + ); - test('Should set the flash mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setFlashMode': null}, - ); + // Act + final double maxExposureOffset = + await camera.getMaxExposureOffset(cameraId); + + // Assert + expect(maxExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMaxExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - await camera.setFlashMode(cameraId, FlashMode.torch); - await camera.setFlashMode(cameraId, FlashMode.always); - await camera.setFlashMode(cameraId, FlashMode.auto); - await camera.setFlashMode(cameraId, FlashMode.off); - - // Assert - expect(channel.log, [ - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'torch' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'always' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'auto' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'off' - }), - ]); - }); - - test('Should set the exposure mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setExposureMode': null}, - ); + test('Should get the exposure offset step size', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getExposureOffsetStepSize': 0.25}, + ); - // Act - await camera.setExposureMode(cameraId, ExposureMode.auto); - await camera.setExposureMode(cameraId, ExposureMode.locked); - - // Assert - expect(channel.log, [ - isMethodCall('setExposureMode', arguments: { - 'cameraId': cameraId, - 'mode': 'auto' - }), - isMethodCall('setExposureMode', arguments: { - 'cameraId': cameraId, - 'mode': 'locked' - }), - ]); - }); - - test('Should set the exposure point', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setExposurePoint': null}, - ); + // Act + final double stepSize = await camera.getExposureOffsetStepSize(cameraId); - // Act - await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); - await camera.setExposurePoint(cameraId, null); - - // Assert - expect(channel.log, [ - isMethodCall('setExposurePoint', arguments: { - 'cameraId': cameraId, - 'x': 0.5, - 'y': 0.5, - 'reset': false - }), - isMethodCall('setExposurePoint', arguments: { - 'cameraId': cameraId, - 'x': null, - 'y': null, - 'reset': true - }), - ]); - }); - - test('Should get the min exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMinExposureOffset': 2.0}, - ); + // Assert + expect(stepSize, 0.25); + expect(channel.log, [ + isMethodCall('getExposureOffsetStepSize', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - final double minExposureOffset = - await camera.getMinExposureOffset(cameraId); - - // Assert - expect(minExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMinExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should get the max exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMaxExposureOffset': 2.0}, - ); + test('Should set the exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureOffset': 0.6}, + ); - // Act - final double maxExposureOffset = - await camera.getMaxExposureOffset(cameraId); - - // Assert - expect(maxExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMaxExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should get the exposure offset step size', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getExposureOffsetStepSize': 0.25}, - ); + // Act + final double actualOffset = await camera.setExposureOffset(cameraId, 0.5); + + // Assert + expect(actualOffset, 0.6); + expect(channel.log, [ + isMethodCall('setExposureOffset', arguments: { + 'cameraId': cameraId, + 'offset': 0.5, + }), + ]); + }); - // Act - final double stepSize = - await camera.getExposureOffsetStepSize(cameraId); - - // Assert - expect(stepSize, 0.25); - expect(channel.log, [ - isMethodCall('getExposureOffsetStepSize', - arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should set the exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setExposureOffset': 0.6}, - ); + test('Should set the focus mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusMode': null}, + ); - // Act - final double actualOffset = - await camera.setExposureOffset(cameraId, 0.5); - - // Assert - expect(actualOffset, 0.6); - expect(channel.log, [ - isMethodCall('setExposureOffset', arguments: { - 'cameraId': cameraId, - 'offset': 0.5, - }), - ]); - }); - - test('Should set the focus mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setFocusMode': null}, - ); + // Act + await camera.setFocusMode(cameraId, FocusMode.auto); + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFocusMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); - // Act - await camera.setFocusMode(cameraId, FocusMode.auto); - await camera.setFocusMode(cameraId, FocusMode.locked); - - // Assert - expect(channel.log, [ - isMethodCall('setFocusMode', arguments: { - 'cameraId': cameraId, - 'mode': 'auto' - }), - isMethodCall('setFocusMode', arguments: { - 'cameraId': cameraId, - 'mode': 'locked' - }), - ]); - }); - - test('Should set the exposure point', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setFocusPoint': null}, - ); + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusPoint': null}, + ); - // Act - await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); - await camera.setFocusPoint(cameraId, null); - - // Assert - expect(channel.log, [ - isMethodCall('setFocusPoint', arguments: { - 'cameraId': cameraId, - 'x': 0.5, - 'y': 0.5, - 'reset': false - }), - isMethodCall('setFocusPoint', arguments: { - 'cameraId': cameraId, - 'x': null, - 'y': null, - 'reset': true - }), - ]); - }); - - test('Should build a texture widget as preview widget', () async { - // Act - final Widget widget = camera.buildPreview(cameraId); + // Act + await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); + await camera.setFocusPoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); - // Act - expect(widget is Texture, isTrue); - expect((widget as Texture).textureId, cameraId); - }); + test('Should build a texture widget as preview widget', () async { + // Act + final Widget widget = camera.buildPreview(cameraId); - test('Should throw MissingPluginException when handling unknown method', - () { - final AndroidCamera camera = AndroidCamera(); + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); - expect( - () => camera.handleCameraMethodCall( - const MethodCall('unknown_method'), 1), - throwsA(isA())); - }); + test('Should throw MissingPluginException when handling unknown method', + () { + final AndroidCamera camera = AndroidCamera(); - test('Should get the max zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMaxZoomLevel': 10.0}, - ); + expect( + () => camera.handleCameraMethodCall( + const MethodCall('unknown_method'), 1), + throwsA(isA())); + }); - // Act - final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); - - // Assert - expect(maxZoomLevel, 10.0); - expect(channel.log, [ - isMethodCall('getMaxZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should get the min zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMinZoomLevel': 1.0}, - ); + test('Should get the max zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxZoomLevel': 10.0}, + ); - // Act - final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); - - // Assert - expect(maxZoomLevel, 1.0); - expect(channel.log, [ - isMethodCall('getMinZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should set the zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setZoomLevel': null}, - ); + // Act + final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); - // Act - await camera.setZoomLevel(cameraId, 2.0); + // Assert + expect(maxZoomLevel, 10.0); + expect(channel.log, [ + isMethodCall('getMaxZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Assert - expect(channel.log, [ - isMethodCall('setZoomLevel', - arguments: {'cameraId': cameraId, 'zoom': 2.0}), - ]); - }); + test('Should get the min zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinZoomLevel': 1.0}, + ); - test('Should throw CameraException when illegal zoom level is supplied', - () async { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'setZoomLevel': PlatformException( - code: 'ZOOM_ERROR', - message: 'Illegal zoom error', - details: null, - ) - }, - ); + // Act + final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); - // Act & assert - expect( - () => camera.setZoomLevel(cameraId, -1.0), - throwsA(isA() - .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') - .having((CameraException e) => e.description, 'description', - 'Illegal zoom error'))); - }); - - test('Should lock the capture orientation', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'lockCaptureOrientation': null}, - ); + // Assert + expect(maxZoomLevel, 1.0); + expect(channel.log, [ + isMethodCall('getMinZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - await camera.lockCaptureOrientation( - cameraId, DeviceOrientation.portraitUp); - - // Assert - expect(channel.log, [ - isMethodCall('lockCaptureOrientation', arguments: { - 'cameraId': cameraId, - 'orientation': 'portraitUp' - }), - ]); - }); - - test('Should unlock the capture orientation', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'unlockCaptureOrientation': null}, - ); + test('Should set the zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setZoomLevel': null}, + ); - // Act - await camera.unlockCaptureOrientation(cameraId); + // Act + await camera.setZoomLevel(cameraId, 2.0); - // Assert - expect(channel.log, [ - isMethodCall('unlockCaptureOrientation', - arguments: {'cameraId': cameraId}), - ]); - }); + // Assert + expect(channel.log, [ + isMethodCall('setZoomLevel', + arguments: {'cameraId': cameraId, 'zoom': 2.0}), + ]); + }); - test('Should pause the camera preview', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'pausePreview': null}, - ); + test('Should throw CameraException when illegal zoom level is supplied', + () async { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'setZoomLevel': PlatformException( + code: 'ZOOM_ERROR', + message: 'Illegal zoom error', + details: null, + ) + }, + ); - // Act - await camera.pausePreview(cameraId); + // Act & assert + expect( + () => camera.setZoomLevel(cameraId, -1.0), + throwsA(isA() + .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') + .having((CameraException e) => e.description, 'description', + 'Illegal zoom error'))); + }); - // Assert - expect(channel.log, [ - isMethodCall('pausePreview', - arguments: {'cameraId': cameraId}), - ]); - }); + test('Should lock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'lockCaptureOrientation': null}, + ); - test('Should resume the camera preview', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'resumePreview': null}, - ); + // Act + await camera.lockCaptureOrientation( + cameraId, DeviceOrientation.portraitUp); + + // Assert + expect(channel.log, [ + isMethodCall('lockCaptureOrientation', arguments: { + 'cameraId': cameraId, + 'orientation': 'portraitUp' + }), + ]); + }); - // Act - await camera.resumePreview(cameraId); + test('Should unlock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'unlockCaptureOrientation': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('resumePreview', - arguments: {'cameraId': cameraId}), - ]); - }); + // Act + await camera.unlockCaptureOrientation(cameraId); - test('Should start streaming', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); + // Assert + expect(channel.log, [ + isMethodCall('unlockCaptureOrientation', + arguments: {'cameraId': cameraId}), + ]); + }); - // Act - final StreamSubscription subscription = camera - .onStreamedFrameAvailable(cameraId) - .listen((CameraImageData imageData) {}); + test('Should pause the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pausePreview': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - ]); + // Act + await camera.pausePreview(cameraId); - subscription.cancel(); - }); + // Assert + expect(channel.log, [ + isMethodCall('pausePreview', + arguments: {'cameraId': cameraId}), + ]); + }); - test('Should stop streaming', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); + test('Should resume the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumePreview': null}, + ); - // Act - final StreamSubscription subscription = camera - .onStreamedFrameAvailable(cameraId) - .listen((CameraImageData imageData) {}); - subscription.cancel(); - - // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null), - ]); - }); + // Act + await camera.resumePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should start streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + ]); + + subscription.cancel(); + }); + + test('Should stop streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + subscription.cancel(); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null), + ]); }); }); } diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 31f25e963da9..28c50ecc1a36 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -18,212 +18,428 @@ import 'method_channel_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('AVFoundationCamera', () { - group('Creation, Initialization & Disposal Tests', () { - test('Should send creation data and receive back a camera id', () async { - // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - } - }); - final AVFoundationCamera camera = AVFoundationCamera(); + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + } + }); + final AVFoundationCamera camera = AVFoundationCamera(); - // Act - final int cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0), - ResolutionPreset.high, - ); + // Act + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0), + ResolutionPreset.high, + ); - // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'high', - 'enableAudio': false - }, - ), - ]); - expect(cameraId, 1); - }); + // Assert + expect(cameraMockChannel.log, [ + isMethodCall( + 'create', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': false + }, + ), + ]); + expect(cameraId, 1); + }); - test( - 'Should throw CameraException when create throws a PlatformException', - () { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); - final AVFoundationCamera camera = AVFoundationCamera(); + test('Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final AVFoundationCamera camera = AVFoundationCamera(); - // Act - expect( - () => camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, ), - throwsA( - isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test('Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final AVFoundationCamera camera = AVFoundationCamera(); + + // Act + expect( + () => camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, ), - ); - }); + ResolutionPreset.high, + ), + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); - test( - 'Should throw CameraException when create throws a PlatformException', - () { + test( + 'Should throw CameraException when initialize throws a PlatformException', + () { // Arrange MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }, + ); final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( - () => camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ), + () => camera.initializeCamera(0), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), + .having( + (CameraException e) => e.description, + 'description', + 'Mock error message used during testing.', + ), ), ); - }); + }, + ); - test( - 'Should throw CameraException when initialize throws a PlatformException', - () { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'initialize': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) + test('Should send initialization data', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', }, - ); - final AVFoundationCamera camera = AVFoundationCamera(); - - // Act - expect( - () => camera.initializeCamera(0), - throwsA( - isA() - .having((CameraException e) => e.code, 'code', - 'TESTING_ERROR_CODE') - .having( - (CameraException e) => e.description, - 'description', - 'Mock error message used during testing.', - ), - ), - ); + 'initialize': null + }); + final AVFoundationCamera camera = AVFoundationCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + + // Act + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: { + 'cameraId': 1, + 'imageFormatGroup': 'unknown', + }, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + final MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null, + 'dispose': {'cameraId': 1} + }); + + final AVFoundationCamera camera = AVFoundationCamera(); + final int cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + + // Act + await camera.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + anything, + isMethodCall( + 'dispose', + arguments: {'cameraId': 1}, + ), + ]); + }); + }); + + group('Event Tests', () { + late AVFoundationCamera camera; + late int cameraId; + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null }, ); + camera = AVFoundationCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + FocusMode.auto, + true, + )); + await initializeFuture; + }); - test('Should send initialization data', () async { - // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - 'initialize': null - }); - final AVFoundationCamera camera = AVFoundationCamera(); - final int cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); + test('Should receive initialized event', () async { + // Act + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraInitializedEvent event = CameraInitializedEvent( + cameraId, + 3840, + 2160, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ); + await camera.handleCameraMethodCall( + MethodCall('initialized', event.toJson()), cameraId); - // Act - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add(CameraInitializedEvent( - cameraId, - 1920, - 1080, - ExposureMode.auto, - true, - FocusMode.auto, - true, - )); - await initializeFuture; - - // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - isMethodCall( - 'initialize', - arguments: { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - ), - ]); - }); + // Assert + expect(await streamQueue.next, event); - test('Should send a disposal call on dispose', () async { - // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null, - 'dispose': {'cameraId': 1} - }); + // Clean up + await streamQueue.cancel(); + }); - final AVFoundationCamera camera = AVFoundationCamera(); - final int cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add(CameraInitializedEvent( + test('Should receive resolution changes', () async { + // Act + final Stream resolutionStream = + camera.onCameraResolutionChanged(cameraId); + final StreamQueue streamQueue = + StreamQueue(resolutionStream); + + // Emit test events + final CameraResolutionChangedEvent fhdEvent = + CameraResolutionChangedEvent(cameraId, 1920, 1080); + final CameraResolutionChangedEvent uhdEvent = + CameraResolutionChangedEvent(cameraId, 3840, 2160); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + camera.onCameraClosing(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + final CameraClosingEvent event = CameraClosingEvent(cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('camera_closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final Stream errorStream = + camera.onCameraError(cameraId); + final StreamQueue streamQueue = + StreamQueue(errorStream); + + // Emit test events + final CameraErrorEvent event = + CameraErrorEvent(cameraId, 'Error Description'); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + await camera.handleCameraMethodCall( + MethodCall('error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive device orientation change events', () async { + // Act + final Stream eventStream = + camera.onDeviceOrientationChanged(); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + // Emit test events + const DeviceOrientationChangedEvent event = + DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + await camera.handleDeviceMethodCall( + MethodCall('orientation_changed', event.toJson())); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + + group('Function Tests', () { + late AVFoundationCamera camera; + late int cameraId; + + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }, + ); + camera = AVFoundationCamera(); + cameraId = await camera.createCamera( + const CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0, + ), + ResolutionPreset.high, + ); + final Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController.add( + CameraInitializedEvent( cameraId, 1920, 1080, @@ -231,859 +447,628 @@ void main() { true, FocusMode.auto, true, - )); - await initializeFuture; + ), + ); + await initializeFuture; + }); - // Act - await camera.dispose(cameraId); - - // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - anything, - isMethodCall( - 'dispose', - arguments: {'cameraId': 1}, - ), - ]); - }); + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Test 1', + 'lensFacing': 'front', + 'sensorOrientation': 1 + }, + { + 'name': 'Test 2', + 'lensFacing': 'back', + 'sensorOrientation': 2 + } + ]; + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'availableCameras': returnData}, + ); + + // Act + final List cameras = await camera.availableCameras(); + + // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name']! as String, + lensDirection: + parseCameraLensDirection(returnData[i]['lensFacing']! as String), + sensorOrientation: returnData[i]['sensorOrientation']! as int, + ); + expect(cameras[i], cameraDescription); + } }); - group('Event Tests', () { - late AVFoundationCamera camera; - late int cameraId; - setUp(() async { - MethodChannelMock( + test( + 'Should throw CameraException when availableCameras throws a PlatformException', + () { + // Arrange + MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = AVFoundationCamera(); - cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add(CameraInitializedEvent( - cameraId, - 1920, - 1080, - ExposureMode.auto, - true, - FocusMode.auto, - true, - )); - await initializeFuture; - }); + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + + // Act + expect( + camera.availableCameras, + throwsA( + isA() + .having( + (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); - test('Should receive initialized event', () async { - // Act - final Stream eventStream = - camera.onCameraInitialized(cameraId); - final StreamQueue streamQueue = - StreamQueue(eventStream); + test('Should take a picture and return an XFile instance', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'takePicture': '/test/path.jpg'}); + + // Act + final XFile file = await camera.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.jpg'); + }); - // Emit test events - final CameraInitializedEvent event = CameraInitializedEvent( - cameraId, - 3840, - 2160, - ExposureMode.auto, - true, - FocusMode.auto, - true, - ); - await camera.handleCameraMethodCall( - MethodCall('initialized', event.toJson()), cameraId); + test('Should prepare for video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'prepareForVideoRecording': null}, + ); - // Assert - expect(await streamQueue.next, event); + // Act + await camera.prepareForVideoRecording(); - // Clean up - await streamQueue.cancel(); - }); + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); - test('Should receive resolution changes', () async { - // Act - final Stream resolutionStream = - camera.onCameraResolutionChanged(cameraId); - final StreamQueue streamQueue = - StreamQueue(resolutionStream); - - // Emit test events - final CameraResolutionChangedEvent fhdEvent = - CameraResolutionChangedEvent(cameraId, 1920, 1080); - final CameraResolutionChangedEvent uhdEvent = - CameraResolutionChangedEvent(cameraId, 3840, 2160); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, fhdEvent); - expect(await streamQueue.next, uhdEvent); - expect(await streamQueue.next, fhdEvent); - expect(await streamQueue.next, uhdEvent); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive camera closing events', () async { - // Act - final Stream eventStream = - camera.onCameraClosing(cameraId); - final StreamQueue streamQueue = - StreamQueue(eventStream); - - // Emit test events - final CameraClosingEvent event = CameraClosingEvent(cameraId); - await camera.handleCameraMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('camera_closing', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive camera error events', () async { - // Act - final Stream errorStream = - camera.onCameraError(cameraId); - final StreamQueue streamQueue = - StreamQueue(errorStream); - - // Emit test events - final CameraErrorEvent event = - CameraErrorEvent(cameraId, 'Error Description'); - await camera.handleCameraMethodCall( - MethodCall('error', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('error', event.toJson()), cameraId); - await camera.handleCameraMethodCall( - MethodCall('error', event.toJson()), cameraId); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); - - test('Should receive device orientation change events', () async { - // Act - final Stream eventStream = - camera.onDeviceOrientationChanged(); - final StreamQueue streamQueue = - StreamQueue(eventStream); - - // Emit test events - const DeviceOrientationChangedEvent event = - DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); - await camera.handleDeviceMethodCall( - MethodCall('orientation_changed', event.toJson())); - await camera.handleDeviceMethodCall( - MethodCall('orientation_changed', event.toJson())); - await camera.handleDeviceMethodCall( - MethodCall('orientation_changed', event.toJson())); - - // Assert - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - expect(await streamQueue.next, event); - - // Clean up - await streamQueue.cancel(); - }); + test('Should start recording a video', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': null, + }), + ]); }); - group('Function Tests', () { - late AVFoundationCamera camera; - late int cameraId; + test('Should pass maxVideoDuration when starting recording a video', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); - setUp(() async { - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = AVFoundationCamera(); - cameraId = await camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ); - final Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController.add( - CameraInitializedEvent( - cameraId, - 1920, - 1080, - ExposureMode.auto, - true, - FocusMode.auto, - true, - ), - ); - await initializeFuture; - }); + // Act + await camera.startVideoRecording( + cameraId, + maxVideoDuration: const Duration(seconds: 10), + ); - test('Should fetch CameraDescription instances for available cameras', - () async { - // Arrange - final List returnData = [ - { - 'name': 'Test 1', - 'lensFacing': 'front', - 'sensorOrientation': 1 - }, - { - 'name': 'Test 2', - 'lensFacing': 'back', - 'sensorOrientation': 2 - } - ]; - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'availableCameras': returnData}, - ); + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': 10000 + }), + ]); + }); - // Act - final List cameras = await camera.availableCameras(); - - // Assert - expect(channel.log, [ - isMethodCall('availableCameras', arguments: null), - ]); - expect(cameras.length, returnData.length); - for (int i = 0; i < returnData.length; i++) { - final CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name']! as String, - lensDirection: parseCameraLensDirection( - returnData[i]['lensFacing']! as String), - sensorOrientation: returnData[i]['sensorOrientation']! as int, - ); - expect(cameras[i], cameraDescription); - } - }); + test('Should stop a video recording and return the file', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': '/test/path.mp4'}, + ); - test( - 'Should throw CameraException when availableCameras throws a PlatformException', - () { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'availableCameras': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + // Act + final XFile file = await camera.stopVideoRecording(cameraId); - // Act - expect( - camera.availableCameras, - throwsA( - isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), - ), - ); - }); + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.mp4'); + }); - test('Should take a picture and return an XFile instance', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'takePicture': '/test/path.jpg'}); + test('Should pause a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pauseVideoRecording': null}, + ); - // Act - final XFile file = await camera.takePicture(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('takePicture', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.jpg'); - }); - - test('Should prepare for video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'prepareForVideoRecording': null}, - ); + // Act + await camera.pauseVideoRecording(cameraId); - // Act - await camera.prepareForVideoRecording(); + // Assert + expect(channel.log, [ + isMethodCall('pauseVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Assert - expect(channel.log, [ - isMethodCall('prepareForVideoRecording', arguments: null), - ]); - }); + test('Should resume a video recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumeVideoRecording': null}, + ); - test('Should start recording a video', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': null}, - ); + // Act + await camera.resumeVideoRecording(cameraId); - // Act - await camera.startVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': null, - }), - ]); - }); - - test('Should pass maxVideoDuration when starting recording a video', - () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': null}, - ); + // Assert + expect(channel.log, [ + isMethodCall('resumeVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - await camera.startVideoRecording( - cameraId, - maxVideoDuration: const Duration(seconds: 10), - ); + test('Should set the flash mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFlashMode': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': 10000 - }), - ]); - }); + // Act + await camera.setFlashMode(cameraId, FlashMode.torch); + await camera.setFlashMode(cameraId, FlashMode.always); + await camera.setFlashMode(cameraId, FlashMode.auto); + await camera.setFlashMode(cameraId, FlashMode.off); + + // Assert + expect(channel.log, [ + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'torch' + }), + isMethodCall('setFlashMode', arguments: { + 'cameraId': cameraId, + 'mode': 'always' + }), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'off'}), + ]); + }); - test('Should stop a video recording and return the file', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'stopVideoRecording': '/test/path.mp4'}, - ); + test('Should set the exposure mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureMode': null}, + ); - // Act - final XFile file = await camera.stopVideoRecording(cameraId); - - // Assert - expect(channel.log, [ - isMethodCall('stopVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.mp4'); - }); - - test('Should pause a video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'pauseVideoRecording': null}, - ); + // Act + await camera.setExposureMode(cameraId, ExposureMode.auto); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setExposureMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setExposureMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); - // Act - await camera.pauseVideoRecording(cameraId); + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposurePoint': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('pauseVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); + // Act + await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); + await camera.setExposurePoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); - test('Should resume a video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'resumeVideoRecording': null}, - ); + test('Should get the min exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinExposureOffset': 2.0}, + ); - // Act - await camera.resumeVideoRecording(cameraId); + // Act + final double minExposureOffset = + await camera.getMinExposureOffset(cameraId); + + // Assert + expect(minExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMinExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Assert - expect(channel.log, [ - isMethodCall('resumeVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - }); + test('Should get the max exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxExposureOffset': 2.0}, + ); - test('Should set the flash mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setFlashMode': null}, - ); + // Act + final double maxExposureOffset = + await camera.getMaxExposureOffset(cameraId); + + // Assert + expect(maxExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMaxExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - await camera.setFlashMode(cameraId, FlashMode.torch); - await camera.setFlashMode(cameraId, FlashMode.always); - await camera.setFlashMode(cameraId, FlashMode.auto); - await camera.setFlashMode(cameraId, FlashMode.off); - - // Assert - expect(channel.log, [ - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'torch' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'always' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'auto' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'off' - }), - ]); - }); - - test('Should set the exposure mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setExposureMode': null}, - ); + test('Should get the exposure offset step size', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getExposureOffsetStepSize': 0.25}, + ); - // Act - await camera.setExposureMode(cameraId, ExposureMode.auto); - await camera.setExposureMode(cameraId, ExposureMode.locked); - - // Assert - expect(channel.log, [ - isMethodCall('setExposureMode', arguments: { - 'cameraId': cameraId, - 'mode': 'auto' - }), - isMethodCall('setExposureMode', arguments: { - 'cameraId': cameraId, - 'mode': 'locked' - }), - ]); - }); - - test('Should set the exposure point', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setExposurePoint': null}, - ); + // Act + final double stepSize = await camera.getExposureOffsetStepSize(cameraId); - // Act - await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); - await camera.setExposurePoint(cameraId, null); - - // Assert - expect(channel.log, [ - isMethodCall('setExposurePoint', arguments: { - 'cameraId': cameraId, - 'x': 0.5, - 'y': 0.5, - 'reset': false - }), - isMethodCall('setExposurePoint', arguments: { - 'cameraId': cameraId, - 'x': null, - 'y': null, - 'reset': true - }), - ]); - }); - - test('Should get the min exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMinExposureOffset': 2.0}, - ); + // Assert + expect(stepSize, 0.25); + expect(channel.log, [ + isMethodCall('getExposureOffsetStepSize', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - final double minExposureOffset = - await camera.getMinExposureOffset(cameraId); - - // Assert - expect(minExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMinExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should get the max exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMaxExposureOffset': 2.0}, - ); + test('Should set the exposure offset', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureOffset': 0.6}, + ); - // Act - final double maxExposureOffset = - await camera.getMaxExposureOffset(cameraId); - - // Assert - expect(maxExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMaxExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should get the exposure offset step size', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getExposureOffsetStepSize': 0.25}, - ); + // Act + final double actualOffset = await camera.setExposureOffset(cameraId, 0.5); + + // Assert + expect(actualOffset, 0.6); + expect(channel.log, [ + isMethodCall('setExposureOffset', arguments: { + 'cameraId': cameraId, + 'offset': 0.5, + }), + ]); + }); - // Act - final double stepSize = - await camera.getExposureOffsetStepSize(cameraId); - - // Assert - expect(stepSize, 0.25); - expect(channel.log, [ - isMethodCall('getExposureOffsetStepSize', - arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should set the exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setExposureOffset': 0.6}, - ); + test('Should set the focus mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusMode': null}, + ); - // Act - final double actualOffset = - await camera.setExposureOffset(cameraId, 0.5); - - // Assert - expect(actualOffset, 0.6); - expect(channel.log, [ - isMethodCall('setExposureOffset', arguments: { - 'cameraId': cameraId, - 'offset': 0.5, - }), - ]); - }); - - test('Should set the focus mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setFocusMode': null}, - ); + // Act + await camera.setFocusMode(cameraId, FocusMode.auto); + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFocusMode', arguments: { + 'cameraId': cameraId, + 'mode': 'locked' + }), + ]); + }); - // Act - await camera.setFocusMode(cameraId, FocusMode.auto); - await camera.setFocusMode(cameraId, FocusMode.locked); - - // Assert - expect(channel.log, [ - isMethodCall('setFocusMode', arguments: { - 'cameraId': cameraId, - 'mode': 'auto' - }), - isMethodCall('setFocusMode', arguments: { - 'cameraId': cameraId, - 'mode': 'locked' - }), - ]); - }); - - test('Should set the exposure point', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setFocusPoint': null}, - ); + test('Should set the exposure point', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusPoint': null}, + ); - // Act - await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); - await camera.setFocusPoint(cameraId, null); - - // Assert - expect(channel.log, [ - isMethodCall('setFocusPoint', arguments: { - 'cameraId': cameraId, - 'x': 0.5, - 'y': 0.5, - 'reset': false - }), - isMethodCall('setFocusPoint', arguments: { - 'cameraId': cameraId, - 'x': null, - 'y': null, - 'reset': true - }), - ]); - }); - - test('Should build a texture widget as preview widget', () async { - // Act - final Widget widget = camera.buildPreview(cameraId); + // Act + await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); + await camera.setFocusPoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); - // Act - expect(widget is Texture, isTrue); - expect((widget as Texture).textureId, cameraId); - }); + test('Should build a texture widget as preview widget', () async { + // Act + final Widget widget = camera.buildPreview(cameraId); - test('Should throw MissingPluginException when handling unknown method', - () { - final AVFoundationCamera camera = AVFoundationCamera(); + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); - expect( - () => camera.handleCameraMethodCall( - const MethodCall('unknown_method'), 1), - throwsA(isA())); - }); + test('Should throw MissingPluginException when handling unknown method', + () { + final AVFoundationCamera camera = AVFoundationCamera(); - test('Should get the max zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMaxZoomLevel': 10.0}, - ); + expect( + () => camera.handleCameraMethodCall( + const MethodCall('unknown_method'), 1), + throwsA(isA())); + }); - // Act - final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); - - // Assert - expect(maxZoomLevel, 10.0); - expect(channel.log, [ - isMethodCall('getMaxZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should get the min zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'getMinZoomLevel': 1.0}, - ); + test('Should get the max zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxZoomLevel': 10.0}, + ); - // Act - final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); - - // Assert - expect(maxZoomLevel, 1.0); - expect(channel.log, [ - isMethodCall('getMinZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); - }); - - test('Should set the zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'setZoomLevel': null}, - ); + // Act + final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); - // Act - await camera.setZoomLevel(cameraId, 2.0); + // Assert + expect(maxZoomLevel, 10.0); + expect(channel.log, [ + isMethodCall('getMaxZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Assert - expect(channel.log, [ - isMethodCall('setZoomLevel', - arguments: {'cameraId': cameraId, 'zoom': 2.0}), - ]); - }); + test('Should get the min zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinZoomLevel': 1.0}, + ); - test('Should throw CameraException when illegal zoom level is supplied', - () async { - // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'setZoomLevel': PlatformException( - code: 'ZOOM_ERROR', - message: 'Illegal zoom error', - details: null, - ) - }, - ); + // Act + final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); - // Act & assert - expect( - () => camera.setZoomLevel(cameraId, -1.0), - throwsA(isA() - .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') - .having((CameraException e) => e.description, 'description', - 'Illegal zoom error'))); - }); - - test('Should lock the capture orientation', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'lockCaptureOrientation': null}, - ); + // Assert + expect(maxZoomLevel, 1.0); + expect(channel.log, [ + isMethodCall('getMinZoomLevel', arguments: { + 'cameraId': cameraId, + }), + ]); + }); - // Act - await camera.lockCaptureOrientation( - cameraId, DeviceOrientation.portraitUp); - - // Assert - expect(channel.log, [ - isMethodCall('lockCaptureOrientation', arguments: { - 'cameraId': cameraId, - 'orientation': 'portraitUp' - }), - ]); - }); - - test('Should unlock the capture orientation', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'unlockCaptureOrientation': null}, - ); + test('Should set the zoom level', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setZoomLevel': null}, + ); - // Act - await camera.unlockCaptureOrientation(cameraId); + // Act + await camera.setZoomLevel(cameraId, 2.0); - // Assert - expect(channel.log, [ - isMethodCall('unlockCaptureOrientation', - arguments: {'cameraId': cameraId}), - ]); - }); + // Assert + expect(channel.log, [ + isMethodCall('setZoomLevel', + arguments: {'cameraId': cameraId, 'zoom': 2.0}), + ]); + }); - test('Should pause the camera preview', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'pausePreview': null}, - ); + test('Should throw CameraException when illegal zoom level is supplied', + () async { + // Arrange + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'setZoomLevel': PlatformException( + code: 'ZOOM_ERROR', + message: 'Illegal zoom error', + details: null, + ) + }, + ); - // Act - await camera.pausePreview(cameraId); + // Act & assert + expect( + () => camera.setZoomLevel(cameraId, -1.0), + throwsA(isA() + .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') + .having((CameraException e) => e.description, 'description', + 'Illegal zoom error'))); + }); - // Assert - expect(channel.log, [ - isMethodCall('pausePreview', - arguments: {'cameraId': cameraId}), - ]); - }); + test('Should lock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'lockCaptureOrientation': null}, + ); - test('Should resume the camera preview', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'resumePreview': null}, - ); + // Act + await camera.lockCaptureOrientation( + cameraId, DeviceOrientation.portraitUp); + + // Assert + expect(channel.log, [ + isMethodCall('lockCaptureOrientation', arguments: { + 'cameraId': cameraId, + 'orientation': 'portraitUp' + }), + ]); + }); - // Act - await camera.resumePreview(cameraId); + test('Should unlock the capture orientation', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'unlockCaptureOrientation': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('resumePreview', - arguments: {'cameraId': cameraId}), - ]); - }); + // Act + await camera.unlockCaptureOrientation(cameraId); - test('Should start streaming', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); + // Assert + expect(channel.log, [ + isMethodCall('unlockCaptureOrientation', + arguments: {'cameraId': cameraId}), + ]); + }); - // Act - final StreamSubscription subscription = camera - .onStreamedFrameAvailable(cameraId) - .listen((CameraImageData imageData) {}); + test('Should pause the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pausePreview': null}, + ); - // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - ]); + // Act + await camera.pausePreview(cameraId); - subscription.cancel(); - }); + // Assert + expect(channel.log, [ + isMethodCall('pausePreview', + arguments: {'cameraId': cameraId}), + ]); + }); - test('Should stop streaming', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); + test('Should resume the camera preview', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumePreview': null}, + ); - // Act - final StreamSubscription subscription = camera - .onStreamedFrameAvailable(cameraId) - .listen((CameraImageData imageData) {}); - subscription.cancel(); - - // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null), - ]); - }); + // Act + await camera.resumePreview(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumePreview', + arguments: {'cameraId': cameraId}), + ]); + }); + + test('Should start streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + ]); + + subscription.cancel(); + }); + + test('Should stop streaming', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'startImageStream': null, + 'stopImageStream': null, + }, + ); + + // Act + final StreamSubscription subscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) {}); + subscription.cancel(); + + // Assert + expect(channel.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null), + ]); }); }); } From cdd5a13d343e8da836edbe353fec49193a354f5b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 12:53:43 -0400 Subject: [PATCH 4/9] Rename channels, add registration --- .../flutter/plugins/camera/DartMessenger.java | 5 +- .../plugins/camera/MethodCallHandlerImpl.java | 5 +- .../lib/src/android_camera.dart | 14 ++- packages/camera/camera_android/pubspec.yaml | 1 + .../test/android_camera_test.dart | 115 +++++++++--------- .../ios/Classes/CameraPlugin.m | 13 +- .../camera_avfoundation/ios/Classes/FLTCam.m | 6 +- .../lib/src/avfoundation_camera.dart | 17 ++- .../camera/camera_avfoundation/pubspec.yaml | 1 + .../test/avfoundation_camera_test.dart | 115 +++++++++--------- 10 files changed, 156 insertions(+), 136 deletions(-) diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index dc62fce524d3..e15078e66afc 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -64,8 +64,9 @@ enum CameraEventType { * the main thread. The handler is mainly supplied so it will be easier test this class. */ DartMessenger(BinaryMessenger messenger, long cameraId, @NonNull Handler handler) { - cameraChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); - deviceChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/device"); + cameraChannel = + new MethodChannel(messenger, "plugins.flutter.io/camera_android/camera" + cameraId); + deviceChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android/fromPlatform"); this.handler = handler; } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 35cc2b081bae..38201e1136c9 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -49,8 +49,9 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { this.permissionsRegistry = permissionsAdder; this.textureRegistry = textureRegistry; - methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera"); - imageStreamChannel = new EventChannel(messenger, "plugins.flutter.io/camera/imageStream"); + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android"); + imageStreamChannel = + new EventChannel(messenger, "plugins.flutter.io/camera_android/imageStream"); methodChannel.setMethodCallHandler(this); } diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 0ab2273c53cf..d6beb1b8c1c9 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -14,18 +14,24 @@ import 'package:stream_transform/stream_transform.dart'; import 'type_conversion.dart'; import 'utils.dart'; -const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); +const MethodChannel _channel = + MethodChannel('plugins.flutter.io/camera_android'); /// The Android implementation of [CameraPlatform] that uses method channels. class AndroidCamera extends CameraPlatform { /// Construct a new method channel camera instance. AndroidCamera() { const MethodChannel channel = - MethodChannel('flutter.io/cameraPlugin/device'); + MethodChannel('plugins.flutter.io/camera_android/fromPlatform'); channel.setMethodCallHandler( (MethodCall call) => handleDeviceMethodCall(call)); } + /// Registers this class as the default instance of [CameraPlatform]. + static void registerWith() { + CameraPlatform.instance = AndroidCamera(); + } + final Map _channels = {}; /// The controller we need to broadcast the different events coming @@ -112,7 +118,7 @@ class AndroidCamera extends CameraPlatform { }) { _channels.putIfAbsent(cameraId, () { final MethodChannel channel = - MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); + MethodChannel('plugins.flutter.io/camera_android/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleCameraMethodCall(call, cameraId)); return channel; @@ -294,7 +300,7 @@ class AndroidCamera extends CameraPlatform { Future _startPlatformStream() async { await _channel.invokeMethod('startImageStream'); const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); + EventChannel('plugins.flutter.io/camera_android/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { if (defaultTargetPlatform == TargetPlatform.iOS) { diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 908d55f9c50e..8330828b2227 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -15,6 +15,7 @@ flutter: android: package: io.flutter.plugins.camera pluginClass: CameraPlugin + dartPluginClass: AndroidCamera dependencies: camera_platform_interface: ^2.2.0 diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index de8abca07afe..9674b0c5a420 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -15,14 +15,21 @@ import 'package:flutter_test/flutter_test.dart'; import 'method_channel_mock.dart'; +const String _channelName = 'plugins.flutter.io/camera_android'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); + test('registers instance', () async { + AndroidCamera.registerWith(); + expect(CameraPlatform.instance, isA()); + }); + group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': { 'cameraId': 1, @@ -57,14 +64,12 @@ void main() { test('Should throw CameraException when create throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + MethodChannelMock(channelName: _channelName, methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); final AndroidCamera camera = AndroidCamera(); // Act @@ -90,14 +95,12 @@ void main() { test('Should throw CameraException when create throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + MethodChannelMock(channelName: _channelName, methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); final AndroidCamera camera = AndroidCamera(); // Act @@ -125,7 +128,7 @@ void main() { () { // Arrange MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'initialize': PlatformException( code: 'TESTING_ERROR_CODE', @@ -155,7 +158,7 @@ void main() { test('Should send initialization data', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': { 'cameraId': 1, @@ -203,7 +206,7 @@ void main() { test('Should send a disposal call on dispose', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null, @@ -252,7 +255,7 @@ void main() { late int cameraId; setUp(() async { MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null @@ -422,7 +425,7 @@ void main() { setUp(() async { MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null @@ -468,7 +471,7 @@ void main() { } ]; final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'availableCameras': returnData}, ); @@ -495,14 +498,12 @@ void main() { 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'availableCameras': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + MethodChannelMock(channelName: _channelName, methods: { + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); // Act expect( @@ -520,7 +521,7 @@ void main() { test('Should take a picture and return an XFile instance', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'takePicture': '/test/path.jpg'}); // Act @@ -538,7 +539,7 @@ void main() { test('Should prepare for video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'prepareForVideoRecording': null}, ); @@ -554,7 +555,7 @@ void main() { test('Should start recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'startVideoRecording': null}, ); @@ -574,7 +575,7 @@ void main() { () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'startVideoRecording': null}, ); @@ -596,7 +597,7 @@ void main() { test('Should stop a video recording and return the file', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'stopVideoRecording': '/test/path.mp4'}, ); @@ -615,7 +616,7 @@ void main() { test('Should pause a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'pauseVideoRecording': null}, ); @@ -633,7 +634,7 @@ void main() { test('Should resume a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'resumeVideoRecording': null}, ); @@ -651,7 +652,7 @@ void main() { test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setFlashMode': null}, ); @@ -681,7 +682,7 @@ void main() { test('Should set the exposure mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setExposureMode': null}, ); @@ -703,7 +704,7 @@ void main() { test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setExposurePoint': null}, ); @@ -731,7 +732,7 @@ void main() { test('Should get the min exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMinExposureOffset': 2.0}, ); @@ -751,7 +752,7 @@ void main() { test('Should get the max exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMaxExposureOffset': 2.0}, ); @@ -771,7 +772,7 @@ void main() { test('Should get the exposure offset step size', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getExposureOffsetStepSize': 0.25}, ); @@ -790,7 +791,7 @@ void main() { test('Should set the exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setExposureOffset': 0.6}, ); @@ -810,7 +811,7 @@ void main() { test('Should set the focus mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setFocusMode': null}, ); @@ -832,7 +833,7 @@ void main() { test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setFocusPoint': null}, ); @@ -879,7 +880,7 @@ void main() { test('Should get the max zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMaxZoomLevel': 10.0}, ); @@ -898,7 +899,7 @@ void main() { test('Should get the min zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMinZoomLevel': 1.0}, ); @@ -917,7 +918,7 @@ void main() { test('Should set the zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setZoomLevel': null}, ); @@ -935,7 +936,7 @@ void main() { () async { // Arrange MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'setZoomLevel': PlatformException( code: 'ZOOM_ERROR', @@ -957,7 +958,7 @@ void main() { test('Should lock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'lockCaptureOrientation': null}, ); @@ -977,7 +978,7 @@ void main() { test('Should unlock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'unlockCaptureOrientation': null}, ); @@ -994,7 +995,7 @@ void main() { test('Should pause the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'pausePreview': null}, ); @@ -1011,7 +1012,7 @@ void main() { test('Should resume the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'resumePreview': null}, ); @@ -1028,7 +1029,7 @@ void main() { test('Should start streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, @@ -1051,7 +1052,7 @@ void main() { test('Should stop streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m index 90327e35e187..cb19c0909158 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m @@ -26,7 +26,7 @@ @implementation CameraPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera_avfoundation" binaryMessenger:[registrar messenger]]; CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] messenger:[registrar messenger]]; @@ -49,9 +49,9 @@ - (instancetype)initWithRegistry:(NSObject *)registry } - (void)initDeviceEventMethodChannel { - FlutterMethodChannel *methodChannel = - [FlutterMethodChannel methodChannelWithName:@"flutter.io/cameraPlugin/device" - binaryMessenger:_messenger]; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:@"plugins.flutter.io/camera_avfoundation/fromPlatform" + binaryMessenger:_messenger]; _deviceEventMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; } @@ -162,8 +162,9 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call } }; FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", - (unsigned long)cameraId] + methodChannelWithName: + [NSString stringWithFormat:@"plugins.flutter.io/camera_avfoundation/camera%lu", + (unsigned long)cameraId] binaryMessenger:_messenger]; FLTThreadSafeMethodChannel *threadSafeMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 7af505b249cb..f267604af419 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -910,9 +910,9 @@ - (void)startImageStreamWithMessenger:(NSObject *)messen - (void)startImageStreamWithMessenger:(NSObject *)messenger imageStreamHandler:(FLTImageStreamHandler *)imageStreamHandler { if (!_isStreamingImages) { - FlutterEventChannel *eventChannel = - [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" - binaryMessenger:messenger]; + FlutterEventChannel *eventChannel = [FlutterEventChannel + eventChannelWithName:@"plugins.flutter.io/camera_avfoundation/imageStream" + binaryMessenger:messenger]; FLTThreadSafeEventChannel *threadSafeEventChannel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:eventChannel]; diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 1e9247b5e1d8..3956514dd014 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -14,18 +14,24 @@ import 'package:stream_transform/stream_transform.dart'; import 'type_conversion.dart'; import 'utils.dart'; -const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); +const MethodChannel _channel = + MethodChannel('plugins.flutter.io/camera_avfoundation'); /// An iOS implementation of [CameraPlatform] based on AVFoundation. class AVFoundationCamera extends CameraPlatform { /// Construct a new method channel camera instance. AVFoundationCamera() { const MethodChannel channel = - MethodChannel('flutter.io/cameraPlugin/device'); + MethodChannel('plugins.flutter.io/camera_avfoundation/fromPlatform'); channel.setMethodCallHandler( (MethodCall call) => handleDeviceMethodCall(call)); } + /// Registers this class as the default instance of [CameraPlatform]. + static void registerWith() { + CameraPlatform.instance = AVFoundationCamera(); + } + final Map _channels = {}; /// The controller we need to broadcast the different events coming @@ -111,8 +117,8 @@ class AVFoundationCamera extends CameraPlatform { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) { _channels.putIfAbsent(cameraId, () { - final MethodChannel channel = - MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); + final MethodChannel channel = MethodChannel( + 'plugins.flutter.io/camera_avfoundation/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleCameraMethodCall(call, cameraId)); return channel; @@ -294,7 +300,7 @@ class AVFoundationCamera extends CameraPlatform { Future _startPlatformStream() async { await _channel.invokeMethod('startImageStream'); const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); + EventChannel('plugins.flutter.io/camera_avfoundation/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { if (defaultTargetPlatform == TargetPlatform.iOS) { @@ -522,6 +528,7 @@ class AVFoundationCamera extends CameraPlatform { /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. + @visibleForTesting Future handleDeviceMethodCall(MethodCall call) async { switch (call.method) { case 'orientation_changed': diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 237f5ad329c5..452b2ddc8822 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -14,6 +14,7 @@ flutter: platforms: ios: pluginClass: CameraPlugin + dartPluginClass: AVFoundationCamera dependencies: camera_platform_interface: ^2.2.0 diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 28c50ecc1a36..4b32d2e50b4a 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -15,14 +15,21 @@ import 'package:flutter_test/flutter_test.dart'; import 'method_channel_mock.dart'; +const String _channelName = 'plugins.flutter.io/camera_avfoundation'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); + test('registers instance', () async { + AVFoundationCamera.registerWith(); + expect(CameraPlatform.instance, isA()); + }); + group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': { 'cameraId': 1, @@ -57,14 +64,12 @@ void main() { test('Should throw CameraException when create throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + MethodChannelMock(channelName: _channelName, methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); final AVFoundationCamera camera = AVFoundationCamera(); // Act @@ -90,14 +95,12 @@ void main() { test('Should throw CameraException when create throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + MethodChannelMock(channelName: _channelName, methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); final AVFoundationCamera camera = AVFoundationCamera(); // Act @@ -125,7 +128,7 @@ void main() { () { // Arrange MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'initialize': PlatformException( code: 'TESTING_ERROR_CODE', @@ -155,7 +158,7 @@ void main() { test('Should send initialization data', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': { 'cameraId': 1, @@ -203,7 +206,7 @@ void main() { test('Should send a disposal call on dispose', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null, @@ -252,7 +255,7 @@ void main() { late int cameraId; setUp(() async { MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null @@ -422,7 +425,7 @@ void main() { setUp(() async { MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null @@ -468,7 +471,7 @@ void main() { } ]; final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'availableCameras': returnData}, ); @@ -495,14 +498,12 @@ void main() { 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: { - 'availableCameras': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + MethodChannelMock(channelName: _channelName, methods: { + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); // Act expect( @@ -520,7 +521,7 @@ void main() { test('Should take a picture and return an XFile instance', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'takePicture': '/test/path.jpg'}); // Act @@ -538,7 +539,7 @@ void main() { test('Should prepare for video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'prepareForVideoRecording': null}, ); @@ -554,7 +555,7 @@ void main() { test('Should start recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'startVideoRecording': null}, ); @@ -574,7 +575,7 @@ void main() { () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'startVideoRecording': null}, ); @@ -596,7 +597,7 @@ void main() { test('Should stop a video recording and return the file', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'stopVideoRecording': '/test/path.mp4'}, ); @@ -615,7 +616,7 @@ void main() { test('Should pause a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'pauseVideoRecording': null}, ); @@ -633,7 +634,7 @@ void main() { test('Should resume a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'resumeVideoRecording': null}, ); @@ -651,7 +652,7 @@ void main() { test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setFlashMode': null}, ); @@ -681,7 +682,7 @@ void main() { test('Should set the exposure mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setExposureMode': null}, ); @@ -703,7 +704,7 @@ void main() { test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setExposurePoint': null}, ); @@ -731,7 +732,7 @@ void main() { test('Should get the min exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMinExposureOffset': 2.0}, ); @@ -751,7 +752,7 @@ void main() { test('Should get the max exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMaxExposureOffset': 2.0}, ); @@ -771,7 +772,7 @@ void main() { test('Should get the exposure offset step size', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getExposureOffsetStepSize': 0.25}, ); @@ -790,7 +791,7 @@ void main() { test('Should set the exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setExposureOffset': 0.6}, ); @@ -810,7 +811,7 @@ void main() { test('Should set the focus mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setFocusMode': null}, ); @@ -832,7 +833,7 @@ void main() { test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setFocusPoint': null}, ); @@ -879,7 +880,7 @@ void main() { test('Should get the max zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMaxZoomLevel': 10.0}, ); @@ -898,7 +899,7 @@ void main() { test('Should get the min zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'getMinZoomLevel': 1.0}, ); @@ -917,7 +918,7 @@ void main() { test('Should set the zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'setZoomLevel': null}, ); @@ -935,7 +936,7 @@ void main() { () async { // Arrange MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'setZoomLevel': PlatformException( code: 'ZOOM_ERROR', @@ -957,7 +958,7 @@ void main() { test('Should lock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'lockCaptureOrientation': null}, ); @@ -977,7 +978,7 @@ void main() { test('Should unlock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'unlockCaptureOrientation': null}, ); @@ -994,7 +995,7 @@ void main() { test('Should pause the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'pausePreview': null}, ); @@ -1011,7 +1012,7 @@ void main() { test('Should resume the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: {'resumePreview': null}, ); @@ -1028,7 +1029,7 @@ void main() { test('Should start streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, @@ -1051,7 +1052,7 @@ void main() { test('Should stop streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', + channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, From 634d9d97be7d9e209b4c2983734e44e68314401c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 12:55:59 -0400 Subject: [PATCH 5/9] Fix CODEOWNERS --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 68e4cb768a6a..16bf98ba9e4b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,7 +26,7 @@ packages/webview_flutter/** @bparrishMines packages/**/*_web/** @ditman # - Android -packages/camera/camera/android/** @camsim99 +packages/camera/camera_android/** @camsim99 packages/espresso/** @blasten packages/flutter_plugin_android_lifecycle/** @blasten packages/google_maps_flutter/google_maps_flutter/android/** @GaryQian @@ -40,7 +40,7 @@ packages/url_launcher/url_launcher_android/** @GaryQian packages/video_player/video_player_android/** @blasten # - iOS -packages/camera/camera/ios/** @hellohuanlin +packages/camera/camera_avfoundation/** @hellohuanlin packages/google_maps_flutter/google_maps_flutter/ios/** @cyanglaz packages/google_sign_in/google_sign_in_ios/** @jmagman packages/image_picker/image_picker_ios/** @cyanglaz From 5dfb4bc32eed12a98d7bb45dfdd90886791f523e Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 12:59:01 -0400 Subject: [PATCH 6/9] Version bumps --- packages/camera/camera_android/CHANGELOG.md | 4 ++++ packages/camera/camera_android/pubspec.yaml | 2 +- packages/camera/camera_avfoundation/CHANGELOG.md | 4 ++++ packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index c57f301fae95..5bc7c8ae8d19 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Switches to internal method channel implementation. + ## 0.9.7+1 * Splits from `camera` as a federated implementation. diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 8330828b2227..4cbac8cea8ea 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.7+1 +version: 0.9.8 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index c57f301fae95..5bc7c8ae8d19 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Switches to internal method channel implementation. + ## 0.9.7+1 * Splits from `camera` as a federated implementation. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 452b2ddc8822..f89dd602dcdd 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.7+1 +version: 0.9.8 environment: sdk: ">=2.14.0 <3.0.0" From 942f863693b6c35ca635c8884b8c4673b64d77f6 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Jun 2022 13:44:23 -0400 Subject: [PATCH 7/9] Clean up unnecessary platform checks --- .../lib/src/android_camera.dart | 7 --- .../lib/src/type_conversion.dart | 23 ++----- .../test/type_conversion_test.dart | 35 ++--------- .../camera_android/test/utils_test.dart | 2 +- .../example/test_driver/integration_test.dart | 61 +------------------ .../lib/src/avfoundation_camera.dart | 10 ++- .../lib/src/type_conversion.dart | 22 ++----- .../test/type_conversion_test.dart | 35 ++--------- .../camera_avfoundation/test/utils_test.dart | 2 +- 9 files changed, 26 insertions(+), 171 deletions(-) diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index d6beb1b8c1c9..5fb3443ff91e 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -303,13 +303,6 @@ class AndroidCamera extends CameraPlatform { EventChannel('plugins.flutter.io/camera_android/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { - if (defaultTargetPlatform == TargetPlatform.iOS) { - try { - _channel.invokeMethod('receivedImageStreamData'); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } _frameStreamController! .add(cameraImageFromPlatformData(imageData as Map)); }); diff --git a/packages/camera/camera_android/lib/src/type_conversion.dart b/packages/camera/camera_android/lib/src/type_conversion.dart index fc33032e2fa1..754a5a032715 100644 --- a/packages/camera/camera_android/lib/src/type_conversion.dart +++ b/packages/camera/camera_android/lib/src/type_conversion.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. @@ -30,23 +29,11 @@ CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { } ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { - if (defaultTargetPlatform == TargetPlatform.android) { - switch (data) { - case 35: // android.graphics.ImageFormat.YUV_420_888 - return ImageFormatGroup.yuv420; - case 256: // android.graphics.ImageFormat.JPEG - return ImageFormatGroup.jpeg; - } - } - - if (defaultTargetPlatform == TargetPlatform.iOS) { - switch (data) { - case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange - return ImageFormatGroup.yuv420; - - case 1111970369: // kCVPixelFormatType_32BGRA - return ImageFormatGroup.bgra8888; - } + switch (data) { + case 35: // android.graphics.ImageFormat.YUV_420_888 + return ImageFormatGroup.yuv420; + case 256: // android.graphics.ImageFormat.JPEG + return ImageFormatGroup.jpeg; } return ImageFormatGroup.unknown; diff --git a/packages/camera/camera_android/test/type_conversion_test.dart b/packages/camera/camera_android/test/type_conversion_test.dart index 4818074ec767..b07466df791f 100644 --- a/packages/camera/camera_android/test/type_conversion_test.dart +++ b/packages/camera/camera_android/test/type_conversion_test.dart @@ -6,16 +6,15 @@ // ignore: unnecessary_import import 'dart:typed_data'; +import 'package:camera_android/src/type_conversion.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/method_channel/type_conversion.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('CameraImageData can be created', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ - 'format': 35, + 'format': 1, 'height': 1, 'width': 4, 'lensAperture': 1.8, @@ -33,37 +32,11 @@ void main() { }); expect(cameraImage.height, 1); expect(cameraImage.width, 4); - expect(cameraImage.format.group, ImageFormatGroup.yuv420); + expect(cameraImage.format.group, ImageFormatGroup.unknown); expect(cameraImage.planes.length, 1); }); - test('CameraImageData has ImageFormatGroup.yuv420 for iOS', () { - debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - - final CameraImageData cameraImage = - cameraImageFromPlatformData({ - 'format': 875704438, - 'height': 1, - 'width': 4, - 'lensAperture': 1.8, - 'sensorExposureTime': 9991324, - 'sensorSensitivity': 92.0, - 'planes': [ - { - 'bytes': Uint8List.fromList([1, 2, 3, 4]), - 'bytesPerPixel': 1, - 'bytesPerRow': 4, - 'height': 1, - 'width': 4 - } - ] - }); - expect(cameraImage.format.group, ImageFormatGroup.yuv420); - }); - - test('CameraImageData has ImageFormatGroup.yuv420 for Android', () { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - + test('CameraImageData has ImageFormatGroup.yuv420', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 35, diff --git a/packages/camera/camera_android/test/utils_test.dart b/packages/camera/camera_android/test/utils_test.dart index 0e4171d73aa6..6f426bc90f6f 100644 --- a/packages/camera/camera_android/test/utils_test.dart +++ b/packages/camera/camera_android/test/utils_test.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_android/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/camera/camera_avfoundation/example/test_driver/integration_test.dart b/packages/camera/camera_avfoundation/example/test_driver/integration_test.dart index 4ec97e66d36c..4f10f2a522f3 100644 --- a/packages/camera/camera_avfoundation/example/test_driver/integration_test.dart +++ b/packages/camera/camera_avfoundation/example/test_driver/integration_test.dart @@ -2,63 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; +import 'package:integration_test/integration_test_driver.dart'; -import 'package:flutter_driver/flutter_driver.dart'; - -const String _examplePackage = 'io.flutter.plugins.cameraexample'; - -Future main() async { - if (!(Platform.isLinux || Platform.isMacOS)) { - print('This test must be run on a POSIX host. Skipping...'); - exit(0); - } - final bool adbExists = - Process.runSync('which', ['adb']).exitCode == 0; - if (!adbExists) { - print(r'This test needs ADB to exist on the $PATH. Skipping...'); - exit(0); - } - print('Granting camera permissions...'); - Process.runSync('adb', [ - 'shell', - 'pm', - 'grant', - _examplePackage, - 'android.permission.CAMERA' - ]); - Process.runSync('adb', [ - 'shell', - 'pm', - 'grant', - _examplePackage, - 'android.permission.RECORD_AUDIO' - ]); - print('Starting test.'); - final FlutterDriver driver = await FlutterDriver.connect(); - final String data = await driver.requestData( - null, - timeout: const Duration(minutes: 1), - ); - await driver.close(); - print('Test finished. Revoking camera permissions...'); - Process.runSync('adb', [ - 'shell', - 'pm', - 'revoke', - _examplePackage, - 'android.permission.CAMERA' - ]); - Process.runSync('adb', [ - 'shell', - 'pm', - 'revoke', - _examplePackage, - 'android.permission.RECORD_AUDIO' - ]); - - final Map result = jsonDecode(data) as Map; - exit(result['result'] == 'true' ? 0 : 1); -} +Future main() => integrationDriver(); diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 3956514dd014..1bff9011586b 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -303,12 +303,10 @@ class AVFoundationCamera extends CameraPlatform { EventChannel('plugins.flutter.io/camera_avfoundation/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { - if (defaultTargetPlatform == TargetPlatform.iOS) { - try { - _channel.invokeMethod('receivedImageStreamData'); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } + try { + _channel.invokeMethod('receivedImageStreamData'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); } _frameStreamController! .add(cameraImageFromPlatformData(imageData as Map)); diff --git a/packages/camera/camera_avfoundation/lib/src/type_conversion.dart b/packages/camera/camera_avfoundation/lib/src/type_conversion.dart index fc33032e2fa1..c2a539a63dab 100644 --- a/packages/camera/camera_avfoundation/lib/src/type_conversion.dart +++ b/packages/camera/camera_avfoundation/lib/src/type_conversion.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. @@ -30,23 +29,12 @@ CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { } ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { - if (defaultTargetPlatform == TargetPlatform.android) { - switch (data) { - case 35: // android.graphics.ImageFormat.YUV_420_888 - return ImageFormatGroup.yuv420; - case 256: // android.graphics.ImageFormat.JPEG - return ImageFormatGroup.jpeg; - } - } - - if (defaultTargetPlatform == TargetPlatform.iOS) { - switch (data) { - case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange - return ImageFormatGroup.yuv420; + switch (data) { + case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + return ImageFormatGroup.yuv420; - case 1111970369: // kCVPixelFormatType_32BGRA - return ImageFormatGroup.bgra8888; - } + case 1111970369: // kCVPixelFormatType_32BGRA + return ImageFormatGroup.bgra8888; } return ImageFormatGroup.unknown; diff --git a/packages/camera/camera_avfoundation/test/type_conversion_test.dart b/packages/camera/camera_avfoundation/test/type_conversion_test.dart index 4818074ec767..282f4aedb21d 100644 --- a/packages/camera/camera_avfoundation/test/type_conversion_test.dart +++ b/packages/camera/camera_avfoundation/test/type_conversion_test.dart @@ -6,16 +6,15 @@ // ignore: unnecessary_import import 'dart:typed_data'; +import 'package:camera_avfoundation/src/type_conversion.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/method_channel/type_conversion.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('CameraImageData can be created', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ - 'format': 35, + 'format': 1, 'height': 1, 'width': 4, 'lensAperture': 1.8, @@ -33,13 +32,11 @@ void main() { }); expect(cameraImage.height, 1); expect(cameraImage.width, 4); - expect(cameraImage.format.group, ImageFormatGroup.yuv420); + expect(cameraImage.format.group, ImageFormatGroup.unknown); expect(cameraImage.planes.length, 1); }); - test('CameraImageData has ImageFormatGroup.yuv420 for iOS', () { - debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - + test('CameraImageData has ImageFormatGroup.yuv420', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 875704438, @@ -60,28 +57,4 @@ void main() { }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); - - test('CameraImageData has ImageFormatGroup.yuv420 for Android', () { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final CameraImageData cameraImage = - cameraImageFromPlatformData({ - 'format': 35, - 'height': 1, - 'width': 4, - 'lensAperture': 1.8, - 'sensorExposureTime': 9991324, - 'sensorSensitivity': 92.0, - 'planes': [ - { - 'bytes': Uint8List.fromList([1, 2, 3, 4]), - 'bytesPerPixel': 1, - 'bytesPerRow': 4, - 'height': 1, - 'width': 4 - } - ] - }); - expect(cameraImage.format.group, ImageFormatGroup.yuv420); - }); } diff --git a/packages/camera/camera_avfoundation/test/utils_test.dart b/packages/camera/camera_avfoundation/test/utils_test.dart index 0e4171d73aa6..bd28abb0dc63 100644 --- a/packages/camera/camera_avfoundation/test/utils_test.dart +++ b/packages/camera/camera_avfoundation/test/utils_test.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_avfoundation/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; From d30f0bea92c6cdc94886e3614d48332fc28b6f50 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 13 Jun 2022 13:34:56 -0400 Subject: [PATCH 8/9] Missing dependencies --- packages/camera/camera_android/pubspec.yaml | 2 ++ packages/camera/camera_avfoundation/pubspec.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 4cbac8cea8ea..73eeaa720dbc 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -22,8 +22,10 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 + stream_transform: ^2.0.0 dev_dependencies: + async: ^2.5.0 flutter_driver: sdk: flutter flutter_test: diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index f89dd602dcdd..231ce26227b8 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -20,8 +20,10 @@ dependencies: camera_platform_interface: ^2.2.0 flutter: sdk: flutter + stream_transform: ^2.0.0 dev_dependencies: + async: ^2.5.0 flutter_driver: sdk: flutter flutter_test: From 23e9a5d0ed27c0907d986701c3ee75d811a23c89 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 13 Jun 2022 13:47:20 -0400 Subject: [PATCH 9/9] Register for integration tests --- .../camera_android/example/integration_test/camera_test.dart | 2 ++ .../example/integration_test/camera_test.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/camera/camera_android/example/integration_test/camera_test.dart b/packages/camera/camera_android/example/integration_test/camera_test.dart index 05c669307dcc..99029fcac605 100644 --- a/packages/camera/camera_android/example/integration_test/camera_test.dart +++ b/packages/camera/camera_android/example/integration_test/camera_test.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'dart:ui'; +import 'package:camera_android/camera_android.dart'; import 'package:camera_example/camera_controller.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/painting.dart'; @@ -19,6 +20,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { + CameraPlatform.instance = AndroidCamera(); final Directory extDir = await getTemporaryDirectory(); testDir = await Directory('${extDir.path}/test').create(recursive: true); }); diff --git a/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart b/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart index 6ac7a6860dd9..51eab634c84b 100644 --- a/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart +++ b/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui'; +import 'package:camera_avfoundation/camera_avfoundation.dart'; import 'package:camera_example/camera_controller.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/painting.dart'; @@ -20,6 +21,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { + CameraPlatform.instance = AVFoundationCamera(); final Directory extDir = await getTemporaryDirectory(); testDir = await Directory('${extDir.path}/test').create(recursive: true); });