Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7894cc2
Add base code from proof of concept
camsim99 Feb 7, 2023
efca3ed
Fix analyzer
camsim99 Feb 7, 2023
b4ae1db
Add example app and tests
camsim99 Feb 9, 2023
7c8168b
Actually add tets and visible for testing annotation
camsim99 Feb 9, 2023
93d442b
Make methods private
camsim99 Feb 9, 2023
d99ef9c
Merge remote-tracking branch 'upstream/main' into camx_preview
camsim99 Feb 9, 2023
bf1ca9a
Fix tests
camsim99 Feb 9, 2023
b237d6e
Fix analyze:
camsim99 Feb 10, 2023
a21429a
Update changelog
camsim99 Feb 10, 2023
dcff8bc
Formatting
camsim99 Feb 10, 2023
a183944
Merge remote-tracking branch 'upstream/main' into camx_preview
camsim99 Feb 10, 2023
f163907
Update todos with links and modify camera controller
camsim99 Feb 10, 2023
285659d
Format and add availableCameras
camsim99 Feb 10, 2023
cffc0ac
Fix mocks
camsim99 Feb 10, 2023
889802d
Try bumping mockito version
camsim99 Feb 10, 2023
24ee512
Review documentation
camsim99 Feb 10, 2023
573cf33
Re-generate mocks
camsim99 Feb 13, 2023
d08c087
Fix typo
camsim99 Feb 14, 2023
1e1dd8b
Fix another typo
camsim99 Feb 14, 2023
e1cea85
Address review and fix bug
camsim99 Feb 14, 2023
5ccc872
Merge remote-tracking branch 'upstream/main' into camx_preview
camsim99 Feb 14, 2023
7129845
Fix analyze
camsim99 Feb 14, 2023
c54c547
Merge remote-tracking branch 'upstream/main' into camx_preview
camsim99 Feb 14, 2023
5f30bc7
Fix tests
camsim99 Feb 14, 2023
8fc71f9
Foramtting
camsim99 Feb 14, 2023
d50bd87
Clear preview is paused
camsim99 Feb 15, 2023
de2fc29
Add unknown lens
camsim99 Feb 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Actually add tets and visible for testing annotation
  • Loading branch information
camsim99 committed Feb 9, 2023
commit 7c8168b39b5c8bdf4339b151c6ed977fa327efe0
Original file line number Diff line number Diff line change
Expand Up @@ -294,25 +294,30 @@ class AndroidCameraCameraX extends CameraPlatform {

// Methods for calls that need to be tested:

@visibleForTesting
Future<void> requestCameraPermissions(bool enableAudio) async {
await SystemServices.requestCameraPermissions(enableAudio);
}

@visibleForTesting
void startListeningForDeviceOrientationChange(
bool cameraIsFrontFacing, int sensorOrientation) {
SystemServices.startListeningForDeviceOrientationChange(
cameraIsFrontFacing, sensorOrientation);
}

@visibleForTesting
Future<ProcessCameraProvider> getProcessCameraProviderInstance() async {
ProcessCameraProvider instance = await ProcessCameraProvider.getInstance();
return instance;
}

@visibleForTesting
CameraSelector createCameraSelector(int cameraSelectorLensDirection) {
return CameraSelector(lensFacing: cameraSelectorLensDirection);
}

@visibleForTesting
Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) {
return Preview(
targetRotation: targetRotation, targetResolution: targetResolution);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:async/async.dart';
import 'package:camera_android_camerax/camera_android_camerax.dart';
import 'package:camera_android_camerax/src/camerax_library.g.dart';
import 'package:camera_android_camerax/src/camera.dart';
import 'package:camera_android_camerax/src/camera_selector.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/surface.dart';
import 'package:camera_android_camerax/src/system_services.dart';
import 'package:camera_android_camerax/src/use_case.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart' show DeviceOrientation;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'android_camera_camerax_test.mocks.dart';

@GenerateNiceMocks(<MockSpec<Object>>[
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stuartmorgan @bparrishMines Generate mocks for BuildContext this way has caused me to run into this error: https://cirrus-ci.com/task/6299387630977024?logs=unit_test#L203. This passes for me locally, so I'm wondering if y'all have any insight as to what might be happening with the CI?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the error is about a typedef that was added on master recently, so it's not on stable. You can probably just generate the mocks on stable.

MockSpec<BuildContext>(),
MockSpec<Camera>(),
MockSpec<CameraSelector>(),
MockSpec<Preview>(),
MockSpec<ProcessCameraProvider>(),
])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();

test(
'createCamera requests permissions, starts listening for device orientation changes, and returns flutter surface texture ID ',
() async {
final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax();
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;

when(camera.testPreview.setSurfaceProvider())
.thenAnswer((_) async => testSurfaceTextureId);

expect(
await camera.createCamera(testCameraDescription, testResolutionPreset,
enableAudio: enableAudio),
equals(testSurfaceTextureId));

// Verify permnissions 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.testCameraSelector));

// Verify ProcessCameraProvider instance is received.
expect(
camera.processCameraProvider, equals(camera.testProcessCameraProvider));

// Verify the camera's Preview instance is instantiated properly.
expect(camera.preview, equals(camera.testPreview));

// Verify the camera's Preview instance has its surface provider set.
verify(camera.preview!.setSurfaceProvider());
});

test(
'initializeCamera throws AssertionError when createCamera has not been called before initializedCamera',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
expect(() => camera.initializeCamera(3), throwsAssertionError);
});

test('initializeCamera sends expected CameraInitializedEvent', () async {
final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax();
final 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;
final int resolutionWidth = 350;
final 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.
final CameraInitializedEvent testCameraInitializedEvent =
CameraInitializedEvent(
cameraId,
resolutionWidth.toDouble(),
resolutionHeight.toDouble(),
ExposureMode.auto,
false,
FocusMode.auto,
false);

// Call createCamera.
when(camera.testPreview.setSurfaceProvider())
.thenAnswer((_) async => cameraId);
await camera.createCamera(testCameraDescription, testResolutionPreset,
enableAudio: enableAudio);

when(camera.testProcessCameraProvider.bindToLifecycle(
camera.cameraSelector, <UseCase>[camera.testPreview]))
.thenAnswer((_) async => mockCamera);
when(camera.testPreview.getResolutionInfo())
.thenAnswer((_) async => testResolutionInfo);

// Start listening to camera events stream to verify the proper CameraInitializedEvent is sent.
camera.cameraEventStreamController.stream.listen((CameraEvent event) {
expect(event, TypeMatcher<CameraInitializedEvent>());
expect(event, equals(testCameraInitializedEvent));
});

await camera.initializeCamera(cameraId);

// Verify preview was bound and unbound to get preview resolution information.
verify(camera.testProcessCameraProvider
.bindToLifecycle(camera.cameraSelector, <UseCase>[camera.testPreview]));
verify(
camera.testProcessCameraProvider.unbind(<UseCase>[camera.testPreview]));

// Check camera instance was received, but preview is no longer bound.
expect(camera.camera, equals(mockCamera));
expect(camera.previewIsBound, isFalse);
});

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();
final int cameraId = 16;
final Stream<CameraInitializedEvent> eventStream =
camera.onCameraInitialized(cameraId);
final StreamQueue<CameraInitializedEvent> streamQueue =
StreamQueue<CameraInitializedEvent>(eventStream);
final 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();
final int cameraId = 27;
final 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(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);
final 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();
camera.previewIsBound = true;

await camera.pausePreview(579);

verify(camera.processCameraProvider!.unbind(<UseCase>[camera.preview!]));
expect(camera.previewIsBound, isFalse);
});

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();

camera.processCameraProvider = MockProcessCameraProvider();
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();
camera.previewIsBound = true;

await camera.resumePreview(78);

verifyNever(camera.processCameraProvider!
.bindToLifecycle(camera.cameraSelector!, <UseCase>[camera.preview!]));
});

test('resumePreview binds preview to lifecycle if not already bound',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();

camera.processCameraProvider = MockProcessCameraProvider();
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();

await camera.resumePreview(78);

verify(camera.processCameraProvider!
.bindToLifecycle(camera.cameraSelector!, <UseCase>[camera.preview!]));
});

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 int textureId = 75;

camera.processCameraProvider = MockProcessCameraProvider();
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();

FutureBuilder<void> previewWidget =
camera.buildPreview(textureId) as FutureBuilder<void>;

expect(previewWidget.builder(MockBuildContext(), AsyncSnapshot.nothing()),
isA<SizedBox>());
expect(previewWidget.builder(MockBuildContext(), AsyncSnapshot.waiting()),
isA<SizedBox>());
expect(
previewWidget.builder(MockBuildContext(),
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 int textureId = 75;

camera.processCameraProvider = MockProcessCameraProvider();
camera.cameraSelector = MockCameraSelector();
camera.preview = MockPreview();

FutureBuilder<void> previewWidget =
camera.buildPreview(textureId) as FutureBuilder<void>;

Texture previewTexture = previewWidget.builder(MockBuildContext(),
AsyncSnapshot<void>.withData(ConnectionState.done, null)) as Texture;
expect(previewTexture.textureId, equals(textureId));
});
}

/// Mock of [AndroidCameraCameraX] that stubs behavior of some methods for
/// testing.
class MockAndroidCameraCamerax extends AndroidCameraCameraX {
bool cameraPermissionsRequested = false;
bool startedListeningForDeviceOrientationChanges = false;
final MockProcessCameraProvider testProcessCameraProvider =
MockProcessCameraProvider();
final MockPreview testPreview = MockPreview();
final MockCameraSelector testCameraSelector = MockCameraSelector();

@override
Future<void> requestCameraPermissions(bool enableAudio) async {
cameraPermissionsRequested = true;
}

@override
void startListeningForDeviceOrientationChange(
bool cameraIsFrontFacing, int sensorOrientation) {
startedListeningForDeviceOrientationChanges = true;
return;
}

@override
Future<ProcessCameraProvider> getProcessCameraProviderInstance() async {
return testProcessCameraProvider;
}

@override
CameraSelector createCameraSelector(int cameraSelectorLensDirection) {
return testCameraSelector;
}

@override
Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) {
return testPreview;
}
}
Loading