blob: e7a6707d6e3f378b8dcdb61865c7a652378d8b3a [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package: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/camera.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/camerax_library.g.dart';
import 'package:camera_android_camerax/src/exposure_state.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/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/system_services.dart';
import 'package:camera_android_camerax/src/use_case.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, Uint8List;
import 'package:flutter/widgets.dart';
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<Camera>(),
MockSpec<CameraInfo>(),
MockSpec<CameraImageData>(),
MockSpec<CameraSelector>(),
MockSpec<ExposureState>(),
MockSpec<ImageAnalysis>(),
MockSpec<ImageCapture>(),
MockSpec<ImageProxy>(),
MockSpec<PlaneProxy>(),
MockSpec<Preview>(),
MockSpec<ProcessCameraProvider>(),
MockSpec<TestInstanceManagerHostApi>(),
MockSpec<ZoomState>(),
])
@GenerateMocks(<Type>[BuildContext])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
// Mocks the call to clear the native InstanceManager.
TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi());
test('Should fetch CameraDescription instances for available cameras',
() async {
// Arrange
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
camera.processCameraProvider = MockProcessCameraProvider();
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 MockCameraInfo mockFrontCameraInfo = MockCameraInfo();
final MockCameraInfo mockBackCameraInfo = MockCameraInfo();
// Mock calls to native platform
when(camera.processCameraProvider!.getAvailableCameraInfos()).thenAnswer(
(_) async => <MockCameraInfo>[mockBackCameraInfo, mockFrontCameraInfo]);
when(camera.mockBackCameraSelector
.filter(<MockCameraInfo>[mockFrontCameraInfo]))
.thenAnswer((_) async => <MockCameraInfo>[]);
when(camera.mockBackCameraSelector
.filter(<MockCameraInfo>[mockBackCameraInfo]))
.thenAnswer((_) async => <MockCameraInfo>[mockBackCameraInfo]);
when(camera.mockFrontCameraSelector
.filter(<MockCameraInfo>[mockBackCameraInfo]))
.thenAnswer((_) async => <MockCameraInfo>[]);
when(camera.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, and returns flutter surface texture ID',
() async {
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
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;
const int testSurfaceTextureId = 6;
camera.processCameraProvider = mockProcessCameraProvider;
when(camera.testPreview.setSurfaceProvider())
.thenAnswer((_) async => testSurfaceTextureId);
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
expect(
await camera.createCamera(testCameraDescription, testResolutionPreset,
enableAudio: enableAudio),
equals(testSurfaceTextureId));
// Verify permissions are requested and the camera starts listening for device orientation changes.
expect(camera.cameraPermissionsRequested, isTrue);
expect(camera.startedListeningForDeviceOrientationChanges, isTrue);
// Verify CameraSelector is set with appropriate lens direction.
expect(camera.cameraSelector, equals(camera.mockBackCameraSelector));
// Verify the camera's Preview instance is instantiated properly.
expect(camera.preview, equals(camera.testPreview));
// Verify the camera's ImageCapture instance is instantiated properly.
expect(camera.imageCapture, equals(camera.testImageCapture));
// Verify the camera's Preview instance has its surface provider set.
verify(camera.preview!.setSurfaceProvider());
});
test(
'createCamera binds Preview and ImageCapture use cases to ProcessCameraProvider instance',
() async {
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
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;
camera.processCameraProvider = mockProcessCameraProvider;
when(mockProcessCameraProvider.bindToLifecycle(
camera.mockBackCameraSelector,
<UseCase>[camera.testPreview, camera.testImageCapture]))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
await camera.createCamera(testCameraDescription, testResolutionPreset,
enableAudio: enableAudio);
// Verify expected UseCases were bound.
verify(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!,
<UseCase>[camera.testPreview, camera.testImageCapture]));
// Verify the camera's CameraInfo instance got updated.
expect(camera.cameraInfo, equals(mockCameraInfo));
});
test(
'initializeCamera throws a CameraException when createCamera has not been called before initializedCamera',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
expect(() => camera.initializeCamera(3), throwsA(isA<CameraException>()));
});
test('initializeCamera sends expected CameraInitializedEvent', () async {
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
const int cameraId = 10;
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;
const int resolutionWidth = 350;
const int resolutionHeight = 750;
final Camera mockCamera = MockCamera();
final ResolutionInfo testResolutionInfo =
ResolutionInfo(width: resolutionWidth, height: resolutionHeight);
// TODO(camsim99): Modify this when camera configuration is supported and
// defualt values no longer being used.
// https://github.com/flutter/flutter/issues/120468
// https://github.com/flutter/flutter/issues/120467
final CameraInitializedEvent testCameraInitializedEvent =
CameraInitializedEvent(
cameraId,
resolutionWidth.toDouble(),
resolutionHeight.toDouble(),
ExposureMode.auto,
false,
FocusMode.auto,
false);
camera.processCameraProvider = mockProcessCameraProvider;
// Call createCamera.
when(camera.testPreview.setSurfaceProvider())
.thenAnswer((_) async => cameraId);
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
when(camera.testPreview.getResolutionInfo())
.thenAnswer((_) async => testResolutionInfo);
await camera.createCamera(testCameraDescription, testResolutionPreset,
enableAudio: enableAudio);
// 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 and unbinds all use cases',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
camera.preview = MockPreview();
camera.processCameraProvider = MockProcessCameraProvider();
camera.dispose(3);
verify(camera.preview!.releaseFlutterSurfaceTexture());
verify(camera.processCameraProvider!.unbindAll());
});
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('onCameraError stream emits errors caught by system services', () async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 27;
const String testErrorDescription = 'Test error description!';
final Stream<CameraErrorEvent> eventStream = camera.onCameraError(cameraId);
final StreamQueue<CameraErrorEvent> streamQueue =
StreamQueue<CameraErrorEvent>(eventStream);
SystemServices.cameraErrorStreamController.add(testErrorDescription);
expect(await streamQueue.next,
equals(const CameraErrorEvent(cameraId, testErrorDescription)));
await streamQueue.cancel();
});
test(
'onDeviceOrientationChanged stream emits changes in device oreintation 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);
SystemServices.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();
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();
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 if already bound',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();
when(camera.processCameraProvider!.isBound(camera.preview!))
.thenAnswer((_) async => true);
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
await camera.resumePreview(78);
verifyNever(camera.processCameraProvider!
.bindToLifecycle(camera.cameraSelector!, <UseCase>[camera.preview!]));
expect(camera.cameraInfo, isNot(mockCameraInfo));
});
test('resumePreview binds preview to lifecycle if not already bound',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
await camera.resumePreview(78);
verify(camera.processCameraProvider!
.bindToLifecycle(camera.cameraSelector!, <UseCase>[camera.preview!]));
expect(camera.cameraInfo, equals(mockCameraInfo));
});
test(
'buildPreview returns a FutureBuilder that does not return a Texture until the preview is bound to the lifecycle',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
const int textureId = 75;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
final FutureBuilder<void> previewWidget =
camera.buildPreview(textureId) as FutureBuilder<void>;
expect(
previewWidget.builder(
MockBuildContext(), const AsyncSnapshot<void>.nothing()),
isA<SizedBox>());
expect(
previewWidget.builder(
MockBuildContext(), const AsyncSnapshot<void>.waiting()),
isA<SizedBox>());
expect(
previewWidget.builder(MockBuildContext(),
const AsyncSnapshot<void>.withData(ConnectionState.active, null)),
isA<SizedBox>());
});
test(
'buildPreview returns a FutureBuilder that returns a Texture once the preview is bound to the lifecycle',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
const int textureId = 75;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
final FutureBuilder<void> previewWidget =
camera.buildPreview(textureId) as FutureBuilder<void>;
final Texture previewTexture = previewWidget.builder(MockBuildContext(),
const AsyncSnapshot<void>.withData(ConnectionState.done, null))
as Texture;
expect(previewTexture.textureId, equals(textureId));
});
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';
camera.processCameraProvider = MockProcessCameraProvider();
camera.cameraSelector = MockCameraSelector();
camera.imageCapture = MockImageCapture();
when(camera.imageCapture!.takePicture())
.thenAnswer((_) async => testPicturePath);
final XFile imageFile = await camera.takePicture(3);
expect(imageFile.path, equals(testPicturePath));
});
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);
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);
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);
camera.cameraInfo = mockCameraInfo;
when(mockCameraInfo.getExposureState())
.thenAnswer((_) async => exposureState);
expect(await camera.getExposureOffsetStepSize(55), 0.2);
});
test('getMaxZoomLevel returns expected exposure offset', () async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
const double maxZoomRatio = 1;
final ZoomState zoomState =
ZoomState.detached(maxZoomRatio: maxZoomRatio, minZoomRatio: 0);
camera.cameraInfo = mockCameraInfo;
when(mockCameraInfo.getZoomState()).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 ZoomState zoomState =
ZoomState.detached(maxZoomRatio: 1, minZoomRatio: minZoomRatio);
camera.cameraInfo = mockCameraInfo;
when(mockCameraInfo.getZoomState()).thenAnswer((_) async => zoomState);
expect(await camera.getMinZoomLevel(55), minZoomRatio);
});
test(
'onStreamedFrameAvailable emits CameraImageData when picked up from CameraImageData stream controller',
() async {
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
const int cameraId = 22;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
camera.createDetachedCallbacks = true;
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
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(
'onStreamedFrameAvaiable returns stream that responds expectedly to being listened to',
() async {
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
const int cameraId = 33;
final ProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final CameraSelector mockCameraSelector = MockCameraSelector();
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;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = mockCameraSelector;
camera.createDetachedCallbacks = true;
when(mockProcessCameraProvider.bindToLifecycle(
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
.thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
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(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single
as Analyzer;
verify(mockProcessCameraProvider.bindToLifecycle(
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]));
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));
// Verify camera and cameraInfo were properly updated.
expect(camera.camera, equals(mockCamera));
expect(camera.cameraInfo, equals(mockCameraInfo));
onStreamedFrameAvailableSubscription.cancel();
});
test(
'onStreamedFrameAvaiable returns stream that responds expectedly to being canceled',
() async {
final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX();
const int cameraId = 32;
final ProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final CameraSelector mockCameraSelector = MockCameraSelector();
final Camera mockCamera = MockCamera();
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = mockCameraSelector;
camera.createDetachedCallbacks = true;
when(mockProcessCameraProvider.bindToLifecycle(
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
.thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => MockCameraInfo());
final StreamSubscription<CameraImageData> imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
.thenAnswer((_) async => Future<bool>.value(true));
await imageStreamSubscription.cancel();
verify(camera.mockImageAnalysis.clearAnalyzer());
});
}
/// Mock of [AndroidCameraCameraX] that stubs behavior of some methods for
/// testing.
class MockAndroidCameraCameraX extends AndroidCameraCameraX {
bool cameraPermissionsRequested = false;
bool startedListeningForDeviceOrientationChanges = false;
// Mocks available for use throughout testing.
final MockPreview testPreview = MockPreview();
final MockImageCapture testImageCapture = MockImageCapture();
final MockCameraSelector mockBackCameraSelector = MockCameraSelector();
final MockCameraSelector mockFrontCameraSelector = MockCameraSelector();
final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
@override
Future<void> requestCameraPermissions(bool enableAudio) async {
cameraPermissionsRequested = true;
}
@override
void startListeningForDeviceOrientationChange(
bool cameraIsFrontFacing, int sensorOrientation) {
startedListeningForDeviceOrientationChanges = true;
return;
}
@override
CameraSelector createCameraSelector(int cameraSelectorLensDirection) {
switch (cameraSelectorLensDirection) {
case CameraSelector.lensFacingFront:
return mockFrontCameraSelector;
case CameraSelector.lensFacingBack:
default:
return mockBackCameraSelector;
}
}
@override
Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) {
return testPreview;
}
@override
ImageCapture createImageCapture(
int? flashMode, ResolutionInfo? targetResolution) {
return testImageCapture;
}
@override
ImageAnalysis createImageAnalysis(ResolutionInfo? targetResolution) {
return mockImageAnalysis;
}
}