| // 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' show Point; |
| |
| import 'package:async/async.dart'; |
| import 'package:camera_android_camerax/camera_android_camerax.dart'; |
| import 'package:camera_android_camerax/src/analyzer.dart'; |
| import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart'; |
| import 'package:camera_android_camerax/src/camera.dart'; |
| import 'package:camera_android_camerax/src/camera2_camera_control.dart'; |
| import 'package:camera_android_camerax/src/camera_control.dart'; |
| import 'package:camera_android_camerax/src/camera_info.dart'; |
| import 'package:camera_android_camerax/src/camera_selector.dart'; |
| import 'package:camera_android_camerax/src/camera_state.dart'; |
| import 'package:camera_android_camerax/src/camera_state_error.dart'; |
| import 'package:camera_android_camerax/src/camerax_library.g.dart'; |
| import 'package:camera_android_camerax/src/camerax_proxy.dart'; |
| import 'package:camera_android_camerax/src/capture_request_options.dart'; |
| import 'package:camera_android_camerax/src/device_orientation_manager.dart'; |
| import 'package:camera_android_camerax/src/exposure_state.dart'; |
| import 'package:camera_android_camerax/src/fallback_strategy.dart'; |
| import 'package:camera_android_camerax/src/focus_metering_action.dart'; |
| import 'package:camera_android_camerax/src/focus_metering_result.dart'; |
| import 'package:camera_android_camerax/src/image_analysis.dart'; |
| import 'package:camera_android_camerax/src/image_capture.dart'; |
| import 'package:camera_android_camerax/src/image_proxy.dart'; |
| import 'package:camera_android_camerax/src/live_data.dart'; |
| import 'package:camera_android_camerax/src/metering_point.dart'; |
| import 'package:camera_android_camerax/src/observer.dart'; |
| import 'package:camera_android_camerax/src/pending_recording.dart'; |
| import 'package:camera_android_camerax/src/plane_proxy.dart'; |
| import 'package:camera_android_camerax/src/preview.dart'; |
| import 'package:camera_android_camerax/src/process_camera_provider.dart'; |
| import 'package:camera_android_camerax/src/quality_selector.dart'; |
| import 'package:camera_android_camerax/src/recorder.dart'; |
| import 'package:camera_android_camerax/src/recording.dart'; |
| import 'package:camera_android_camerax/src/resolution_filter.dart'; |
| import 'package:camera_android_camerax/src/resolution_selector.dart'; |
| import 'package:camera_android_camerax/src/resolution_strategy.dart'; |
| import 'package:camera_android_camerax/src/surface.dart'; |
| import 'package:camera_android_camerax/src/system_services.dart'; |
| import 'package:camera_android_camerax/src/use_case.dart'; |
| import 'package:camera_android_camerax/src/video_capture.dart'; |
| import 'package:camera_android_camerax/src/zoom_state.dart'; |
| import 'package:camera_platform_interface/camera_platform_interface.dart'; |
| import 'package:flutter/services.dart' |
| show DeviceOrientation, PlatformException, Uint8List; |
| import 'package:flutter/widgets.dart' show BuildContext, Size, Texture, Widget; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:mockito/annotations.dart'; |
| import 'package:mockito/mockito.dart'; |
| |
| import 'android_camera_camerax_test.mocks.dart'; |
| import 'test_camerax_library.g.dart'; |
| |
| @GenerateNiceMocks(<MockSpec<Object>>[ |
| MockSpec<Analyzer>(), |
| MockSpec<AspectRatioStrategy>(), |
| MockSpec<Camera>(), |
| MockSpec<CameraInfo>(), |
| MockSpec<CameraControl>(), |
| MockSpec<Camera2CameraControl>(), |
| MockSpec<CameraImageData>(), |
| MockSpec<CameraSelector>(), |
| MockSpec<ExposureState>(), |
| MockSpec<FallbackStrategy>(), |
| MockSpec<FocusMeteringResult>(), |
| MockSpec<ImageAnalysis>(), |
| MockSpec<ImageCapture>(), |
| MockSpec<ImageProxy>(), |
| MockSpec<Observer<CameraState>>(), |
| MockSpec<PendingRecording>(), |
| MockSpec<PlaneProxy>(), |
| MockSpec<Preview>(), |
| MockSpec<ProcessCameraProvider>(), |
| MockSpec<QualitySelector>(), |
| MockSpec<Recorder>(), |
| MockSpec<ResolutionFilter>(), |
| MockSpec<ResolutionSelector>(), |
| MockSpec<ResolutionStrategy>(), |
| MockSpec<Recording>(), |
| MockSpec<VideoCapture>(), |
| MockSpec<BuildContext>(), |
| MockSpec<TestInstanceManagerHostApi>(), |
| MockSpec<TestSystemServicesHostApi>(), |
| MockSpec<ZoomState>(), |
| ]) |
| @GenerateMocks(<Type>[], customMocks: <MockSpec<Object>>[ |
| MockSpec<LiveData<CameraState>>(as: #MockLiveCameraState), |
| MockSpec<LiveData<ZoomState>>(as: #MockLiveZoomState), |
| ]) |
| void main() { |
| TestWidgetsFlutterBinding.ensureInitialized(); |
| |
| // Mocks the call to clear the native InstanceManager. |
| TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); |
| |
| /// Helper method for testing sending/receiving CameraErrorEvents. |
| Future<bool> testCameraClosingObserver(AndroidCameraCameraX camera, |
| int cameraId, Observer<dynamic> observer) async { |
| final CameraStateError testCameraStateError = |
| CameraStateError.detached(code: 0); |
| final Stream<CameraClosingEvent> cameraClosingEventStream = |
| camera.onCameraClosing(cameraId); |
| final StreamQueue<CameraClosingEvent> cameraClosingStreamQueue = |
| StreamQueue<CameraClosingEvent>(cameraClosingEventStream); |
| final Stream<CameraErrorEvent> cameraErrorEventStream = |
| camera.onCameraError(cameraId); |
| final StreamQueue<CameraErrorEvent> cameraErrorStreamQueue = |
| StreamQueue<CameraErrorEvent>(cameraErrorEventStream); |
| |
| observer.onChanged(CameraState.detached( |
| type: CameraStateType.closing, error: testCameraStateError)); |
| |
| final bool cameraClosingEventSent = |
| await cameraClosingStreamQueue.next == CameraClosingEvent(cameraId); |
| final bool cameraErrorSent = await cameraErrorStreamQueue.next == |
| CameraErrorEvent(cameraId, testCameraStateError.getDescription()); |
| |
| await cameraClosingStreamQueue.cancel(); |
| await cameraErrorStreamQueue.cancel(); |
| |
| return cameraClosingEventSent && cameraErrorSent; |
| } |
| |
| /// CameraXProxy for testing functionality related to the camera resolution |
| /// preset (setting expected ResolutionSelectors, QualitySelectors, etc.). |
| CameraXProxy getProxyForTestingResolutionPreset( |
| MockProcessCameraProvider mockProcessCameraProvider) => |
| CameraXProxy( |
| getProcessCameraProvider: () => |
| Future<ProcessCameraProvider>.value(mockProcessCameraProvider), |
| createCameraSelector: (int cameraSelectorLensDirection) => |
| MockCameraSelector(), |
| createPreview: |
| (ResolutionSelector? resolutionSelector, int? targetRotation) => |
| Preview.detached( |
| initialTargetRotation: targetRotation, |
| resolutionSelector: resolutionSelector), |
| createImageCapture: |
| (ResolutionSelector? resolutionSelector, int? targetRotation) => |
| ImageCapture.detached( |
| resolutionSelector: resolutionSelector, |
| initialTargetRotation: targetRotation), |
| createRecorder: (QualitySelector? qualitySelector) => |
| Recorder.detached(qualitySelector: qualitySelector), |
| createVideoCapture: (_) => |
| Future<VideoCapture>.value(MockVideoCapture()), |
| createImageAnalysis: |
| (ResolutionSelector? resolutionSelector, int? targetRotation) => |
| ImageAnalysis.detached( |
| resolutionSelector: resolutionSelector, |
| initialTargetRotation: targetRotation), |
| createResolutionStrategy: ( |
| {bool highestAvailable = false, |
| Size? boundSize, |
| int? fallbackRule}) { |
| if (highestAvailable) { |
| return ResolutionStrategy.detachedHighestAvailableStrategy(); |
| } |
| return ResolutionStrategy.detached( |
| boundSize: boundSize, fallbackRule: fallbackRule); |
| }, |
| createResolutionSelector: (ResolutionStrategy resolutionStrategy, |
| ResolutionFilter? resolutionFilter, |
| AspectRatioStrategy? aspectRatioStrategy) => |
| ResolutionSelector.detached( |
| resolutionStrategy: resolutionStrategy, |
| resolutionFilter: resolutionFilter, |
| aspectRatioStrategy: aspectRatioStrategy), |
| createFallbackStrategy: ( |
| {required VideoQuality quality, |
| required VideoResolutionFallbackRule fallbackRule}) => |
| FallbackStrategy.detached( |
| quality: quality, fallbackRule: fallbackRule), |
| createQualitySelector: ( |
| {required VideoQuality videoQuality, |
| required FallbackStrategy fallbackStrategy}) => |
| QualitySelector.detached(qualityList: <VideoQualityData>[ |
| VideoQualityData(quality: videoQuality) |
| ], fallbackStrategy: fallbackStrategy), |
| createCameraStateObserver: (_) => MockObserver(), |
| requestCameraPermissions: (_) => Future<void>.value(), |
| startListeningForDeviceOrientationChange: (_, __) {}, |
| setPreviewSurfaceProvider: (_) => Future<int>.value( |
| 3), // 3 is a random Flutter SurfaceTexture ID for testing, |
| createAspectRatioStrategy: (int aspectRatio, int fallbackRule) => |
| AspectRatioStrategy.detached( |
| preferredAspectRatio: aspectRatio, fallbackRule: fallbackRule), |
| createResolutionFilterWithOnePreferredSize: |
| (Size preferredResolution) => |
| ResolutionFilter.onePreferredSizeDetached( |
| preferredResolution: preferredResolution), |
| ); |
| |
| /// CameraXProxy for testing exposure and focus related controls. |
| /// |
| /// Modifies the creation of [MeteringPoint]s and [FocusMeteringAction]s to |
| /// return objects detached from a native object. |
| CameraXProxy getProxyForExposureAndFocus() => CameraXProxy( |
| createMeteringPoint: |
| (double x, double y, double? size, CameraInfo cameraInfo) => |
| MeteringPoint.detached( |
| x: x, y: y, size: size, cameraInfo: cameraInfo), |
| createFocusMeteringAction: |
| (List<(MeteringPoint, int?)> meteringPointInfos, |
| bool? disableAutoCancel) => |
| FocusMeteringAction.detached( |
| meteringPointInfos: meteringPointInfos, |
| disableAutoCancel: disableAutoCancel), |
| ); |
| |
| /// CameraXProxy for testing setting focus and exposure points. |
| /// |
| /// Modifies the retrieval of a [Camera2CameraControl] instance to depend on |
| /// interaction with expected [cameraControl] instance and modifies creation |
| /// of [CaptureRequestOptions] to return objects detached from a native object. |
| CameraXProxy getProxyForSettingFocusandExposurePoints( |
| CameraControl cameraControlForComparison, |
| Camera2CameraControl camera2cameraControl) { |
| final CameraXProxy proxy = getProxyForExposureAndFocus(); |
| |
| proxy.getCamera2CameraControl = (CameraControl cameraControl) => |
| cameraControl == cameraControlForComparison |
| ? camera2cameraControl |
| : Camera2CameraControl.detached(cameraControl: cameraControl); |
| |
| proxy.createCaptureRequestOptions = |
| (List<(CaptureRequestKeySupportedType, Object?)> options) => |
| CaptureRequestOptions.detached(requestedOptions: options); |
| |
| return proxy; |
| } |
| |
| test('Should fetch CameraDescription instances for available cameras', |
| () async { |
| // Arrange |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final List<dynamic> returnData = <dynamic>[ |
| <String, dynamic>{ |
| 'name': 'Camera 0', |
| 'lensFacing': 'back', |
| 'sensorOrientation': 0 |
| }, |
| <String, dynamic>{ |
| 'name': 'Camera 1', |
| 'lensFacing': 'front', |
| 'sensorOrientation': 90 |
| } |
| ]; |
| |
| // Create mocks to use |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); |
| final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); |
| final MockCameraInfo mockFrontCameraInfo = MockCameraInfo(); |
| final MockCameraInfo mockBackCameraInfo = MockCameraInfo(); |
| |
| // Tell plugin to create mock CameraSelectors for testing. |
| camera.proxy = CameraXProxy( |
| getProcessCameraProvider: () => |
| Future<ProcessCameraProvider>.value(mockProcessCameraProvider), |
| createCameraSelector: (int cameraSelectorLensDirection) { |
| switch (cameraSelectorLensDirection) { |
| case CameraSelector.lensFacingFront: |
| return mockFrontCameraSelector; |
| case CameraSelector.lensFacingBack: |
| default: |
| return mockBackCameraSelector; |
| } |
| }, |
| ); |
| |
| // Mock calls to native platform |
| when(mockProcessCameraProvider.getAvailableCameraInfos()).thenAnswer( |
| (_) async => <MockCameraInfo>[mockBackCameraInfo, mockFrontCameraInfo]); |
| when(mockBackCameraSelector.filter(<MockCameraInfo>[mockFrontCameraInfo])) |
| .thenAnswer((_) async => <MockCameraInfo>[]); |
| when(mockBackCameraSelector.filter(<MockCameraInfo>[mockBackCameraInfo])) |
| .thenAnswer((_) async => <MockCameraInfo>[mockBackCameraInfo]); |
| when(mockFrontCameraSelector.filter(<MockCameraInfo>[mockBackCameraInfo])) |
| .thenAnswer((_) async => <MockCameraInfo>[]); |
| when(mockFrontCameraSelector.filter(<MockCameraInfo>[mockFrontCameraInfo])) |
| .thenAnswer((_) async => <MockCameraInfo>[mockFrontCameraInfo]); |
| when(mockBackCameraInfo.getSensorRotationDegrees()) |
| .thenAnswer((_) async => 0); |
| when(mockFrontCameraInfo.getSensorRotationDegrees()) |
| .thenAnswer((_) async => 90); |
| |
| final List<CameraDescription> cameraDescriptions = |
| await camera.availableCameras(); |
| |
| expect(cameraDescriptions.length, returnData.length); |
| for (int i = 0; i < returnData.length; i++) { |
| final Map<String, Object?> typedData = |
| (returnData[i] as Map<dynamic, dynamic>).cast<String, Object?>(); |
| final CameraDescription cameraDescription = CameraDescription( |
| name: typedData['name']! as String, |
| lensDirection: (typedData['lensFacing']! as String) == 'front' |
| ? CameraLensDirection.front |
| : CameraLensDirection.back, |
| sensorOrientation: typedData['sensorOrientation']! as int, |
| ); |
| expect(cameraDescriptions[i], cameraDescription); |
| } |
| }); |
| |
| test( |
| 'createCamera requests permissions, starts listening for device orientation changes, updates camera state observers, and returns flutter surface texture ID', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const CameraLensDirection testLensDirection = CameraLensDirection.back; |
| const int testSensorOrientation = 90; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| |
| const int testSurfaceTextureId = 6; |
| |
| // Mock/Detached objects for (typically attached) objects created by |
| // createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockPreview mockPreview = MockPreview(); |
| final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); |
| final MockImageCapture mockImageCapture = MockImageCapture(); |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| final MockRecorder mockRecorder = MockRecorder(); |
| final MockVideoCapture mockVideoCapture = MockVideoCapture(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); |
| bool cameraPermissionsRequested = false; |
| bool startedListeningForDeviceOrientationChanges = false; |
| |
| // Tell plugin to create mock/detached objects and stub method calls for the |
| // testing of createCamera. |
| camera.proxy = CameraXProxy( |
| getProcessCameraProvider: () => |
| Future<ProcessCameraProvider>.value(mockProcessCameraProvider), |
| createCameraSelector: (int cameraSelectorLensDirection) { |
| switch (cameraSelectorLensDirection) { |
| case CameraSelector.lensFacingFront: |
| return MockCameraSelector(); |
| case CameraSelector.lensFacingBack: |
| default: |
| return mockBackCameraSelector; |
| } |
| }, |
| createPreview: (_, __) => mockPreview, |
| createImageCapture: (_, __) => mockImageCapture, |
| createRecorder: (_) => mockRecorder, |
| createVideoCapture: (_) => Future<VideoCapture>.value(mockVideoCapture), |
| createImageAnalysis: (_, __) => mockImageAnalysis, |
| createResolutionStrategy: ( |
| {bool highestAvailable = false, |
| Size? boundSize, |
| int? fallbackRule}) => |
| MockResolutionStrategy(), |
| createResolutionSelector: (_, __, ___) => MockResolutionSelector(), |
| createFallbackStrategy: ( |
| {required VideoQuality quality, |
| required VideoResolutionFallbackRule fallbackRule}) => |
| MockFallbackStrategy(), |
| createQualitySelector: ( |
| {required VideoQuality videoQuality, |
| required FallbackStrategy fallbackStrategy}) => |
| MockQualitySelector(), |
| createCameraStateObserver: (void Function(Object) onChanged) => |
| Observer<CameraState>.detached(onChanged: onChanged), |
| requestCameraPermissions: (_) { |
| cameraPermissionsRequested = true; |
| return Future<void>.value(); |
| }, |
| startListeningForDeviceOrientationChange: (_, __) { |
| startedListeningForDeviceOrientationChanges = true; |
| }, |
| createAspectRatioStrategy: (_, __) => MockAspectRatioStrategy(), |
| createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), |
| ); |
| |
| when(mockPreview.setSurfaceProvider()) |
| .thenAnswer((_) async => testSurfaceTextureId); |
| when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, |
| <UseCase>[mockPreview, mockImageCapture, mockImageAnalysis])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => mockLiveCameraState); |
| camera.processCameraProvider = mockProcessCameraProvider; |
| |
| expect( |
| await camera.createCameraWithSettings( |
| testCameraDescription, |
| const MediaSettings( |
| resolutionPreset: ResolutionPreset.low, |
| fps: 15, |
| videoBitrate: 200000, |
| audioBitrate: 32000, |
| enableAudio: true, |
| ), |
| ), |
| equals(testSurfaceTextureId)); |
| |
| // Verify permissions are requested and the camera starts listening for device orientation changes. |
| expect(cameraPermissionsRequested, isTrue); |
| expect(startedListeningForDeviceOrientationChanges, isTrue); |
| |
| // Verify CameraSelector is set with appropriate lens direction. |
| expect(camera.cameraSelector, equals(mockBackCameraSelector)); |
| |
| // Verify the camera's Preview instance is instantiated properly. |
| expect(camera.preview, equals(mockPreview)); |
| |
| // Verify the camera's ImageCapture instance is instantiated properly. |
| expect(camera.imageCapture, equals(mockImageCapture)); |
| |
| // Verify the camera's Recorder and VideoCapture instances are instantiated properly. |
| expect(camera.recorder, equals(mockRecorder)); |
| expect(camera.videoCapture, equals(mockVideoCapture)); |
| |
| // Verify the camera's Preview instance has its surface provider set. |
| verify(camera.preview!.setSurfaceProvider()); |
| |
| // Verify the camera state observer is updated. |
| expect( |
| await testCameraClosingObserver( |
| camera, |
| testSurfaceTextureId, |
| verify(mockLiveCameraState.observe(captureAny)).captured.single |
| as Observer<CameraState>), |
| isTrue); |
| }); |
| |
| test( |
| 'createCamera binds Preview and ImageCapture use cases to ProcessCameraProvider instance', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const CameraLensDirection testLensDirection = CameraLensDirection.back; |
| const int testSensorOrientation = 90; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; |
| const bool enableAudio = true; |
| |
| // Mock/Detached objects for (typically attached) objects created by |
| // createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockPreview mockPreview = MockPreview(); |
| final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); |
| final MockImageCapture mockImageCapture = MockImageCapture(); |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| final MockRecorder mockRecorder = MockRecorder(); |
| final MockVideoCapture mockVideoCapture = MockVideoCapture(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| |
| // Tell plugin to create mock/detached objects and stub method calls for the |
| // testing of createCamera. |
| camera.proxy = CameraXProxy( |
| getProcessCameraProvider: () => |
| Future<ProcessCameraProvider>.value(mockProcessCameraProvider), |
| createCameraSelector: (int cameraSelectorLensDirection) { |
| switch (cameraSelectorLensDirection) { |
| case CameraSelector.lensFacingFront: |
| return MockCameraSelector(); |
| case CameraSelector.lensFacingBack: |
| default: |
| return mockBackCameraSelector; |
| } |
| }, |
| createPreview: (_, __) => mockPreview, |
| createImageCapture: (_, __) => mockImageCapture, |
| createRecorder: (_) => mockRecorder, |
| createVideoCapture: (_) => Future<VideoCapture>.value(mockVideoCapture), |
| createImageAnalysis: (_, __) => mockImageAnalysis, |
| createResolutionStrategy: ( |
| {bool highestAvailable = false, |
| Size? boundSize, |
| int? fallbackRule}) => |
| MockResolutionStrategy(), |
| createResolutionSelector: (_, __, ___) => MockResolutionSelector(), |
| createFallbackStrategy: ( |
| {required VideoQuality quality, |
| required VideoResolutionFallbackRule fallbackRule}) => |
| MockFallbackStrategy(), |
| createQualitySelector: ( |
| {required VideoQuality videoQuality, |
| required FallbackStrategy fallbackStrategy}) => |
| MockQualitySelector(), |
| createCameraStateObserver: (void Function(Object) onChanged) => |
| Observer<CameraState>.detached(onChanged: onChanged), |
| requestCameraPermissions: (_) => Future<void>.value(), |
| startListeningForDeviceOrientationChange: (_, __) {}, |
| createAspectRatioStrategy: (_, __) => MockAspectRatioStrategy(), |
| createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), |
| ); |
| |
| when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, |
| <UseCase>[mockPreview, mockImageCapture, mockImageAnalysis])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| when(mockCamera.getCameraControl()) |
| .thenAnswer((_) async => mockCameraControl); |
| camera.processCameraProvider = mockProcessCameraProvider; |
| |
| await camera.createCameraWithSettings( |
| testCameraDescription, |
| const MediaSettings( |
| resolutionPreset: testResolutionPreset, |
| fps: 15, |
| videoBitrate: 2000000, |
| audioBitrate: 64000, |
| enableAudio: enableAudio, |
| )); |
| |
| // Verify expected UseCases were bound. |
| verify(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!, |
| <UseCase>[mockPreview, mockImageCapture, mockImageAnalysis])); |
| |
| // Verify the camera's CameraInfo instance got updated. |
| expect(camera.cameraInfo, equals(mockCameraInfo)); |
| |
| // Verify camera's CameraControl instance got updated. |
| expect(camera.cameraControl, equals(mockCameraControl)); |
| |
| // Verify preview has been marked as bound to the camera lifecycle by |
| // createCamera. |
| expect(camera.previewInitiallyBound, isTrue); |
| }); |
| |
| test( |
| 'createCamera properly sets preset resolution selection strategy for non-video capture use cases', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const CameraLensDirection testLensDirection = CameraLensDirection.back; |
| const int testSensorOrientation = 90; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| const bool enableAudio = true; |
| final MockCamera mockCamera = MockCamera(); |
| |
| // Mock/Detached objects for (typically attached) objects created by |
| // createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Tell plugin to create mock/detached objects for testing createCamera |
| // as needed. |
| camera.proxy = |
| getProxyForTestingResolutionPreset(mockProcessCameraProvider); |
| |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| camera.processCameraProvider = mockProcessCameraProvider; |
| |
| // Test non-null resolution presets. |
| for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) { |
| await camera.createCamera( |
| testCameraDescription, |
| resolutionPreset, |
| enableAudio: enableAudio, |
| ); |
| |
| Size? expectedBoundSize; |
| ResolutionStrategy? expectedResolutionStrategy; |
| switch (resolutionPreset) { |
| case ResolutionPreset.low: |
| expectedBoundSize = const Size(320, 240); |
| case ResolutionPreset.medium: |
| expectedBoundSize = const Size(720, 480); |
| case ResolutionPreset.high: |
| expectedBoundSize = const Size(1280, 720); |
| case ResolutionPreset.veryHigh: |
| expectedBoundSize = const Size(1920, 1080); |
| case ResolutionPreset.ultraHigh: |
| expectedBoundSize = const Size(3840, 2160); |
| case ResolutionPreset.max: |
| expectedResolutionStrategy = |
| ResolutionStrategy.detachedHighestAvailableStrategy(); |
| } |
| |
| // We expect the strategy to be the highest available or correspond to the |
| // expected bound size, with fallback to the closest and highest available |
| // resolution. |
| expectedResolutionStrategy ??= ResolutionStrategy.detached( |
| boundSize: expectedBoundSize, |
| fallbackRule: ResolutionStrategy.fallbackRuleClosestLowerThenHigher); |
| |
| expect(camera.preview!.resolutionSelector!.resolutionStrategy!.boundSize, |
| equals(expectedResolutionStrategy.boundSize)); |
| expect( |
| camera |
| .imageCapture!.resolutionSelector!.resolutionStrategy!.boundSize, |
| equals(expectedResolutionStrategy.boundSize)); |
| expect( |
| camera |
| .imageAnalysis!.resolutionSelector!.resolutionStrategy!.boundSize, |
| equals(expectedResolutionStrategy.boundSize)); |
| expect( |
| camera.preview!.resolutionSelector!.resolutionStrategy!.fallbackRule, |
| equals(expectedResolutionStrategy.fallbackRule)); |
| expect( |
| camera.imageCapture!.resolutionSelector!.resolutionStrategy! |
| .fallbackRule, |
| equals(expectedResolutionStrategy.fallbackRule)); |
| expect( |
| camera.imageAnalysis!.resolutionSelector!.resolutionStrategy! |
| .fallbackRule, |
| equals(expectedResolutionStrategy.fallbackRule)); |
| } |
| |
| // Test null case. |
| await camera.createCamera(testCameraDescription, null); |
| expect(camera.preview!.resolutionSelector, isNull); |
| expect(camera.imageCapture!.resolutionSelector, isNull); |
| expect(camera.imageAnalysis!.resolutionSelector, isNull); |
| }); |
| |
| test( |
| 'createCamera properly sets filter for resolution preset for non-video capture use cases', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const CameraLensDirection testLensDirection = CameraLensDirection.front; |
| const int testSensorOrientation = 180; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| const bool enableAudio = true; |
| final MockCamera mockCamera = MockCamera(); |
| |
| // Mock/Detached objects for (typically attached) objects created by |
| // createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Tell plugin to create mock/detached objects for testing createCamera |
| // as needed. |
| camera.proxy = |
| getProxyForTestingResolutionPreset(mockProcessCameraProvider); |
| |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| camera.processCameraProvider = mockProcessCameraProvider; |
| |
| // Test non-null resolution presets. |
| for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) { |
| await camera.createCamera(testCameraDescription, resolutionPreset, |
| enableAudio: enableAudio); |
| |
| Size? expectedPreferredResolution; |
| switch (resolutionPreset) { |
| case ResolutionPreset.low: |
| expectedPreferredResolution = const Size(320, 240); |
| case ResolutionPreset.medium: |
| expectedPreferredResolution = const Size(720, 480); |
| case ResolutionPreset.high: |
| expectedPreferredResolution = const Size(1280, 720); |
| case ResolutionPreset.veryHigh: |
| expectedPreferredResolution = const Size(1920, 1080); |
| case ResolutionPreset.ultraHigh: |
| expectedPreferredResolution = const Size(3840, 2160); |
| case ResolutionPreset.max: |
| expectedPreferredResolution = null; |
| } |
| |
| if (expectedPreferredResolution == null) { |
| expect(camera.preview!.resolutionSelector!.resolutionFilter, isNull); |
| expect( |
| camera.imageCapture!.resolutionSelector!.resolutionFilter, isNull); |
| expect( |
| camera.imageAnalysis!.resolutionSelector!.resolutionFilter, isNull); |
| continue; |
| } |
| |
| expect( |
| camera.preview!.resolutionSelector!.resolutionFilter! |
| .preferredResolution, |
| equals(expectedPreferredResolution)); |
| expect( |
| camera |
| .imageCapture!.resolutionSelector!.resolutionStrategy!.boundSize, |
| equals(expectedPreferredResolution)); |
| expect( |
| camera |
| .imageAnalysis!.resolutionSelector!.resolutionStrategy!.boundSize, |
| equals(expectedPreferredResolution)); |
| } |
| |
| // Test null case. |
| await camera.createCamera(testCameraDescription, null); |
| expect(camera.preview!.resolutionSelector, isNull); |
| expect(camera.imageCapture!.resolutionSelector, isNull); |
| expect(camera.imageAnalysis!.resolutionSelector, isNull); |
| }); |
| |
| test( |
| 'createCamera properly sets aspect ratio based on preset resolution for non-video capture use cases', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const CameraLensDirection testLensDirection = CameraLensDirection.back; |
| const int testSensorOrientation = 90; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| const bool enableAudio = true; |
| final MockCamera mockCamera = MockCamera(); |
| |
| // Mock/Detached objects for (typically attached) objects created by |
| // createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Tell plugin to create mock/detached objects for testing createCamera |
| // as needed. |
| camera.proxy = |
| getProxyForTestingResolutionPreset(mockProcessCameraProvider); |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| camera.processCameraProvider = mockProcessCameraProvider; |
| |
| // Test non-null resolution presets. |
| for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) { |
| await camera.createCamera(testCameraDescription, resolutionPreset, |
| enableAudio: enableAudio); |
| |
| int? expectedAspectRatio; |
| AspectRatioStrategy? expectedAspectRatioStrategy; |
| switch (resolutionPreset) { |
| case ResolutionPreset.low: |
| expectedAspectRatio = AspectRatio.ratio4To3; |
| case ResolutionPreset.high: |
| case ResolutionPreset.veryHigh: |
| case ResolutionPreset.ultraHigh: |
| expectedAspectRatio = AspectRatio.ratio16To9; |
| case ResolutionPreset.medium: |
| // Medium resolution preset uses aspect ratio 3:2 which is unsupported |
| // by CameraX. |
| case ResolutionPreset.max: |
| expectedAspectRatioStrategy = null; |
| } |
| |
| expectedAspectRatioStrategy = expectedAspectRatio == null |
| ? null |
| : AspectRatioStrategy.detached( |
| preferredAspectRatio: expectedAspectRatio, |
| fallbackRule: AspectRatioStrategy.fallbackRuleAuto); |
| |
| if (expectedAspectRatio == null) { |
| expect(camera.preview!.resolutionSelector!.aspectRatioStrategy, isNull); |
| expect(camera.imageCapture!.resolutionSelector!.aspectRatioStrategy, |
| isNull); |
| expect(camera.imageAnalysis!.resolutionSelector!.aspectRatioStrategy, |
| isNull); |
| continue; |
| } |
| |
| // Check aspect ratio. |
| expect( |
| camera.preview!.resolutionSelector!.aspectRatioStrategy! |
| .preferredAspectRatio, |
| equals(expectedAspectRatioStrategy!.preferredAspectRatio)); |
| expect( |
| camera.imageCapture!.resolutionSelector!.aspectRatioStrategy! |
| .preferredAspectRatio, |
| equals(expectedAspectRatioStrategy.preferredAspectRatio)); |
| expect( |
| camera.imageAnalysis!.resolutionSelector!.aspectRatioStrategy! |
| .preferredAspectRatio, |
| equals(expectedAspectRatioStrategy.preferredAspectRatio)); |
| |
| // Check fallback rule. |
| expect( |
| camera.preview!.resolutionSelector!.aspectRatioStrategy!.fallbackRule, |
| equals(expectedAspectRatioStrategy.fallbackRule)); |
| expect( |
| camera.imageCapture!.resolutionSelector!.aspectRatioStrategy! |
| .fallbackRule, |
| equals(expectedAspectRatioStrategy.fallbackRule)); |
| expect( |
| camera.imageAnalysis!.resolutionSelector!.aspectRatioStrategy! |
| .fallbackRule, |
| equals(expectedAspectRatioStrategy.fallbackRule)); |
| } |
| |
| // Test null case. |
| await camera.createCamera(testCameraDescription, null); |
| expect(camera.preview!.resolutionSelector, isNull); |
| expect(camera.imageCapture!.resolutionSelector, isNull); |
| expect(camera.imageAnalysis!.resolutionSelector, isNull); |
| }); |
| |
| test( |
| 'createCamera properly sets preset resolution for video capture use case', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const CameraLensDirection testLensDirection = CameraLensDirection.back; |
| const int testSensorOrientation = 90; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| const bool enableAudio = true; |
| final MockCamera mockCamera = MockCamera(); |
| |
| // Mock/Detached objects for (typically attached) objects created by |
| // createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Tell plugin to create mock/detached objects for testing createCamera |
| // as needed. |
| camera.proxy = |
| getProxyForTestingResolutionPreset(mockProcessCameraProvider); |
| |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| |
| // Test non-null resolution presets. |
| for (final ResolutionPreset resolutionPreset in ResolutionPreset.values) { |
| await camera.createCamera(testCameraDescription, resolutionPreset, |
| enableAudio: enableAudio); |
| |
| VideoQuality? expectedVideoQuality; |
| switch (resolutionPreset) { |
| case ResolutionPreset.low: |
| // 240p is not supported by CameraX. |
| case ResolutionPreset.medium: |
| expectedVideoQuality = VideoQuality.SD; |
| case ResolutionPreset.high: |
| expectedVideoQuality = VideoQuality.HD; |
| case ResolutionPreset.veryHigh: |
| expectedVideoQuality = VideoQuality.FHD; |
| case ResolutionPreset.ultraHigh: |
| expectedVideoQuality = VideoQuality.UHD; |
| case ResolutionPreset.max: |
| expectedVideoQuality = VideoQuality.highest; |
| } |
| |
| const VideoResolutionFallbackRule expectedFallbackRule = |
| VideoResolutionFallbackRule.lowerQualityOrHigherThan; |
| final FallbackStrategy expectedFallbackStrategy = |
| FallbackStrategy.detached( |
| quality: expectedVideoQuality, |
| fallbackRule: expectedFallbackRule); |
| |
| expect(camera.recorder!.qualitySelector!.qualityList.length, equals(1)); |
| expect(camera.recorder!.qualitySelector!.qualityList.first.quality, |
| equals(expectedVideoQuality)); |
| expect(camera.recorder!.qualitySelector!.fallbackStrategy!.quality, |
| equals(expectedFallbackStrategy.quality)); |
| expect(camera.recorder!.qualitySelector!.fallbackStrategy!.fallbackRule, |
| equals(expectedFallbackStrategy.fallbackRule)); |
| } |
| |
| // Test null case. |
| await camera.createCamera(testCameraDescription, null); |
| expect(camera.recorder!.qualitySelector, isNull); |
| }); |
| |
| test( |
| 'initializeCamera throws a CameraException when createCamera has not been called before initializedCamera', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| await expectLater(() async { |
| await camera.initializeCamera(3); |
| }, throwsA(isA<CameraException>())); |
| }); |
| |
| test('initializeCamera sends expected CameraInitializedEvent', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| |
| const int cameraId = 10; |
| const CameraLensDirection testLensDirection = CameraLensDirection.back; |
| const int testSensorOrientation = 90; |
| const CameraDescription testCameraDescription = CameraDescription( |
| name: 'cameraName', |
| lensDirection: testLensDirection, |
| sensorOrientation: testSensorOrientation); |
| const int resolutionWidth = 350; |
| const int resolutionHeight = 750; |
| final Camera mockCamera = MockCamera(); |
| final ResolutionInfo testResolutionInfo = |
| ResolutionInfo(width: resolutionWidth, height: resolutionHeight); |
| |
| // Mocks for (typically attached) objects created by createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final CameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); |
| final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); |
| final MockPreview mockPreview = MockPreview(); |
| final MockImageCapture mockImageCapture = MockImageCapture(); |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| |
| // Tell plugin to create mock/detached objects for testing createCamera |
| // as needed. |
| camera.proxy = CameraXProxy( |
| getProcessCameraProvider: () => |
| Future<ProcessCameraProvider>.value(mockProcessCameraProvider), |
| createCameraSelector: (int cameraSelectorLensDirection) { |
| switch (cameraSelectorLensDirection) { |
| case CameraSelector.lensFacingFront: |
| return mockFrontCameraSelector; |
| case CameraSelector.lensFacingBack: |
| default: |
| return mockBackCameraSelector; |
| } |
| }, |
| createPreview: (_, __) => mockPreview, |
| createImageCapture: (_, __) => mockImageCapture, |
| createRecorder: (QualitySelector? qualitySelector) => MockRecorder(), |
| createVideoCapture: (_) => Future<VideoCapture>.value(MockVideoCapture()), |
| createImageAnalysis: (_, __) => mockImageAnalysis, |
| createResolutionStrategy: ( |
| {bool highestAvailable = false, |
| Size? boundSize, |
| int? fallbackRule}) => |
| MockResolutionStrategy(), |
| createResolutionSelector: (_, __, ___) => MockResolutionSelector(), |
| createFallbackStrategy: ( |
| {required VideoQuality quality, |
| required VideoResolutionFallbackRule fallbackRule}) => |
| MockFallbackStrategy(), |
| createQualitySelector: ( |
| {required VideoQuality videoQuality, |
| required FallbackStrategy fallbackStrategy}) => |
| MockQualitySelector(), |
| createCameraStateObserver: (void Function(Object) onChanged) => |
| Observer<CameraState>.detached(onChanged: onChanged), |
| requestCameraPermissions: (_) => Future<void>.value(), |
| startListeningForDeviceOrientationChange: (_, __) {}, |
| createAspectRatioStrategy: (_, __) => MockAspectRatioStrategy(), |
| createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), |
| ); |
| |
| final CameraInitializedEvent testCameraInitializedEvent = |
| CameraInitializedEvent( |
| cameraId, |
| resolutionWidth.toDouble(), |
| resolutionHeight.toDouble(), |
| ExposureMode.auto, |
| true, |
| FocusMode.auto, |
| true); |
| |
| // Call createCamera. |
| when(mockPreview.setSurfaceProvider()).thenAnswer((_) async => cameraId); |
| |
| when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, |
| <UseCase>[mockPreview, mockImageCapture, mockImageAnalysis])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| when(mockPreview.getResolutionInfo()) |
| .thenAnswer((_) async => testResolutionInfo); |
| |
| await camera.createCameraWithSettings( |
| testCameraDescription, |
| const MediaSettings( |
| resolutionPreset: ResolutionPreset.medium, |
| fps: 15, |
| videoBitrate: 200000, |
| audioBitrate: 32000, |
| enableAudio: true, |
| ), |
| ); |
| |
| // Start listening to camera events stream to verify the proper CameraInitializedEvent is sent. |
| camera.cameraEventStreamController.stream.listen((CameraEvent event) { |
| expect(event, const TypeMatcher<CameraInitializedEvent>()); |
| expect(event, equals(testCameraInitializedEvent)); |
| }); |
| |
| await camera.initializeCamera(cameraId); |
| |
| // Check camera instance was received. |
| expect(camera.camera, isNotNull); |
| }); |
| |
| test( |
| 'dispose releases Flutter surface texture, removes camera state observers, and unbinds all use cases', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| |
| camera.preview = MockPreview(); |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.liveCameraState = MockLiveCameraState(); |
| camera.imageAnalysis = MockImageAnalysis(); |
| |
| await camera.dispose(3); |
| |
| verify(camera.preview!.releaseFlutterSurfaceTexture()); |
| verify(camera.liveCameraState!.removeObservers()); |
| verify(camera.processCameraProvider!.unbindAll()); |
| verify(camera.imageAnalysis!.clearAnalyzer()); |
| }); |
| |
| test('onCameraInitialized stream emits CameraInitializedEvents', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 16; |
| final Stream<CameraInitializedEvent> eventStream = |
| camera.onCameraInitialized(cameraId); |
| final StreamQueue<CameraInitializedEvent> streamQueue = |
| StreamQueue<CameraInitializedEvent>(eventStream); |
| const CameraInitializedEvent testEvent = CameraInitializedEvent( |
| cameraId, 320, 80, ExposureMode.auto, false, FocusMode.auto, false); |
| |
| camera.cameraEventStreamController.add(testEvent); |
| |
| expect(await streamQueue.next, testEvent); |
| await streamQueue.cancel(); |
| }); |
| |
| test( |
| 'onCameraClosing stream emits camera closing event when cameraEventStreamController emits a camera closing event', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 99; |
| const CameraClosingEvent cameraClosingEvent = CameraClosingEvent(cameraId); |
| final Stream<CameraClosingEvent> eventStream = |
| camera.onCameraClosing(cameraId); |
| final StreamQueue<CameraClosingEvent> streamQueue = |
| StreamQueue<CameraClosingEvent>(eventStream); |
| |
| camera.cameraEventStreamController.add(cameraClosingEvent); |
| |
| expect(await streamQueue.next, equals(cameraClosingEvent)); |
| await streamQueue.cancel(); |
| }); |
| |
| test( |
| 'onCameraError stream emits errors caught by system services or added to stream within plugin', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 27; |
| const String firstTestErrorDescription = 'Test error description 1!'; |
| const String secondTestErrorDescription = 'Test error description 2!'; |
| const CameraErrorEvent secondCameraErrorEvent = |
| CameraErrorEvent(cameraId, secondTestErrorDescription); |
| final Stream<CameraErrorEvent> eventStream = camera.onCameraError(cameraId); |
| final StreamQueue<CameraErrorEvent> streamQueue = |
| StreamQueue<CameraErrorEvent>(eventStream); |
| |
| SystemServices.cameraErrorStreamController.add(firstTestErrorDescription); |
| expect(await streamQueue.next, |
| equals(const CameraErrorEvent(cameraId, firstTestErrorDescription))); |
| |
| camera.cameraEventStreamController.add(secondCameraErrorEvent); |
| expect(await streamQueue.next, equals(secondCameraErrorEvent)); |
| |
| await streamQueue.cancel(); |
| }); |
| |
| test( |
| 'onDeviceOrientationChanged stream emits changes in device orientation detected by system services', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final Stream<DeviceOrientationChangedEvent> eventStream = |
| camera.onDeviceOrientationChanged(); |
| final StreamQueue<DeviceOrientationChangedEvent> streamQueue = |
| StreamQueue<DeviceOrientationChangedEvent>(eventStream); |
| const DeviceOrientationChangedEvent testEvent = |
| DeviceOrientationChangedEvent(DeviceOrientation.portraitDown); |
| |
| DeviceOrientationManager.deviceOrientationChangedStreamController |
| .add(testEvent); |
| |
| expect(await streamQueue.next, testEvent); |
| await streamQueue.cancel(); |
| }); |
| |
| test( |
| 'pausePreview unbinds preview from lifecycle when preview is nonnull and has been bound to lifecycle', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.preview = MockPreview(); |
| |
| when(camera.processCameraProvider!.isBound(camera.preview!)) |
| .thenAnswer((_) async => true); |
| |
| await camera.pausePreview(579); |
| |
| verify(camera.processCameraProvider!.unbind(<UseCase>[camera.preview!])); |
| }); |
| |
| test( |
| 'pausePreview does not unbind preview from lifecycle when preview has not been bound to lifecycle', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.preview = MockPreview(); |
| |
| await camera.pausePreview(632); |
| |
| verifyNever( |
| camera.processCameraProvider!.unbind(<UseCase>[camera.preview!])); |
| }); |
| |
| test( |
| 'resumePreview does not bind preview to lifecycle or update camera state observers if already bound', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = mockProcessCameraProvider; |
| camera.cameraSelector = MockCameraSelector(); |
| camera.preview = MockPreview(); |
| |
| when(camera.processCameraProvider!.isBound(camera.preview!)) |
| .thenAnswer((_) async => true); |
| |
| when(mockProcessCameraProvider |
| .bindToLifecycle(camera.cameraSelector, <UseCase>[camera.preview!])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => mockLiveCameraState); |
| |
| await camera.resumePreview(78); |
| |
| verifyNever(camera.processCameraProvider! |
| .bindToLifecycle(camera.cameraSelector!, <UseCase>[camera.preview!])); |
| verifyNever(mockLiveCameraState.observe(any)); |
| expect(camera.cameraInfo, isNot(mockCameraInfo)); |
| }); |
| |
| test( |
| 'resumePreview binds preview to lifecycle and updates camera state observers if not already bound', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = mockProcessCameraProvider; |
| camera.cameraSelector = MockCameraSelector(); |
| camera.preview = MockPreview(); |
| |
| // Tell plugin to create a detached Observer<CameraState>, that is created to |
| // track camera state once preview is bound to the lifecycle and needed to |
| // test for expected updates. |
| camera.proxy = CameraXProxy( |
| createCameraStateObserver: |
| (void Function(Object stateAsObject) onChanged) => |
| Observer<CameraState>.detached(onChanged: onChanged)); |
| |
| when(mockProcessCameraProvider |
| .bindToLifecycle(camera.cameraSelector, <UseCase>[camera.preview!])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => mockLiveCameraState); |
| when(mockCamera.getCameraControl()) |
| .thenAnswer((_) async => mockCameraControl); |
| |
| await camera.resumePreview(78); |
| |
| verify(camera.processCameraProvider! |
| .bindToLifecycle(camera.cameraSelector!, <UseCase>[camera.preview!])); |
| expect( |
| await testCameraClosingObserver( |
| camera, |
| 78, |
| verify(mockLiveCameraState.observe(captureAny)).captured.single |
| as Observer<dynamic>), |
| isTrue); |
| expect(camera.cameraInfo, equals(mockCameraInfo)); |
| expect(camera.cameraControl, equals(mockCameraControl)); |
| }); |
| |
| test( |
| 'buildPreview throws an exception if the preview is not bound to the lifecycle', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 73; |
| |
| // Tell camera that createCamera has not been called and thus, preview has |
| // not been bound to the lifecycle of the camera. |
| camera.previewInitiallyBound = false; |
| |
| expect( |
| () => camera.buildPreview(cameraId), throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'buildPreview returns a Texture once the preview is bound to the lifecycle', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 37; |
| |
| // Tell camera that createCamera has been called and thus, preview has been |
| // bound to the lifecycle of the camera. |
| camera.previewInitiallyBound = true; |
| |
| final Widget widget = camera.buildPreview(cameraId); |
| |
| expect(widget is Texture, isTrue); |
| expect((widget as Texture).textureId, cameraId); |
| }); |
| |
| group('video recording', () { |
| test( |
| 'startVideoCapturing binds video capture use case, updates saved camera instance and its properties, and starts the recording', |
| () async { |
| // Set up mocks and constants. |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockPendingRecording mockPendingRecording = MockPendingRecording(); |
| final MockRecording mockRecording = MockRecording(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCamera newMockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); |
| final MockLiveCameraState newMockLiveCameraState = MockLiveCameraState(); |
| final TestSystemServicesHostApi mockSystemServicesApi = |
| MockTestSystemServicesHostApi(); |
| TestSystemServicesHostApi.setup(mockSystemServicesApi); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.camera = mockCamera; |
| camera.recorder = MockRecorder(); |
| camera.videoCapture = MockVideoCapture(); |
| camera.cameraSelector = MockCameraSelector(); |
| camera.liveCameraState = mockLiveCameraState; |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| // Tell plugin to create detached Observer when camera info updated. |
| camera.proxy = CameraXProxy( |
| createCameraStateObserver: (void Function(Object) onChanged) => |
| Observer<CameraState>.detached(onChanged: onChanged)); |
| |
| const int cameraId = 17; |
| const String outputPath = '/temp/MOV123.temp'; |
| |
| // Mock method calls. |
| when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) |
| .thenReturn(outputPath); |
| when(camera.recorder!.prepareRecording(outputPath)) |
| .thenAnswer((_) async => mockPendingRecording); |
| when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); |
| when(camera.processCameraProvider!.isBound(camera.videoCapture!)) |
| .thenAnswer((_) async => false); |
| when(camera.processCameraProvider!.bindToLifecycle( |
| camera.cameraSelector!, <UseCase>[camera.videoCapture!])) |
| .thenAnswer((_) async => newMockCamera); |
| when(newMockCamera.getCameraInfo()) |
| .thenAnswer((_) async => mockCameraInfo); |
| when(newMockCamera.getCameraControl()) |
| .thenAnswer((_) async => mockCameraControl); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => newMockLiveCameraState); |
| |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| |
| // Verify VideoCapture UseCase is bound and camera & its properties |
| // are updated. |
| verify(camera.processCameraProvider!.bindToLifecycle( |
| camera.cameraSelector!, <UseCase>[camera.videoCapture!])); |
| expect(camera.camera, equals(newMockCamera)); |
| expect(camera.cameraInfo, equals(mockCameraInfo)); |
| expect(camera.cameraControl, equals(mockCameraControl)); |
| verify(mockLiveCameraState.removeObservers()); |
| expect( |
| await testCameraClosingObserver( |
| camera, |
| cameraId, |
| verify(newMockLiveCameraState.observe(captureAny)).captured.single |
| as Observer<dynamic>), |
| isTrue); |
| |
| // Verify recording is started. |
| expect(camera.pendingRecording, equals(mockPendingRecording)); |
| expect(camera.recording, mockRecording); |
| }); |
| |
| test( |
| 'startVideoCapturing binds video capture use case and starts the recording' |
| ' on first call, and does nothing on second call', () async { |
| // Set up mocks and constants. |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockPendingRecording mockPendingRecording = MockPendingRecording(); |
| final MockRecording mockRecording = MockRecording(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final TestSystemServicesHostApi mockSystemServicesApi = |
| MockTestSystemServicesHostApi(); |
| TestSystemServicesHostApi.setup(mockSystemServicesApi); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.recorder = MockRecorder(); |
| camera.videoCapture = MockVideoCapture(); |
| camera.cameraSelector = MockCameraSelector(); |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| // Tell plugin to create detached Observer when camera info updated. |
| camera.proxy = CameraXProxy( |
| createCameraStateObserver: (void Function(Object) onChanged) => |
| Observer<CameraState>.detached(onChanged: onChanged)); |
| |
| const int cameraId = 17; |
| const String outputPath = '/temp/MOV123.temp'; |
| |
| // Mock method calls. |
| when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) |
| .thenReturn(outputPath); |
| when(camera.recorder!.prepareRecording(outputPath)) |
| .thenAnswer((_) async => mockPendingRecording); |
| when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); |
| when(camera.processCameraProvider!.isBound(camera.videoCapture!)) |
| .thenAnswer((_) async => false); |
| when(camera.processCameraProvider!.bindToLifecycle( |
| camera.cameraSelector!, <UseCase>[camera.videoCapture!])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()) |
| .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo)); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| |
| verify(camera.processCameraProvider!.bindToLifecycle( |
| camera.cameraSelector!, <UseCase>[camera.videoCapture!])); |
| expect(camera.pendingRecording, equals(mockPendingRecording)); |
| expect(camera.recording, mockRecording); |
| |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| // Verify that each of these calls happened only once. |
| verify(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) |
| .called(1); |
| verifyNoMoreInteractions(mockSystemServicesApi); |
| verify(camera.recorder!.prepareRecording(outputPath)).called(1); |
| verifyNoMoreInteractions(camera.recorder); |
| verify(mockPendingRecording.start()).called(1); |
| verifyNoMoreInteractions(mockPendingRecording); |
| }); |
| |
| test( |
| 'startVideoCapturing called with stream options starts image streaming', |
| () async { |
| // Set up mocks and constants. |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| |
| // Set directly for test versus calling createCamera. |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| camera.processCameraProvider = mockProcessCameraProvider; |
| camera.cameraSelector = MockCameraSelector(); |
| camera.videoCapture = MockVideoCapture(); |
| camera.imageAnalysis = MockImageAnalysis(); |
| camera.camera = MockCamera(); |
| final Recorder mockRecorder = MockRecorder(); |
| camera.recorder = mockRecorder; |
| final MockPendingRecording mockPendingRecording = MockPendingRecording(); |
| final TestSystemServicesHostApi mockSystemServicesApi = |
| MockTestSystemServicesHostApi(); |
| TestSystemServicesHostApi.setup(mockSystemServicesApi); |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| // Tell plugin to create detached Analyzer for testing. |
| camera.proxy = CameraXProxy( |
| createAnalyzer: |
| (Future<void> Function(ImageProxy imageProxy) analyze) => |
| Analyzer.detached(analyze: analyze)); |
| |
| const int cameraId = 17; |
| const String outputPath = '/temp/MOV123.temp'; |
| final Completer<CameraImageData> imageDataCompleter = |
| Completer<CameraImageData>(); |
| final VideoCaptureOptions videoCaptureOptions = VideoCaptureOptions( |
| cameraId, |
| streamCallback: (CameraImageData imageData) => |
| imageDataCompleter.complete(imageData)); |
| |
| // Mock method calls. |
| when(camera.processCameraProvider!.isBound(camera.videoCapture!)) |
| .thenAnswer((_) async => true); |
| when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) |
| .thenReturn(outputPath); |
| when(camera.recorder!.prepareRecording(outputPath)) |
| .thenAnswer((_) async => mockPendingRecording); |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) => Future<Camera>.value(camera.camera)); |
| when(camera.camera!.getCameraInfo()) |
| .thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo())); |
| |
| await camera.startVideoCapturing(videoCaptureOptions); |
| |
| final CameraImageData mockCameraImageData = MockCameraImageData(); |
| camera.cameraImageDataStreamController!.add(mockCameraImageData); |
| |
| expect(imageDataCompleter.future, isNotNull); |
| await camera.cameraImageDataStreamController!.close(); |
| }); |
| |
| test( |
| 'startVideoCapturing sets VideoCapture target rotation to current video orientation if orientation unlocked', |
| () async { |
| // Set up mocks and constants. |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockPendingRecording mockPendingRecording = MockPendingRecording(); |
| final MockRecording mockRecording = MockRecording(); |
| final MockVideoCapture mockVideoCapture = MockVideoCapture(); |
| final TestSystemServicesHostApi mockSystemServicesApi = |
| MockTestSystemServicesHostApi(); |
| TestSystemServicesHostApi.setup(mockSystemServicesApi); |
| const int defaultTargetRotation = Surface.ROTATION_270; |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.camera = MockCamera(); |
| camera.recorder = MockRecorder(); |
| camera.videoCapture = mockVideoCapture; |
| camera.cameraSelector = MockCameraSelector(); |
| |
| // Tell plugin to mock call to get current video orientation. |
| camera.proxy = CameraXProxy( |
| getDefaultDisplayRotation: () => |
| Future<int>.value(defaultTargetRotation)); |
| |
| const int cameraId = 87; |
| const String outputPath = '/temp/MOV123.temp'; |
| |
| // Mock method calls. |
| when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) |
| .thenReturn(outputPath); |
| when(camera.recorder!.prepareRecording(outputPath)) |
| .thenAnswer((_) async => mockPendingRecording); |
| when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); |
| when(camera.processCameraProvider!.isBound(camera.videoCapture!)) |
| .thenAnswer((_) async => true); |
| |
| // Orientation is unlocked and plugin does not need to set default target |
| // rotation manually. |
| camera.recording = null; |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| verifyNever(mockVideoCapture.setTargetRotation(any)); |
| |
| // Orientation is locked and plugin does not need to set default target |
| // rotation manually. |
| camera.recording = null; |
| camera.captureOrientationLocked = true; |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| verifyNever(mockVideoCapture.setTargetRotation(any)); |
| |
| // Orientation is locked and plugin does need to set default target |
| // rotation manually. |
| camera.recording = null; |
| camera.captureOrientationLocked = true; |
| camera.shouldSetDefaultRotation = true; |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| verifyNever(mockVideoCapture.setTargetRotation(any)); |
| |
| // Orientation is unlocked and plugin does need to set default target |
| // rotation manually. |
| camera.recording = null; |
| camera.captureOrientationLocked = false; |
| camera.shouldSetDefaultRotation = true; |
| await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); |
| verify(mockVideoCapture.setTargetRotation(defaultTargetRotation)); |
| }); |
| |
| test('pauseVideoRecording pauses the recording', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockRecording recording = MockRecording(); |
| |
| // Set directly for test versus calling startVideoCapturing. |
| camera.recording = recording; |
| |
| await camera.pauseVideoRecording(0); |
| verify(recording.pause()); |
| verifyNoMoreInteractions(recording); |
| }); |
| |
| test('resumeVideoRecording resumes the recording', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockRecording recording = MockRecording(); |
| |
| // Set directly for test versus calling startVideoCapturing. |
| camera.recording = recording; |
| |
| await camera.resumeVideoRecording(0); |
| verify(recording.resume()); |
| verifyNoMoreInteractions(recording); |
| }); |
| |
| test('stopVideoRecording stops the recording', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockRecording recording = MockRecording(); |
| final MockProcessCameraProvider processCameraProvider = |
| MockProcessCameraProvider(); |
| final MockVideoCapture videoCapture = MockVideoCapture(); |
| const String videoOutputPath = '/test/output/path'; |
| |
| // Set directly for test versus calling createCamera and startVideoCapturing. |
| camera.processCameraProvider = processCameraProvider; |
| camera.recording = recording; |
| camera.videoCapture = videoCapture; |
| camera.videoOutputPath = videoOutputPath; |
| |
| // Tell plugin that videoCapture use case was bound to start recording. |
| when(camera.processCameraProvider!.isBound(videoCapture)) |
| .thenAnswer((_) async => true); |
| |
| final XFile file = await camera.stopVideoRecording(0); |
| expect(file.path, videoOutputPath); |
| |
| // Verify that recording stops. |
| verify(recording.close()); |
| verifyNoMoreInteractions(recording); |
| }); |
| |
| test( |
| 'stopVideoRecording throws a camera exception if ' |
| 'no recording is in progress', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const String videoOutputPath = '/test/output/path'; |
| |
| // Set directly for test versus calling startVideoCapturing. |
| camera.recording = null; |
| camera.videoOutputPath = videoOutputPath; |
| |
| await expectLater(() async { |
| await camera.stopVideoRecording(0); |
| }, throwsA(isA<CameraException>())); |
| }); |
| }); |
| |
| test( |
| 'stopVideoRecording throws a camera exception if ' |
| 'videoOutputPath is null, and sets recording to null', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockRecording mockRecording = MockRecording(); |
| final MockVideoCapture mockVideoCapture = MockVideoCapture(); |
| |
| // Set directly for test versus calling startVideoCapturing. |
| camera.processCameraProvider = MockProcessCameraProvider(); |
| camera.recording = mockRecording; |
| camera.videoOutputPath = null; |
| camera.videoCapture = mockVideoCapture; |
| |
| // Tell plugin that videoCapture use case was bound to start recording. |
| when(camera.processCameraProvider!.isBound(mockVideoCapture)) |
| .thenAnswer((_) async => true); |
| |
| await expectLater(() async { |
| await camera.stopVideoRecording(0); |
| }, throwsA(isA<CameraException>())); |
| expect(camera.recording, null); |
| }); |
| |
| test( |
| 'calling stopVideoRecording twice stops the recording ' |
| 'and then throws a CameraException', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockRecording recording = MockRecording(); |
| final MockProcessCameraProvider processCameraProvider = |
| MockProcessCameraProvider(); |
| final MockVideoCapture videoCapture = MockVideoCapture(); |
| const String videoOutputPath = '/test/output/path'; |
| |
| // Set directly for test versus calling createCamera and startVideoCapturing. |
| camera.processCameraProvider = processCameraProvider; |
| camera.recording = recording; |
| camera.videoCapture = videoCapture; |
| camera.videoOutputPath = videoOutputPath; |
| |
| final XFile file = await camera.stopVideoRecording(0); |
| expect(file.path, videoOutputPath); |
| |
| await expectLater(() async { |
| await camera.stopVideoRecording(0); |
| }, throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'takePicture binds and unbinds ImageCapture to lifecycle and makes call to take a picture', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const String testPicturePath = 'test/absolute/path/to/picture'; |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageCapture = MockImageCapture(); |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| when(camera.imageCapture!.takePicture()) |
| .thenAnswer((_) async => testPicturePath); |
| |
| final XFile imageFile = await camera.takePicture(3); |
| |
| expect(imageFile.path, equals(testPicturePath)); |
| }); |
| |
| test( |
| 'takePicture sets ImageCapture target rotation to currrent photo rotation when orientation unlocked', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockImageCapture mockImageCapture = MockImageCapture(); |
| const int cameraId = 3; |
| const int defaultTargetRotation = Surface.ROTATION_180; |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageCapture = mockImageCapture; |
| |
| // Tell plugin to mock call to get current photo orientation. |
| camera.proxy = CameraXProxy( |
| getDefaultDisplayRotation: () => |
| Future<int>.value(defaultTargetRotation)); |
| |
| when(camera.imageCapture!.takePicture()) |
| .thenAnswer((_) async => 'test/absolute/path/to/picture'); |
| |
| // Orientation is unlocked and plugin does not need to set default target |
| // rotation manually. |
| await camera.takePicture(cameraId); |
| verifyNever(mockImageCapture.setTargetRotation(any)); |
| |
| // Orientation is locked and plugin does not need to set default target |
| // rotation manually. |
| camera.captureOrientationLocked = true; |
| await camera.takePicture(cameraId); |
| verifyNever(mockImageCapture.setTargetRotation(any)); |
| |
| // Orientation is locked and plugin does need to set default target |
| // rotation manually. |
| camera.captureOrientationLocked = true; |
| camera.shouldSetDefaultRotation = true; |
| await camera.takePicture(cameraId); |
| verifyNever(mockImageCapture.setTargetRotation(any)); |
| |
| // Orientation is unlocked and plugin does need to set default target |
| // rotation manually. |
| camera.captureOrientationLocked = false; |
| camera.shouldSetDefaultRotation = true; |
| await camera.takePicture(cameraId); |
| verify(mockImageCapture.setTargetRotation(defaultTargetRotation)); |
| }); |
| |
| test('takePicture turns non-torch flash mode off when torch mode enabled', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 77; |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageCapture = MockImageCapture(); |
| camera.cameraControl = MockCameraControl(); |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| await camera.setFlashMode(cameraId, FlashMode.torch); |
| await camera.takePicture(cameraId); |
| verify(camera.imageCapture!.setFlashMode(ImageCapture.flashModeOff)); |
| }); |
| |
| test( |
| 'setFlashMode configures ImageCapture with expected non-torch flash mode', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 22; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageCapture = MockImageCapture(); |
| camera.cameraControl = mockCameraControl; |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| for (final FlashMode flashMode in FlashMode.values) { |
| await camera.setFlashMode(cameraId, flashMode); |
| |
| int? expectedFlashMode; |
| switch (flashMode) { |
| case FlashMode.off: |
| expectedFlashMode = ImageCapture.flashModeOff; |
| case FlashMode.auto: |
| expectedFlashMode = ImageCapture.flashModeAuto; |
| case FlashMode.always: |
| expectedFlashMode = ImageCapture.flashModeOn; |
| case FlashMode.torch: |
| expectedFlashMode = null; |
| } |
| |
| if (expectedFlashMode == null) { |
| // Torch mode enabled and won't be used for configuring image capture. |
| continue; |
| } |
| |
| verifyNever(mockCameraControl.enableTorch(true)); |
| expect(camera.torchEnabled, isFalse); |
| await camera.takePicture(cameraId); |
| verify(camera.imageCapture!.setFlashMode(expectedFlashMode)); |
| } |
| }); |
| |
| test('setFlashMode turns on torch mode as expected', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 44; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| |
| await camera.setFlashMode(cameraId, FlashMode.torch); |
| |
| verify(mockCameraControl.enableTorch(true)); |
| expect(camera.torchEnabled, isTrue); |
| }); |
| |
| test('setFlashMode turns off torch mode when non-torch flash modes set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 33; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| |
| for (final FlashMode flashMode in FlashMode.values) { |
| camera.torchEnabled = true; |
| await camera.setFlashMode(cameraId, flashMode); |
| |
| switch (flashMode) { |
| case FlashMode.off: |
| case FlashMode.auto: |
| case FlashMode.always: |
| verify(mockCameraControl.enableTorch(false)); |
| expect(camera.torchEnabled, isFalse); |
| case FlashMode.torch: |
| verifyNever(mockCameraControl.enableTorch(true)); |
| expect(camera.torchEnabled, true); |
| } |
| } |
| }); |
| |
| test('getMinExposureOffset returns expected exposure offset', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0.2); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| |
| // We expect the minimum exposure to be the minimum exposure compensation * exposure compensation step. |
| // Delta is included due to avoid catching rounding errors. |
| expect(await camera.getMinExposureOffset(35), closeTo(0.6, 0.0000000001)); |
| }); |
| |
| test('getMaxExposureOffset returns expected exposure offset', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0.2); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| |
| // We expect the maximum exposure to be the maximum exposure compensation * exposure compensation step. |
| expect(await camera.getMaxExposureOffset(35), 0.8); |
| }); |
| |
| test('getExposureOffsetStepSize returns expected exposure offset', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0.2); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| |
| expect(await camera.getExposureOffsetStepSize(55), 0.2); |
| }); |
| |
| test( |
| 'getExposureOffsetStepSize returns -1 when exposure compensation not supported on device', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 0, maxCompensation: 0), |
| exposureCompensationStep: 0); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| |
| expect(await camera.getExposureOffsetStepSize(55), -1); |
| }); |
| |
| test('getMaxZoomLevel returns expected exposure offset', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| const double maxZoomRatio = 1; |
| final LiveData<ZoomState> mockLiveZoomState = MockLiveZoomState(); |
| final ZoomState zoomState = |
| ZoomState.detached(maxZoomRatio: maxZoomRatio, minZoomRatio: 0); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getZoomState()) |
| .thenAnswer((_) async => mockLiveZoomState); |
| when(mockLiveZoomState.getValue()).thenAnswer((_) async => zoomState); |
| |
| expect(await camera.getMaxZoomLevel(55), maxZoomRatio); |
| }); |
| |
| test('getMinZoomLevel returns expected exposure offset', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| const double minZoomRatio = 0; |
| final LiveData<ZoomState> mockLiveZoomState = MockLiveZoomState(); |
| final ZoomState zoomState = |
| ZoomState.detached(maxZoomRatio: 1, minZoomRatio: minZoomRatio); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getZoomState()) |
| .thenAnswer((_) async => mockLiveZoomState); |
| when(mockLiveZoomState.getValue()).thenAnswer((_) async => zoomState); |
| |
| expect(await camera.getMinZoomLevel(55), minZoomRatio); |
| }); |
| |
| test('setZoomLevel sets zoom ratio as expected', () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 44; |
| const double zoomRatio = 0.3; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| |
| await camera.setZoomLevel(cameraId, zoomRatio); |
| |
| verify(mockCameraControl.setZoomRatio(zoomRatio)); |
| }); |
| |
| test( |
| 'onStreamedFrameAvailable emits CameraImageData when picked up from CameraImageData stream controller', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| const int cameraId = 22; |
| |
| // Tell plugin to create detached Analyzer for testing. |
| camera.proxy = CameraXProxy( |
| createAnalyzer: |
| (Future<void> Function(ImageProxy imageProxy) analyze) => |
| Analyzer.detached(analyze: analyze)); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = mockProcessCameraProvider; |
| camera.cameraSelector = MockCameraSelector(); |
| camera.imageAnalysis = MockImageAnalysis(); |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) => Future<Camera>.value(mockCamera)); |
| when(mockCamera.getCameraInfo()) |
| .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo)); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| |
| final CameraImageData mockCameraImageData = MockCameraImageData(); |
| final Stream<CameraImageData> imageStream = |
| camera.onStreamedFrameAvailable(cameraId); |
| final StreamQueue<CameraImageData> streamQueue = |
| StreamQueue<CameraImageData>(imageStream); |
| |
| camera.cameraImageDataStreamController!.add(mockCameraImageData); |
| |
| expect(await streamQueue.next, equals(mockCameraImageData)); |
| await streamQueue.cancel(); |
| }); |
| |
| test( |
| 'onStreamedFrameAvailable emits CameraImageData when listened to after cancelation', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| final MockProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final MockCamera mockCamera = MockCamera(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| const int cameraId = 22; |
| |
| // Tell plugin to create detached Analyzer for testing. |
| camera.proxy = CameraXProxy( |
| createAnalyzer: |
| (Future<void> Function(ImageProxy imageProxy) analyze) => |
| Analyzer.detached(analyze: analyze)); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = mockProcessCameraProvider; |
| camera.cameraSelector = MockCameraSelector(); |
| camera.imageAnalysis = MockImageAnalysis(); |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| when(mockProcessCameraProvider.bindToLifecycle(any, any)) |
| .thenAnswer((_) => Future<Camera>.value(mockCamera)); |
| when(mockCamera.getCameraInfo()) |
| .thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo)); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| |
| final CameraImageData mockCameraImageData = MockCameraImageData(); |
| final Stream<CameraImageData> imageStream = |
| camera.onStreamedFrameAvailable(cameraId); |
| |
| // Listen to image stream. |
| final StreamSubscription<CameraImageData> imageStreamSubscription = |
| imageStream.listen((CameraImageData data) {}); |
| |
| // Cancel subscription to image stream. |
| await imageStreamSubscription.cancel(); |
| final Stream<CameraImageData> imageStream2 = |
| camera.onStreamedFrameAvailable(cameraId); |
| |
| // Listen to image stream again. |
| final StreamQueue<CameraImageData> streamQueue = |
| StreamQueue<CameraImageData>(imageStream2); |
| camera.cameraImageDataStreamController!.add(mockCameraImageData); |
| |
| expect(await streamQueue.next, equals(mockCameraImageData)); |
| await streamQueue.cancel(); |
| }); |
| |
| test( |
| 'onStreamedFrameAvailable returns stream that responds expectedly to being listened to', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 33; |
| final ProcessCameraProvider mockProcessCameraProvider = |
| MockProcessCameraProvider(); |
| final CameraSelector mockCameraSelector = MockCameraSelector(); |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| final Camera mockCamera = MockCamera(); |
| final CameraInfo mockCameraInfo = MockCameraInfo(); |
| final MockImageProxy mockImageProxy = MockImageProxy(); |
| final MockPlaneProxy mockPlane = MockPlaneProxy(); |
| final List<MockPlaneProxy> mockPlanes = <MockPlaneProxy>[mockPlane]; |
| final Uint8List buffer = Uint8List(0); |
| const int pixelStride = 27; |
| const int rowStride = 58; |
| const int imageFormat = 582; |
| const int imageHeight = 100; |
| const int imageWidth = 200; |
| |
| // Tell plugin to create detached Analyzer for testing. |
| camera.proxy = CameraXProxy( |
| createAnalyzer: |
| (Future<void> Function(ImageProxy imageProxy) analyze) => |
| Analyzer.detached(analyze: analyze)); |
| |
| // Set directly for test versus calling createCamera. |
| camera.processCameraProvider = mockProcessCameraProvider; |
| camera.cameraSelector = mockCameraSelector; |
| camera.imageAnalysis = mockImageAnalysis; |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| when(mockProcessCameraProvider.isBound(mockImageAnalysis)) |
| .thenAnswer((_) async => Future<bool>.value(false)); |
| when(mockProcessCameraProvider |
| .bindToLifecycle(mockCameraSelector, <UseCase>[mockImageAnalysis])) |
| .thenAnswer((_) async => mockCamera); |
| when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); |
| when(mockCameraInfo.getCameraState()) |
| .thenAnswer((_) async => MockLiveCameraState()); |
| when(mockImageProxy.getPlanes()) |
| .thenAnswer((_) => Future<List<PlaneProxy>>.value(mockPlanes)); |
| when(mockPlane.buffer).thenReturn(buffer); |
| when(mockPlane.rowStride).thenReturn(rowStride); |
| when(mockPlane.pixelStride).thenReturn(pixelStride); |
| when(mockImageProxy.format).thenReturn(imageFormat); |
| when(mockImageProxy.height).thenReturn(imageHeight); |
| when(mockImageProxy.width).thenReturn(imageWidth); |
| |
| final Completer<CameraImageData> imageDataCompleter = |
| Completer<CameraImageData>(); |
| final StreamSubscription<CameraImageData> |
| onStreamedFrameAvailableSubscription = camera |
| .onStreamedFrameAvailable(cameraId) |
| .listen((CameraImageData imageData) { |
| imageDataCompleter.complete(imageData); |
| }); |
| |
| // Test ImageAnalysis use case is bound to ProcessCameraProvider. |
| final Analyzer capturedAnalyzer = |
| verify(mockImageAnalysis.setAnalyzer(captureAny)).captured.single |
| as Analyzer; |
| |
| await capturedAnalyzer.analyze(mockImageProxy); |
| |
| final CameraImageData imageData = await imageDataCompleter.future; |
| |
| // Test Analyzer correctly process ImageProxy instances. |
| expect(imageData.planes.length, equals(1)); |
| expect(imageData.planes[0].bytes, equals(buffer)); |
| expect(imageData.planes[0].bytesPerRow, equals(rowStride)); |
| expect(imageData.planes[0].bytesPerPixel, equals(pixelStride)); |
| expect(imageData.format.raw, equals(imageFormat)); |
| expect(imageData.height, equals(imageHeight)); |
| expect(imageData.width, equals(imageWidth)); |
| |
| await onStreamedFrameAvailableSubscription.cancel(); |
| }); |
| |
| test( |
| 'onStreamedFrameAvailable returns stream that responds expectedly to being canceled', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 32; |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageAnalysis = mockImageAnalysis; |
| |
| // Ignore setting target rotation for this test; tested seprately. |
| camera.captureOrientationLocked = true; |
| |
| // Tell plugin to create a detached analyzer for testing purposes. |
| camera.proxy = CameraXProxy(createAnalyzer: (_) => MockAnalyzer()); |
| |
| final StreamSubscription<CameraImageData> imageStreamSubscription = camera |
| .onStreamedFrameAvailable(cameraId) |
| .listen((CameraImageData data) {}); |
| |
| await imageStreamSubscription.cancel(); |
| |
| verify(mockImageAnalysis.clearAnalyzer()); |
| }); |
| |
| test( |
| 'onStreamedFrameAvailable sets ImageAnalysis target rotation to current photo orientation when orientation unlocked', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 35; |
| const int defaultTargetRotation = Surface.ROTATION_90; |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageAnalysis = mockImageAnalysis; |
| |
| // Tell plugin to create a detached analyzer for testing purposes and mock |
| // call to get current photo orientation. |
| camera.proxy = CameraXProxy( |
| createAnalyzer: (_) => MockAnalyzer(), |
| getDefaultDisplayRotation: () => |
| Future<int>.value(defaultTargetRotation)); |
| |
| // Orientation is unlocked and plugin does not need to set default target |
| // rotation manually. |
| StreamSubscription<CameraImageData> imageStreamSubscription = camera |
| .onStreamedFrameAvailable(cameraId) |
| .listen((CameraImageData data) {}); |
| await untilCalled(mockImageAnalysis.setAnalyzer(any)); |
| verifyNever(mockImageAnalysis.setTargetRotation(any)); |
| await imageStreamSubscription.cancel(); |
| |
| // Orientation is locked and plugin does not need to set default target |
| // rotation manually. |
| camera.captureOrientationLocked = true; |
| imageStreamSubscription = camera |
| .onStreamedFrameAvailable(cameraId) |
| .listen((CameraImageData data) {}); |
| await untilCalled(mockImageAnalysis.setAnalyzer(any)); |
| verifyNever(mockImageAnalysis.setTargetRotation(any)); |
| await imageStreamSubscription.cancel(); |
| |
| // Orientation is locked and plugin does need to set default target |
| // rotation manually. |
| camera.captureOrientationLocked = true; |
| camera.shouldSetDefaultRotation = true; |
| imageStreamSubscription = camera |
| .onStreamedFrameAvailable(cameraId) |
| .listen((CameraImageData data) {}); |
| await untilCalled(mockImageAnalysis.setAnalyzer(any)); |
| verifyNever(mockImageAnalysis.setTargetRotation(any)); |
| await imageStreamSubscription.cancel(); |
| |
| // Orientation is unlocked and plugin does need to set default target |
| // rotation manually. |
| camera.captureOrientationLocked = false; |
| camera.shouldSetDefaultRotation = true; |
| imageStreamSubscription = camera |
| .onStreamedFrameAvailable(cameraId) |
| .listen((CameraImageData data) {}); |
| await untilCalled( |
| mockImageAnalysis.setTargetRotation(defaultTargetRotation)); |
| await imageStreamSubscription.cancel(); |
| }); |
| |
| test( |
| 'lockCaptureOrientation sets capture-related use case target rotations to correct orientation', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 44; |
| |
| final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); |
| final MockImageCapture mockImageCapture = MockImageCapture(); |
| final MockVideoCapture mockVideoCapture = MockVideoCapture(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.imageAnalysis = mockImageAnalysis; |
| camera.imageCapture = mockImageCapture; |
| camera.videoCapture = mockVideoCapture; |
| |
| for (final DeviceOrientation orientation in DeviceOrientation.values) { |
| int? expectedTargetRotation; |
| switch (orientation) { |
| case DeviceOrientation.portraitUp: |
| expectedTargetRotation = Surface.ROTATION_0; |
| case DeviceOrientation.landscapeLeft: |
| expectedTargetRotation = Surface.ROTATION_90; |
| case DeviceOrientation.portraitDown: |
| expectedTargetRotation = Surface.ROTATION_180; |
| case DeviceOrientation.landscapeRight: |
| expectedTargetRotation = Surface.ROTATION_270; |
| } |
| |
| await camera.lockCaptureOrientation(cameraId, orientation); |
| |
| verify(mockImageAnalysis.setTargetRotation(expectedTargetRotation)); |
| verify(mockImageCapture.setTargetRotation(expectedTargetRotation)); |
| verify(mockVideoCapture.setTargetRotation(expectedTargetRotation)); |
| expect(camera.captureOrientationLocked, isTrue); |
| expect(camera.shouldSetDefaultRotation, isTrue); |
| |
| // Reset flags for testing. |
| camera.captureOrientationLocked = false; |
| camera.shouldSetDefaultRotation = false; |
| } |
| }); |
| |
| test( |
| 'unlockCaptureOrientation sets capture-related use case target rotations to current photo/video orientation', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 57; |
| |
| camera.captureOrientationLocked = true; |
| await camera.unlockCaptureOrientation(cameraId); |
| expect(camera.captureOrientationLocked, isFalse); |
| }); |
| |
| test('setExposureMode sets expected controlAeLock value via Camera2 interop', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 78; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.camera = MockCamera(); |
| camera.cameraControl = mockCameraControl; |
| |
| // Tell plugin to create detached Camera2CameraControl and |
| // CaptureRequestOptions instances for testing. |
| camera.proxy = CameraXProxy( |
| getCamera2CameraControl: (CameraControl cameraControl) => |
| cameraControl == mockCameraControl |
| ? mockCamera2CameraControl |
| : Camera2CameraControl.detached(cameraControl: cameraControl), |
| createCaptureRequestOptions: |
| (List<(CaptureRequestKeySupportedType, Object?)> options) => |
| CaptureRequestOptions.detached(requestedOptions: options), |
| ); |
| |
| // Test auto mode. |
| await camera.setExposureMode(cameraId, ExposureMode.auto); |
| |
| VerificationResult verificationResult = |
| verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); |
| CaptureRequestOptions capturedCaptureRequestOptions = |
| verificationResult.captured.single as CaptureRequestOptions; |
| List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = |
| capturedCaptureRequestOptions.requestedOptions; |
| expect(requestedOptions.length, equals(1)); |
| expect(requestedOptions.first.$1, |
| equals(CaptureRequestKeySupportedType.controlAeLock)); |
| expect(requestedOptions.first.$2, equals(false)); |
| |
| // Test locked mode. |
| clearInteractions(mockCamera2CameraControl); |
| await camera.setExposureMode(cameraId, ExposureMode.locked); |
| |
| verificationResult = |
| verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); |
| capturedCaptureRequestOptions = |
| verificationResult.captured.single as CaptureRequestOptions; |
| requestedOptions = capturedCaptureRequestOptions.requestedOptions; |
| expect(requestedOptions.length, equals(1)); |
| expect(requestedOptions.first.$1, |
| equals(CaptureRequestKeySupportedType.controlAeLock)); |
| expect(requestedOptions.first.$2, equals(true)); |
| }); |
| |
| test( |
| 'setExposurePoint clears current auto-exposure metering point as expected', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 93; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = mockCameraInfo; |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| // Verify nothing happens if no current focus and metering action has been |
| // enabled. |
| await camera.setExposurePoint(cameraId, null); |
| verifyNever(mockCameraControl.startFocusAndMetering(any)); |
| verifyNever(mockCameraControl.cancelFocusAndMetering()); |
| |
| // Verify current auto-exposure metering point is removed if previously set. |
| final (MeteringPoint, int?) autofocusMeteringPointInfo = ( |
| MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAf |
| ); |
| List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ |
| ( |
| MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAe |
| ), |
| autofocusMeteringPointInfo |
| ]; |
| |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setExposurePoint(cameraId, null); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect( |
| capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo)); |
| |
| // Verify current focus and metering action is cleared if only previously |
| // set metering point was for auto-exposure. |
| meteringPointInfos = <(MeteringPoint, int?)>[ |
| ( |
| MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAe |
| ) |
| ]; |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setExposurePoint(cameraId, null); |
| |
| verify(mockCameraControl.cancelFocusAndMetering()); |
| }); |
| |
| test('setExposurePoint throws CameraException if invalid point specified', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 23; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| const Point<double> invalidExposurePoint = Point<double>(3, -1); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| expect(() => camera.setExposurePoint(cameraId, invalidExposurePoint), |
| throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'setExposurePoint adds new exposure point to focus metering action to start as expected when previous metering points have been set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 9; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = mockCameraInfo; |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| // Verify current auto-exposure metering point is removed if previously set. |
| double exposurePointX = 0.8; |
| double exposurePointY = 0.1; |
| Point<double> exposurePoint = Point<double>(exposurePointX, exposurePointY); |
| final (MeteringPoint, int?) autofocusMeteringPointInfo = ( |
| MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAf |
| ); |
| List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ |
| ( |
| MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAe |
| ), |
| autofocusMeteringPointInfo |
| ]; |
| |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| |
| VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(2)); |
| expect( |
| capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo)); |
| expect(capturedMeteringPointInfos[1].$1.x, equals(exposurePointX)); |
| expect(capturedMeteringPointInfos[1].$1.y, equals(exposurePointY)); |
| expect( |
| capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAe)); |
| |
| // Verify exposure point is set when no auto-exposure metering point |
| // previously set, but an auto-focus point metering point has been. |
| exposurePointX = 0.2; |
| exposurePointY = 0.9; |
| exposurePoint = Point<double>(exposurePointX, exposurePointY); |
| meteringPointInfos = <(MeteringPoint, int?)>[autofocusMeteringPointInfo]; |
| |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| |
| verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| capturedAction = verificationResult.captured.single as FocusMeteringAction; |
| capturedMeteringPointInfos = capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(2)); |
| expect( |
| capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo)); |
| expect(capturedMeteringPointInfos[1].$1.x, equals(exposurePointX)); |
| expect(capturedMeteringPointInfos[1].$1.y, equals(exposurePointY)); |
| expect( |
| capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAe)); |
| }); |
| |
| test( |
| 'setExposurePoint adds new exposure point to focus metering action to start as expected when no previous metering points have been set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 19; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| const double exposurePointX = 0.8; |
| const double exposurePointY = 0.1; |
| const Point<double> exposurePoint = |
| Point<double>(exposurePointX, exposurePointY); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| camera.currentFocusMeteringAction = null; |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first.$1.x, equals(exposurePointX)); |
| expect(capturedMeteringPointInfos.first.$1.y, equals(exposurePointY)); |
| expect(capturedMeteringPointInfos.first.$2, |
| equals(FocusMeteringAction.flagAe)); |
| }); |
| |
| test( |
| 'setExposurePoint disables auto-cancel for focus and metering as expected', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 2; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final FocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| const Point<double> exposurePoint = Point<double>(0.1, 0.2); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Test not disabling auto cancel. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| clearInteractions(mockCameraControl); |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isFalse); |
| |
| clearInteractions(mockCameraControl); |
| |
| // Test disabling auto cancel. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| capturedAction = verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| }); |
| |
| test( |
| 'setExposureOffset throws exception if exposure compensation not supported', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 6; |
| const double offset = 2; |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| |
| expect(() => camera.setExposureOffset(cameraId, offset), |
| throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'setExposureOffset throws exception if exposure compensation could not be set for unknown reason', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 11; |
| const double offset = 3; |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final CameraControl mockCameraControl = MockCameraControl(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0.2); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| when(mockCameraControl.setExposureCompensationIndex(15)).thenThrow( |
| PlatformException( |
| code: 'TEST_ERROR', |
| message: |
| 'This is a test error message indicating exposure offset could not be set.')); |
| |
| expect(() => camera.setExposureOffset(cameraId, offset), |
| throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'setExposureOffset throws exception if exposure compensation could not be set due to camera being closed or newer value being set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 21; |
| const double offset = 5; |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final CameraControl mockCameraControl = MockCameraControl(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0.1); |
| final int expectedExposureCompensationIndex = |
| (offset / exposureState.exposureCompensationStep).round(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| when(mockCameraControl |
| .setExposureCompensationIndex(expectedExposureCompensationIndex)) |
| .thenAnswer((_) async => Future<int?>.value()); |
| |
| expect(() => camera.setExposureOffset(cameraId, offset), |
| throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'setExposureOffset behaves as expected to successful attempt to set exposure compensation index', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 11; |
| const double offset = 3; |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| final CameraControl mockCameraControl = MockCameraControl(); |
| final ExposureState exposureState = ExposureState.detached( |
| exposureCompensationRange: |
| ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), |
| exposureCompensationStep: 0.2); |
| final int expectedExposureCompensationIndex = |
| (offset / exposureState.exposureCompensationStep).round(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = mockCameraInfo; |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCameraInfo.getExposureState()) |
| .thenAnswer((_) async => exposureState); |
| when(mockCameraControl |
| .setExposureCompensationIndex(expectedExposureCompensationIndex)) |
| .thenAnswer((_) async => Future<int>.value( |
| (expectedExposureCompensationIndex * |
| exposureState.exposureCompensationStep) |
| .round())); |
| |
| // Exposure index * exposure offset step size = exposure offset, i.e. |
| // 15 * 0.2 = 3. |
| expect(await camera.setExposureOffset(cameraId, offset), equals(3)); |
| }); |
| |
| test('setFocusPoint clears current auto-exposure metering point as expected', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 93; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = mockCameraInfo; |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| // Verify nothing happens if no current focus and metering action has been |
| // enabled. |
| await camera.setFocusPoint(cameraId, null); |
| verifyNever(mockCameraControl.startFocusAndMetering(any)); |
| verifyNever(mockCameraControl.cancelFocusAndMetering()); |
| |
| // Verify current auto-exposure metering point is removed if previously set. |
| final (MeteringPoint, int?) autoexposureMeteringPointInfo = ( |
| MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAe |
| ); |
| List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ |
| ( |
| MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAf |
| ), |
| autoexposureMeteringPointInfo |
| ]; |
| |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setFocusPoint(cameraId, null); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first, |
| equals(autoexposureMeteringPointInfo)); |
| |
| // Verify current focus and metering action is cleared if only previously |
| // set metering point was for auto-exposure. |
| meteringPointInfos = <(MeteringPoint, int?)>[ |
| ( |
| MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAf |
| ) |
| ]; |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setFocusPoint(cameraId, null); |
| |
| verify(mockCameraControl.cancelFocusAndMetering()); |
| }); |
| |
| test('setFocusPoint throws CameraException if invalid point specified', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 23; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| const Point<double> invalidFocusPoint = Point<double>(-3, 1); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| expect(() => camera.setFocusPoint(cameraId, invalidFocusPoint), |
| throwsA(isA<CameraException>())); |
| }); |
| |
| test( |
| 'setFocusPoint adds new exposure point to focus metering action to start as expected when previous metering points have been set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 9; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCameraInfo mockCameraInfo = MockCameraInfo(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = mockCameraInfo; |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| // Verify current auto-exposure metering point is removed if previously set. |
| double focusPointX = 0.8; |
| double focusPointY = 0.1; |
| Point<double> exposurePoint = Point<double>(focusPointX, focusPointY); |
| final (MeteringPoint, int?) autoExposureMeteringPointInfo = ( |
| MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAe |
| ); |
| List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[ |
| ( |
| MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo), |
| FocusMeteringAction.flagAf |
| ), |
| autoExposureMeteringPointInfo |
| ]; |
| |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setFocusPoint(cameraId, exposurePoint); |
| |
| VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(2)); |
| expect(capturedMeteringPointInfos.first, |
| equals(autoExposureMeteringPointInfo)); |
| expect(capturedMeteringPointInfos[1].$1.x, equals(focusPointX)); |
| expect(capturedMeteringPointInfos[1].$1.y, equals(focusPointY)); |
| expect( |
| capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAf)); |
| |
| // Verify exposure point is set when no auto-exposure metering point |
| // previously set, but an auto-focus point metering point has been. |
| focusPointX = 0.2; |
| focusPointY = 0.9; |
| exposurePoint = Point<double>(focusPointX, focusPointY); |
| meteringPointInfos = <(MeteringPoint, int?)>[autoExposureMeteringPointInfo]; |
| |
| camera.currentFocusMeteringAction = |
| FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos); |
| |
| await camera.setFocusPoint(cameraId, exposurePoint); |
| |
| verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| capturedAction = verificationResult.captured.single as FocusMeteringAction; |
| capturedMeteringPointInfos = capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(2)); |
| expect(capturedMeteringPointInfos.first, |
| equals(autoExposureMeteringPointInfo)); |
| expect(capturedMeteringPointInfos[1].$1.x, equals(focusPointX)); |
| expect(capturedMeteringPointInfos[1].$1.y, equals(focusPointY)); |
| expect( |
| capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAf)); |
| }); |
| |
| test( |
| 'setFocusPoint adds new exposure point to focus metering action to start as expected when no previous metering points have been set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 19; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| const double focusPointX = 0.8; |
| const double focusPointY = 0.1; |
| const Point<double> exposurePoint = Point<double>(focusPointX, focusPointY); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| camera.currentFocusMeteringAction = null; |
| |
| camera.proxy = getProxyForExposureAndFocus(); |
| |
| await camera.setFocusPoint(cameraId, exposurePoint); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); |
| expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); |
| expect(capturedMeteringPointInfos.first.$2, |
| equals(FocusMeteringAction.flagAf)); |
| }); |
| |
| test('setFocusPoint disables auto-cancel for focus and metering as expected', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 2; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| const Point<double> exposurePoint = Point<double>(0.1, 0.2); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Test not disabling auto cancel. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setFocusPoint(cameraId, exposurePoint); |
| VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isFalse); |
| |
| clearInteractions(mockCameraControl); |
| |
| // Test disabling auto cancel. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setFocusPoint(cameraId, exposurePoint); |
| verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| capturedAction = verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| }); |
| |
| test( |
| 'setFocusMode does nothing if setting auto-focus mode and is already using auto-focus mode', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 4; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set locked focus mode and then try to re-set it. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| verifyNoMoreInteractions(mockCameraControl); |
| }); |
| |
| test( |
| 'setFocusMode does nothing if setting locked focus mode and is already using locked focus mode', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 4; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| |
| // Camera uses auto-focus by default, so try setting auto mode again. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| |
| verifyNoMoreInteractions(mockCameraControl); |
| }); |
| |
| test( |
| 'setFocusMode removes default auto-focus point if previously set and setting auto-focus mode', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 5; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| const double exposurePointX = 0.2; |
| const double exposurePointY = 0.7; |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set exposure points. |
| await camera.setExposurePoint( |
| cameraId, const Point<double>(exposurePointX, exposurePointY)); |
| |
| // Lock focus default focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| clearInteractions(mockCameraControl); |
| |
| // Test removal of default focus point. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isFalse); |
| |
| // We expect only the previously set exposure point to be re-set. |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first.$1.x, equals(exposurePointX)); |
| expect(capturedMeteringPointInfos.first.$1.y, equals(exposurePointY)); |
| expect(capturedMeteringPointInfos.first.$1.size, isNull); |
| expect(capturedMeteringPointInfos.first.$2, |
| equals(FocusMeteringAction.flagAe)); |
| }); |
| |
| test( |
| 'setFocusMode cancels focus and metering if only focus point previously set is a focus point', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 5; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final FocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Lock focus default focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| // Test removal of default focus point. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| |
| verify(mockCameraControl.cancelFocusAndMetering()); |
| }); |
| |
| test( |
| 'setFocusMode re-focuses on previously set auto-focus point with auto-canceled enabled if setting auto-focus mode', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 6; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final FocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| const double focusPointX = 0.1; |
| const double focusPointY = 0.2; |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Lock a focus point. |
| await camera.setFocusPoint( |
| cameraId, const Point<double>(focusPointX, focusPointY)); |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| clearInteractions(mockCameraControl); |
| |
| // Test re-focusing on previously set auto-focus point with auto-cancel enabled. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isFalse); |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); |
| expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); |
| expect(capturedMeteringPointInfos.first.$1.size, isNull); |
| expect(capturedMeteringPointInfos.first.$2, |
| equals(FocusMeteringAction.flagAf)); |
| }); |
| |
| test( |
| 'setFocusMode starts expected focus and metering action with previously set auto-focus point if setting locked focus mode and current focus and metering action has auto-focus point', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 7; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| const double focusPointX = 0.88; |
| const double focusPointY = 0.33; |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Set a focus point. |
| await camera.setFocusPoint( |
| cameraId, const Point<double>(focusPointX, focusPointY)); |
| clearInteractions(mockCameraControl); |
| |
| // Lock focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| |
| // We expect the set focus point to be locked. |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); |
| expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); |
| expect(capturedMeteringPointInfos.first.$1.size, isNull); |
| expect(capturedMeteringPointInfos.first.$2, |
| equals(FocusMeteringAction.flagAf)); |
| }); |
| |
| test( |
| 'setFocusMode starts expected focus and metering action with previously set auto-focus point if setting locked focus mode and current focus and metering action has auto-focus point amongst others', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 8; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| const double focusPointX = 0.38; |
| const double focusPointY = 0.38; |
| const double exposurePointX = 0.54; |
| const double exposurePointY = 0.45; |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Set focus and exposure points. |
| await camera.setFocusPoint( |
| cameraId, const Point<double>(focusPointX, focusPointY)); |
| await camera.setExposurePoint( |
| cameraId, const Point<double>(exposurePointX, exposurePointY)); |
| clearInteractions(mockCameraControl); |
| |
| // Lock focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| |
| // We expect two MeteringPoints, the set focus point and the set exposure |
| // point. |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(2)); |
| |
| final List<(MeteringPoint, int?)> focusPoints = capturedMeteringPointInfos |
| .where(((MeteringPoint, int?) meteringPointInfo) => |
| meteringPointInfo.$2 == FocusMeteringAction.flagAf) |
| .toList(); |
| expect(focusPoints.length, equals(1)); |
| expect(focusPoints.first.$1.x, equals(focusPointX)); |
| expect(focusPoints.first.$1.y, equals(focusPointY)); |
| expect(focusPoints.first.$1.size, isNull); |
| |
| final List<(MeteringPoint, int?)> exposurePoints = |
| capturedMeteringPointInfos |
| .where(((MeteringPoint, int?) meteringPointInfo) => |
| meteringPointInfo.$2 == FocusMeteringAction.flagAe) |
| .toList(); |
| expect(exposurePoints.length, equals(1)); |
| expect(exposurePoints.first.$1.x, equals(exposurePointX)); |
| expect(exposurePoints.first.$1.y, equals(exposurePointY)); |
| expect(exposurePoints.first.$1.size, isNull); |
| }); |
| |
| test( |
| 'setFocusMode starts expected focus and metering action if setting locked focus mode and current focus and metering action does not contain an auto-focus point', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 9; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| const double exposurePointX = 0.8; |
| const double exposurePointY = 0.3; |
| const double defaultFocusPointX = 0.5; |
| const double defaultFocusPointY = 0.5; |
| const double defaultFocusPointSize = 1; |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Set an exposure point (creates a current focus and metering action |
| // without a focus point). |
| await camera.setExposurePoint( |
| cameraId, const Point<double>(exposurePointX, exposurePointY)); |
| clearInteractions(mockCameraControl); |
| |
| // Lock focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| |
| // We expect two MeteringPoints, the default focus point and the set |
| //exposure point. |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(2)); |
| |
| final List<(MeteringPoint, int?)> focusPoints = capturedMeteringPointInfos |
| .where(((MeteringPoint, int?) meteringPointInfo) => |
| meteringPointInfo.$2 == FocusMeteringAction.flagAf) |
| .toList(); |
| expect(focusPoints.length, equals(1)); |
| expect(focusPoints.first.$1.x, equals(defaultFocusPointX)); |
| expect(focusPoints.first.$1.y, equals(defaultFocusPointY)); |
| expect(focusPoints.first.$1.size, equals(defaultFocusPointSize)); |
| |
| final List<(MeteringPoint, int?)> exposurePoints = |
| capturedMeteringPointInfos |
| .where(((MeteringPoint, int?) meteringPointInfo) => |
| meteringPointInfo.$2 == FocusMeteringAction.flagAe) |
| .toList(); |
| expect(exposurePoints.length, equals(1)); |
| expect(exposurePoints.first.$1.x, equals(exposurePointX)); |
| expect(exposurePoints.first.$1.y, equals(exposurePointY)); |
| expect(exposurePoints.first.$1.size, isNull); |
| }); |
| |
| test( |
| 'setFocusMode starts expected focus and metering action if there is no current focus and metering action', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 10; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| const double defaultFocusPointX = 0.5; |
| const double defaultFocusPointY = 0.5; |
| const double defaultFocusPointSize = 1; |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Lock focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| |
| // We expect only the default focus point to be set. |
| final List<(MeteringPoint, int?)> capturedMeteringPointInfos = |
| capturedAction.meteringPointInfos; |
| expect(capturedMeteringPointInfos.length, equals(1)); |
| expect(capturedMeteringPointInfos.first.$1.x, equals(defaultFocusPointX)); |
| expect(capturedMeteringPointInfos.first.$1.y, equals(defaultFocusPointY)); |
| expect(capturedMeteringPointInfos.first.$1.size, |
| equals(defaultFocusPointSize)); |
| expect(capturedMeteringPointInfos.first.$2, |
| equals(FocusMeteringAction.flagAf)); |
| }); |
| |
| test( |
| 'setFocusMode re-sets exposure mode if setting locked focus mode while using auto exposure mode', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 11; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final FocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| final MockCamera2CameraControl mockCamera2CameraControl = |
| MockCamera2CameraControl(); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraInfo = MockCameraInfo(); |
| camera.cameraControl = mockCameraControl; |
| |
| when(mockCamera2CameraControl.addCaptureRequestOptions(any)) |
| .thenAnswer((_) async => Future<void>.value()); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, mockCamera2CameraControl); |
| |
| // Make setting focus and metering action successful for test. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set auto exposure mode. |
| await camera.setExposureMode(cameraId, ExposureMode.auto); |
| clearInteractions(mockCamera2CameraControl); |
| |
| // Lock focus point. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| |
| final VerificationResult verificationResult = |
| verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); |
| final CaptureRequestOptions capturedCaptureRequestOptions = |
| verificationResult.captured.single as CaptureRequestOptions; |
| final List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = |
| capturedCaptureRequestOptions.requestedOptions; |
| expect(requestedOptions.length, equals(1)); |
| expect(requestedOptions.first.$1, |
| equals(CaptureRequestKeySupportedType.controlAeLock)); |
| expect(requestedOptions.first.$2, equals(false)); |
| }); |
| |
| test( |
| 'setFocusPoint disables auto-cancel if auto focus mode fails to be set after locked focus mode is set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 22; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| const Point<double> focusPoint = Point<double>(0.21, 0.21); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action successful to set locked focus |
| // mode. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set exposure point to later mock failed call to set an exposure point ( |
| // otherwise, focus and metering will be canceled altogether, which is |
| //considered a successful call). |
| await camera.setExposurePoint(cameraId, const Point<double>(0.3, 0.4)); |
| |
| // Set locked focus mode so we can set auto mode (cannot set auto mode |
| // directly since it is the default). |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| |
| // Make setting focus and metering action fail to test that auto-cancel is |
| // still disabled. |
| reset(mockFocusMeteringResult); |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(false)); |
| |
| // Test disabling auto cancel. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setFocusPoint(cameraId, focusPoint); |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| }); |
| |
| test( |
| 'setExposurePoint disables auto-cancel if auto focus mode fails to be set after locked focus mode is set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 342; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| const Point<double> exposurePoint = Point<double>(0.23, 0.32); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action successful to set locked focus |
| // mode. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(true)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set exposure point to later mock failed call to set an exposure point ( |
| // otherwise, focus and metering will be canceled altogether, which is |
| //considered a successful call). |
| await camera.setExposurePoint(cameraId, const Point<double>(0.4, 0.3)); |
| |
| // Set locked focus mode so we can set auto mode (cannot set auto mode |
| // directly since it is the default). |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| |
| // Make setting focus and metering action fail to test that auto-cancel is |
| // still disabled. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(false)); |
| |
| // Test disabling auto cancel. |
| await camera.setFocusMode(cameraId, FocusMode.auto); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isTrue); |
| }); |
| |
| test( |
| 'setFocusPoint enables auto-cancel if locked focus mode fails to be set after auto focus mode is set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 232; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| const Point<double> focusPoint = Point<double>(0.221, 0.211); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action fail to test auto-cancel is not |
| // disabled. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(false)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set exposure point to later mock failed call to set an exposure point. |
| await camera.setExposurePoint(cameraId, const Point<double>(0.43, 0.34)); |
| |
| // Test failing to set locked focus mode. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setFocusPoint(cameraId, focusPoint); |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isFalse); |
| }); |
| |
| test( |
| 'setExposurePoint enables auto-cancel if locked focus mode fails to be set after auto focus mode is set', |
| () async { |
| final AndroidCameraCameraX camera = AndroidCameraCameraX(); |
| const int cameraId = 323; |
| final MockCameraControl mockCameraControl = MockCameraControl(); |
| final MockFocusMeteringResult mockFocusMeteringResult = |
| MockFocusMeteringResult(); |
| const Point<double> exposurePoint = Point<double>(0.223, 0.332); |
| |
| // Set directly for test versus calling createCamera. |
| camera.cameraControl = mockCameraControl; |
| camera.cameraInfo = MockCameraInfo(); |
| |
| camera.proxy = getProxyForSettingFocusandExposurePoints( |
| mockCameraControl, MockCamera2CameraControl()); |
| |
| // Make setting focus and metering action fail to test auto-cancel is not |
| // disabled. |
| when(mockFocusMeteringResult.isFocusSuccessful()) |
| .thenAnswer((_) async => Future<bool>.value(false)); |
| when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => |
| Future<FocusMeteringResult>.value(mockFocusMeteringResult)); |
| |
| // Set exposure point to later mock failed call to set an exposure point. |
| await camera.setExposurePoint(cameraId, const Point<double>(0.5, 0.2)); |
| |
| // Test failing to set locked focus mode. |
| await camera.setFocusMode(cameraId, FocusMode.locked); |
| clearInteractions(mockCameraControl); |
| |
| await camera.setExposurePoint(cameraId, exposurePoint); |
| final VerificationResult verificationResult = |
| verify(mockCameraControl.startFocusAndMetering(captureAny)); |
| final FocusMeteringAction capturedAction = |
| verificationResult.captured.single as FocusMeteringAction; |
| expect(capturedAction.disableAutoCancel, isFalse); |
| }); |
| } |