diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index f719078b681..863f99b8aa8 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.4 + +* Prevents usage of unsupported concurrent `UseCase`s based on the capabiliites of the camera device. + ## 0.6.3 * Shortens default interval that internal Java `InstanceManager` uses to release garbage collected weak references to diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 64a56f1a3b5..d7e31e92e03 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -21,6 +21,22 @@ from your project's root directory. ## Limitations +### Concurrent preview display, video recording, image capture, and image streaming + +The CameraX plugin only supports the concurrent camera use cases supported by Camerax; see +[their documentation][6] for more information. To avoid the usage of unsupported concurrent +use cases, the plugin behaves according to the following: + +* If the preview is paused (via `pausePreview`), concurrent video recording and image capture + and/or image streaming (via `startVideoCapturing(cameraId, VideoCaptureOptions(streamCallback:...))`) + is supported. +* If the preview is not paused + * **and** the camera device is at least supported hardware [`LIMITED`][8], then concurrent + image capture and video recording is supported. + * **and** the camera device is at least supported hardware [`LEVEL_3`][7], then concurrent + video recording and image streaming is supported, but concurrent video recording, image + streaming, and image capture is not supported. + ### 240p resolution configuration for video recording 240p resolution configuration for video recording is unsupported by CameraX, @@ -45,6 +61,9 @@ For more information on contributing to this plugin, see [`CONTRIBUTING.md`](CON [3]: https://docs.flutter.dev/packages-and-plugins/developing-packages#non-endorsed-federated-plugin [4]: https://pub.dev/packages/camera_android [5]: https://github.com/flutter/flutter/issues/new/choose +[6]: https://developer.android.com/media/camera/camerax/architecture#combine-use-cases +[7]: https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_3 +[8]: https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED [120462]: https://github.com/flutter/flutter/issues/120462 [125915]: https://github.com/flutter/flutter/issues/125915 [120715]: https://github.com/flutter/flutter/issues/120715 diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoFlutterApiImpl.java new file mode 100644 index 00000000000..398fc38049a --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoFlutterApiImpl.java @@ -0,0 +1,27 @@ +// 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. + +package io.flutter.plugins.camerax; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.camera.camera2.interop.Camera2CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraInfoFlutterApi; + +public class Camera2CameraInfoFlutterApiImpl extends Camera2CameraInfoFlutterApi { + private final InstanceManager instanceManager; + + public Camera2CameraInfoFlutterApiImpl( + @Nullable BinaryMessenger binaryMessenger, @Nullable InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + void create(@NonNull Camera2CameraInfo camera2CameraInfo, @Nullable Reply reply) { + if (!instanceManager.containsInstance(camera2CameraInfo)) { + create(instanceManager.addHostCreatedInstance(camera2CameraInfo), reply); + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java new file mode 100644 index 00000000000..43fd0a383b0 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java @@ -0,0 +1,111 @@ +// 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. + +package io.flutter.plugins.camerax; + +import android.content.Context; +import android.hardware.camera2.CameraCharacteristics; +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.annotation.VisibleForTesting; +import androidx.camera.camera2.interop.Camera2CameraInfo; +import androidx.camera.camera2.interop.ExperimentalCamera2Interop; +import androidx.camera.core.CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraInfoHostApi; +import java.util.Objects; + +/** + * Host API implementation for {@link Camera2CameraInfo}. + * + *

This class handles instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class Camera2CameraInfoHostApiImpl implements Camera2CameraInfoHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private final Camera2CameraInfoProxy proxy; + + /** Proxy for methods of {@link Camera2CameraInfo}. */ + @VisibleForTesting + public static class Camera2CameraInfoProxy { + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public Camera2CameraInfo createFrom(@NonNull CameraInfo cameraInfo) { + return Camera2CameraInfo.from(cameraInfo); + } + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public Integer getSupportedHardwareLevel(@NonNull Camera2CameraInfo camera2CameraInfo) { + return camera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + } + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public String getCameraId(@NonNull Camera2CameraInfo camera2CameraInfo) { + return camera2CameraInfo.getCameraId(); + } + } + + /** + * Constructs an {@link Camera2CameraInfoHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param context {@link Context} used to retrieve {@code Executor} + */ + public Camera2CameraInfoHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this(binaryMessenger, instanceManager, new Camera2CameraInfoProxy()); + } + + /** + * Constructs an {@link Camera2CameraInfoHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for methods of {@link Camera2CameraInfo} + */ + @VisibleForTesting + Camera2CameraInfoHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull Camera2CameraInfoProxy proxy) { + this.instanceManager = instanceManager; + this.binaryMessenger = binaryMessenger; + this.proxy = proxy; + } + + @Override + @NonNull + public Long createFrom(@NonNull Long cameraInfoIdentifier) { + final CameraInfo cameraInfo = + Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)); + final Camera2CameraInfo camera2CameraInfo = proxy.createFrom(cameraInfo); + final Camera2CameraInfoFlutterApiImpl flutterApi = + new Camera2CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); + + flutterApi.create(camera2CameraInfo, reply -> {}); + return instanceManager.getIdentifierForStrongReference(camera2CameraInfo); + } + + @Override + @NonNull + public Long getSupportedHardwareLevel(@NonNull Long identifier) { + return Long.valueOf(proxy.getSupportedHardwareLevel(getCamera2CameraInfoInstance(identifier))); + } + + @Override + @NonNull + public String getCameraId(@NonNull Long identifier) { + return proxy.getCameraId(getCamera2CameraInfoInstance(identifier)); + } + + private Camera2CameraInfo getCamera2CameraInfoInstance(@NonNull Long identifier) { + return Objects.requireNonNull(instanceManager.getInstance(identifier)); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index d281bbe65a8..42e5df0f32c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -136,6 +136,8 @@ public void setUp( GeneratedCameraXLibrary.MeteringPointHostApi.setup(binaryMessenger, meteringPointHostApiImpl); GeneratedCameraXLibrary.ResolutionFilterHostApi.setup( binaryMessenger, new ResolutionFilterHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.Camera2CameraInfoHostApi.setup( + binaryMessenger, new Camera2CameraInfoHostApiImpl(binaryMessenger, instanceManager)); } @Override diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java index fa6be792096..41094fd858a 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java @@ -4267,4 +4267,137 @@ static void setup( } } } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface Camera2CameraInfoHostApi { + + @NonNull + Long createFrom(@NonNull Long cameraInfoIdentifier); + + @NonNull + Long getSupportedHardwareLevel(@NonNull Long identifier); + + @NonNull + String getCameraId(@NonNull Long identifier); + + /** The codec used by Camera2CameraInfoHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `Camera2CameraInfoHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable Camera2CameraInfoHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number cameraInfoIdentifierArg = (Number) args.get(0); + try { + Long output = + api.createFrom( + (cameraInfoIdentifierArg == null) + ? null + : cameraInfoIdentifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + Long output = + api.getSupportedHardwareLevel( + (identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + String output = + api.getCameraId((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class Camera2CameraInfoFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public Camera2CameraInfoFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by Camera2CameraInfoFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void create(@NonNull Long identifierArg, @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Collections.singletonList(identifierArg)), + channelReply -> callback.reply(null)); + } + } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java new file mode 100644 index 00000000000..a5ab10ff79b --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java @@ -0,0 +1,114 @@ +// 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. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; +import androidx.camera.camera2.interop.Camera2CameraInfo; +import androidx.camera.core.CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + +public class Camera2CameraInfoTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public Camera2CameraInfo mockCamera2CameraInfo; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.stopFinalizationListener(); + } + + @Test + public void createFrom_createsInstanceFromCameraInfoInstance() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 60; + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final long cameraInfoIdentifier = 92; + + testInstanceManager.addDartCreatedInstance(mockCameraInfo, cameraInfoIdentifier); + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + + try (MockedStatic mockedCamera2CameraInfo = + Mockito.mockStatic(Camera2CameraInfo.class)) { + mockedCamera2CameraInfo + .when(() -> Camera2CameraInfo.from(mockCameraInfo)) + .thenAnswer((Answer) invocation -> mockCamera2CameraInfo); + + hostApi.createFrom(cameraInfoIdentifier); + assertEquals( + testInstanceManager.getInstance(camera2CameraInfoIdentifier), mockCamera2CameraInfo); + } + } + + @Test + public void getSupportedHardwareLevel_returnsExpectedLevel() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 3; + final int expectedHardwareLevel = CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + when(mockCamera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)) + .thenReturn(expectedHardwareLevel); + + assertEquals( + expectedHardwareLevel, + hostApi.getSupportedHardwareLevel(camera2CameraInfoIdentifier).intValue()); + } + + @Test + public void getCameraId_returnsExpectedId() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 13; + final String expectedCameraId = "testCameraId"; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + when(mockCamera2CameraInfo.getCameraId()).thenReturn(expectedCameraId); + + assertEquals(expectedCameraId, hostApi.getCameraId(camera2CameraInfoIdentifier)); + } + + @Test + public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() { + final Camera2CameraInfoFlutterApiImpl spyFlutterApi = + spy(new Camera2CameraInfoFlutterApiImpl(mock(BinaryMessenger.class), testInstanceManager)); + + spyFlutterApi.create(mockCamera2CameraInfo, reply -> {}); + + final long identifier = + Objects.requireNonNull( + testInstanceManager.getIdentifierForStrongReference(mockCamera2CameraInfo)); + verify(spyFlutterApi).create(eq(identifier), any()); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index e6ef399b657..b73ce0186be 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -17,8 +17,10 @@ import 'analyzer.dart'; import 'aspect_ratio_strategy.dart'; import 'camera.dart'; import 'camera2_camera_control.dart'; +import 'camera2_camera_info.dart'; import 'camera_control.dart'; import 'camera_info.dart'; +import 'camera_metadata.dart'; import 'camera_selector.dart'; import 'camera_state.dart'; import 'camerax_library.g.dart'; @@ -261,6 +263,8 @@ class AndroidCameraCameraX extends CameraPlatform { cameraName = 'Camera $cameraCount'; cameraCount++; + // TODO(camsim99): Use camera ID retrieved from Camera2CameraInfo as + // camera name: https://github.com/flutter/flutter/issues/147545. cameraDescriptions.add(CameraDescription( name: cameraName, lensDirection: cameraLensDirection, @@ -291,9 +295,9 @@ class AndroidCameraCameraX extends CameraPlatform { /// uninitialized camera instance, this method retrieves a /// [ProcessCameraProvider] instance. /// - /// The specified [resolutionPreset] is the target resolution that CameraX - /// will attempt to select for the [UseCase]s constructed in this method - /// ([preview], [imageCapture], [imageAnalysis], [videoCapture]). If + /// The specified `mediaSettings.resolutionPreset` is the target resolution + /// that CameraX will attempt to select for the [UseCase]s constructed in this + /// method ([preview], [imageCapture], [imageAnalysis], [videoCapture]). If /// unavailable, a fallback behavior of targeting the next highest resolution /// will be attempted. See https://developer.android.com/media/camera/camerax/configuration#specify-resolution. /// @@ -779,7 +783,7 @@ class AndroidCameraCameraX extends CameraPlatform { @override Future resumePreview(int cameraId) async { _previewIsPaused = false; - await _bindPreviewToLifecycle(cameraId); + await _bindUseCaseToLifecycle(preview!, cameraId); } /// Returns a widget showing a live camera preview. @@ -804,6 +808,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// [cameraId] is not used. @override Future takePicture(int cameraId) async { + await _bindUseCaseToLifecycle(imageCapture!, cameraId); // Set flash mode. if (_currentFlashMode != null) { await imageCapture!.setFlashMode(_currentFlashMode!); @@ -890,12 +895,50 @@ class AndroidCameraCameraX extends CameraPlatform { return; } - if (!(await processCameraProvider!.isBound(videoCapture!))) { - camera = await processCameraProvider! - .bindToLifecycle(cameraSelector!, [videoCapture!]); - await _updateCameraInfoAndLiveCameraState(options.cameraId); + dynamic Function(CameraImageData)? streamCallback = options.streamCallback; + if (!_previewIsPaused) { + // The plugin binds the preview use case to the camera lifecycle when + // createCamera is called, but camera use cases can become limited + // when video recording and displaying a preview concurrently. This logic + // will prioritize attempting to continue displaying the preview, + // stream images, and record video if specified and supported. Otherwise, + // the preview must be paused in order to allow those concurrently. See + // https://developer.android.com/media/camera/camerax/architecture#combine-use-cases + // for more information on supported concurrent camera use cases. + final Camera2CameraInfo camera2CameraInfo = + await proxy.getCamera2CameraInfo(cameraInfo!); + final int cameraInfoSupportedHardwareLevel = + await camera2CameraInfo.getSupportedHardwareLevel(); + + // Handle limited level device restrictions: + final bool cameraSupportsConcurrentImageCapture = + cameraInfoSupportedHardwareLevel != + CameraMetadata.infoSupportedHardwareLevelLegacy; + if (!cameraSupportsConcurrentImageCapture) { + // Concurrent preview + video recording + image capture is not supported + // unless the camera device is cameraSupportsHardwareLevelLimited or + // better. + await _unbindUseCaseFromLifecycle(imageCapture!); + } + + // Handle level 3 device restrictions: + final bool cameraSupportsHardwareLevel3 = + cameraInfoSupportedHardwareLevel == + CameraMetadata.infoSupportedHardwareLevel3; + if (!cameraSupportsHardwareLevel3 || streamCallback == null) { + // Concurrent preview + video recording + image streaming is not supported + // unless the camera device is cameraSupportsHardwareLevel3 or better. + streamCallback = null; + await _unbindUseCaseFromLifecycle(imageAnalysis!); + } else { + // If image streaming concurrently with video recording, image capture + // is unsupported. + await _unbindUseCaseFromLifecycle(imageCapture!); + } } + await _bindUseCaseToLifecycle(videoCapture!, options.cameraId); + // Set target rotation to default CameraX rotation only if capture // orientation not locked. if (!captureOrientationLocked && shouldSetDefaultRotation) { @@ -908,8 +951,8 @@ class AndroidCameraCameraX extends CameraPlatform { pendingRecording = await recorder!.prepareRecording(videoOutputPath!); recording = await pendingRecording!.start(); - if (options.streamCallback != null) { - onStreamedFrameAvailable(options.cameraId).listen(options.streamCallback); + if (streamCallback != null) { + onStreamedFrameAvailable(options.cameraId).listen(streamCallback); } } @@ -942,6 +985,7 @@ class AndroidCameraCameraX extends CameraPlatform { await recording!.close(); recording = null; pendingRecording = null; + await _unbindUseCaseFromLifecycle(videoCapture!); return XFile(videoOutputPath!); } @@ -975,7 +1019,7 @@ class AndroidCameraCameraX extends CameraPlatform { Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { cameraImageDataStreamController = StreamController( - onListen: () => _configureImageAnalysis(cameraId), + onListen: () async => _configureImageAnalysis(cameraId), onCancel: _onFrameStreamCancel, ); return cameraImageDataStreamController!.stream; @@ -984,26 +1028,32 @@ class AndroidCameraCameraX extends CameraPlatform { // Methods for binding UseCases to the lifecycle of the camera controlled // by a ProcessCameraProvider instance: - /// Binds [preview] instance to the camera lifecycle controlled by the - /// [processCameraProvider]. + /// Binds [useCase] to the camera lifecycle controlled by the + /// [processCameraProvider] if not already bound. /// /// [cameraId] used to build [CameraEvent]s should you wish to filter /// these based on a reference to a cameraId received from calling /// `createCamera(...)`. - Future _bindPreviewToLifecycle(int cameraId) async { - final bool previewIsBound = await processCameraProvider!.isBound(preview!); - if (previewIsBound || _previewIsPaused) { - // Only bind if preview is not already bound or intentionally paused. + Future _bindUseCaseToLifecycle(UseCase useCase, int cameraId) async { + final bool useCaseIsBound = await processCameraProvider!.isBound(useCase); + final bool useCaseIsPausedPreview = useCase is Preview && _previewIsPaused; + + if (useCaseIsBound || useCaseIsPausedPreview) { + // Only bind if useCase is not already bound or preview is intentionally + // paused. return; } camera = await processCameraProvider! - .bindToLifecycle(cameraSelector!, [preview!]); + .bindToLifecycle(cameraSelector!, [useCase]); + await _updateCameraInfoAndLiveCameraState(cameraId); } /// Configures the [imageAnalysis] instance for image streaming. Future _configureImageAnalysis(int cameraId) async { + await _bindUseCaseToLifecycle(imageAnalysis!, cameraId); + // Set target rotation to default CameraX rotation only if capture // orientation not locked. if (!captureOrientationLocked && shouldSetDefaultRotation) { @@ -1044,7 +1094,7 @@ class AndroidCameraCameraX extends CameraPlatform { } /// Unbinds [useCase] from camera lifecycle controlled by the - /// [processCameraProvider]. + /// [processCameraProvider] if not already unbound. Future _unbindUseCaseFromLifecycle(UseCase useCase) async { final bool useCaseIsBound = await processCameraProvider!.isBound(useCase); if (!useCaseIsBound) { @@ -1145,13 +1195,13 @@ class AndroidCameraCameraX extends CameraPlatform { int _getRotationConstantFromDeviceOrientation(DeviceOrientation orientation) { switch (orientation) { case DeviceOrientation.portraitUp: - return Surface.ROTATION_0; + return Surface.rotation0; case DeviceOrientation.landscapeLeft: - return Surface.ROTATION_90; + return Surface.rotation90; case DeviceOrientation.portraitDown: - return Surface.ROTATION_180; + return Surface.rotation180; case DeviceOrientation.landscapeRight: - return Surface.ROTATION_270; + return Surface.rotation270; } } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index db7ec4b32d4..c4e95d0037f 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -4,6 +4,7 @@ import 'analyzer.dart'; import 'camera.dart'; +import 'camera2_camera_info.dart'; import 'camera_control.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; @@ -52,7 +53,8 @@ class AndroidCameraXCameraFlutterApis { PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl, AnalyzerFlutterApiImpl? analyzerFlutterApiImpl, CameraControlFlutterApiImpl? cameraControlFlutterApiImpl, - FocusMeteringResultFlutterApiImpl? focusMeteringResultFlutterApiImpl}) { + FocusMeteringResultFlutterApiImpl? focusMeteringResultFlutterApiImpl, + Camera2CameraInfoFlutterApiImpl? camera2CameraInfoFlutterApiImpl}) { this.javaObjectFlutterApiImpl = javaObjectFlutterApiImpl ?? JavaObjectFlutterApiImpl(); this.cameraInfoFlutterApiImpl = @@ -99,6 +101,8 @@ class AndroidCameraXCameraFlutterApis { this.focusMeteringResultFlutterApiImpl = focusMeteringResultFlutterApiImpl ?? FocusMeteringResultFlutterApiImpl(); + this.camera2CameraInfoFlutterApiImpl = + camera2CameraInfoFlutterApiImpl ?? Camera2CameraInfoFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -178,6 +182,9 @@ class AndroidCameraXCameraFlutterApis { late final FocusMeteringResultFlutterApiImpl focusMeteringResultFlutterApiImpl; + /// Fluter Api implementation for [Camera2CameraInfo]. + late final Camera2CameraInfoFlutterApiImpl camera2CameraInfoFlutterApiImpl; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -205,6 +212,7 @@ class AndroidCameraXCameraFlutterApis { ObserverFlutterApi.setup(observerFlutterApiImpl); CameraControlFlutterApi.setup(cameraControlFlutterApiImpl); FocusMeteringResultFlutterApi.setup(focusMeteringResultFlutterApiImpl); + Camera2CameraInfoFlutterApi.setup(camera2CameraInfoFlutterApiImpl); _haveBeenSetUp = true; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart new file mode 100644 index 00000000000..fafb90f0ecb --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart @@ -0,0 +1,131 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart' show BinaryMessenger; +import 'package:meta/meta.dart' show immutable; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camera_info.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// Interface for retrieving Camera2-related camera information. +/// +/// See https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraInfo. +@immutable +class Camera2CameraInfo extends JavaObject { + /// Constructs a [Camera2CameraInfo] that is not automatically attached to a native object. + Camera2CameraInfo.detached( + {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _Camera2CameraInfoHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + late final _Camera2CameraInfoHostApiImpl _api; + + /// Retrieves [Camera2CameraInfo] instance from [cameraInfo]. + static Future from(CameraInfo cameraInfo, + {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) { + final _Camera2CameraInfoHostApiImpl api = _Camera2CameraInfoHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + return api.fromInstances(cameraInfo); + } + + /// Retrieves the value of `CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL` + /// for the device to which this instance pertains to. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + /// for more information. + Future getSupportedHardwareLevel() => + _api.getSupportedHardwareLevelFromInstance(this); + + /// Gets the camera ID. + /// + /// The ID may change based on the internal configuration of the camera to which + /// this instances pertains. + Future getCameraId() => _api.getCameraIdFromInstance(this); +} + +/// Host API implementation of [Camera2CameraInfo]. +class _Camera2CameraInfoHostApiImpl extends Camera2CameraInfoHostApi { + /// Constructs a [_Camera2CameraInfoHostApiImpl]. + _Camera2CameraInfoHostApiImpl( + {this.binaryMessenger, InstanceManager? instanceManager}) + : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Maintains instances stored to communicate with native language objects. + late final InstanceManager instanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default [BinaryMessenger] will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Gets sensor orientation degrees of the specified [CameraInfo] instance. + Future fromInstances( + CameraInfo cameraInfo, + ) async { + final int? cameraInfoIdentifier = instanceManager.getIdentifier(cameraInfo); + return instanceManager.getInstanceWithWeakReference( + await createFrom(cameraInfoIdentifier!))!; + } + + Future getSupportedHardwareLevelFromInstance( + Camera2CameraInfo instance) { + final int? identifier = instanceManager.getIdentifier(instance); + return getSupportedHardwareLevel(identifier!); + } + + Future getCameraIdFromInstance(Camera2CameraInfo instance) { + final int? identifier = instanceManager.getIdentifier(instance); + return getCameraId(identifier!); + } +} + +/// Flutter API Implementation of [Camera2CameraInfo]. +class Camera2CameraInfoFlutterApiImpl implements Camera2CameraInfoFlutterApi { + /// Constructs an [Camera2CameraInfoFlutterApiImpl]. + /// + /// If [binaryMessenger] is null, the default [BinaryMessenger] will be used, + /// which routes to the host platform. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. If left null, it + /// will default to the global instance defined in [JavaObject]. + Camera2CameraInfoFlutterApiImpl({ + BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + }) : _binaryMessenger = binaryMessenger, + _instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + final BinaryMessenger? _binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager _instanceManager; + + @override + void create(int identifier) { + _instanceManager.addHostCreatedInstance( + Camera2CameraInfo.detached( + binaryMessenger: _binaryMessenger, instanceManager: _instanceManager), + identifier, + onCopy: (Camera2CameraInfo original) { + return Camera2CameraInfo.detached( + binaryMessenger: _binaryMessenger, + instanceManager: _instanceManager); + }, + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart b/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart new file mode 100644 index 00000000000..65098b747c6 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart @@ -0,0 +1,42 @@ +// 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:meta/meta.dart' show immutable; + +/// Base class for camera controls and information. +/// +/// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata. +@immutable +class CameraMetadata { + /// Constant that specifies a camera device does not have enough to quality as + /// a [infoSupportedHardwareLevelFull] level device or better. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED. + static const int infoSupportedHardwareLevelLimited = 0; + + /// Constant that specifies a camera device is capable of supporting advanced + /// imaging applications. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_FULL. + static const int infoSupportedHardwareLevelFull = 1; + + /// Constant that specifies a camera device is running in backward + /// compatibility mode. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY. + static const int infoSupportedHardwareLevelLegacy = 2; + + /// Constant that specifies a camera device is capable of YUV reprocessing and + /// RAW data capture in addition to [infoSupportedHardwareLevelFull] level + /// capabilities. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_3. + static const int infoSupportedHardwareLevel3 = 3; + + /// Constant taht specifies a camera device is backed by an external camera + /// connected to this Android device. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL. + static const int infoSupportedHardwareLevelExternal = 4; +} diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index ac66dfd0ae7..550854fba3e 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -3403,3 +3403,125 @@ class ResolutionFilterHostApi { } } } + +class Camera2CameraInfoHostApi { + /// Constructor for [Camera2CameraInfoHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + Camera2CameraInfoHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future createFrom(int arg_cameraInfoIdentifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_cameraInfoIdentifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as int?)!; + } + } + + Future getSupportedHardwareLevel(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as int?)!; + } + } + + Future getCameraId(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as String?)!; + } + } +} + +abstract class Camera2CameraInfoFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier); + + static void setup(Camera2CameraInfoFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create was null, expected non-null int.'); + api.create(arg_identifier!); + return; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart index 6fec50ce398..d81d1c761c6 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart @@ -7,6 +7,7 @@ import 'dart:ui' show Size; import 'analyzer.dart'; import 'aspect_ratio_strategy.dart'; import 'camera2_camera_control.dart'; +import 'camera2_camera_info.dart'; import 'camera_control.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; @@ -64,6 +65,7 @@ class CameraXProxy { this.createAspectRatioStrategy = _createAttachedAspectRatioStrategy, this.createResolutionFilterWithOnePreferredSize = _createAttachedResolutionFilterWithOnePreferredSize, + this.getCamera2CameraInfo = _getCamera2CameraInfo, }); /// Returns a [ProcessCameraProvider] instance. @@ -154,7 +156,7 @@ class CameraXProxy { /// rotation constants. Future Function() getDefaultDisplayRotation; - /// Get [Camera2CameraControl] instance from [cameraControl]. + /// Gets [Camera2CameraControl] instance from [cameraControl]. Camera2CameraControl Function(CameraControl cameraControl) getCamera2CameraControl; @@ -183,6 +185,10 @@ class CameraXProxy { ResolutionFilter Function(Size preferredResolution) createResolutionFilterWithOnePreferredSize; + /// Gets [Camera2CameraInfo] instance from [cameraInfo]. + Future Function(CameraInfo cameraInfo) + getCamera2CameraInfo; + static Future _getProcessCameraProvider() { return ProcessCameraProvider.getInstance(); } @@ -324,4 +330,9 @@ class CameraXProxy { return ResolutionFilter.onePreferredSize( preferredResolution: preferredSize); } + + static Future _getCamera2CameraInfo( + CameraInfo cameraInfo) async { + return Camera2CameraInfo.from(cameraInfo); + } } diff --git a/packages/camera/camera_android_camerax/lib/src/surface.dart b/packages/camera/camera_android_camerax/lib/src/surface.dart index e0ca96f639e..925b43bf3c0 100644 --- a/packages/camera/camera_android_camerax/lib/src/surface.dart +++ b/packages/camera/camera_android_camerax/lib/src/surface.dart @@ -18,20 +18,20 @@ class Surface extends JavaObject { /// Rotation constant to signify the natural orientation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_0. - static const int ROTATION_0 = 0; + static const int rotation0 = 0; /// Rotation constant to signify a 90 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_90. - static const int ROTATION_90 = 1; + static const int rotation90 = 1; /// Rotation constant to signify a 180 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_180. - static const int ROTATION_180 = 2; + static const int rotation180 = 2; /// Rotation constant to signify a 270 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_270. - static const int ROTATION_270 = 3; + static const int rotation270 = 3; } diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 18741904a8d..f51eae8c306 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -543,3 +543,17 @@ abstract class ResolutionFilterHostApi { void createWithOnePreferredSize( int identifier, ResolutionInfo preferredResolution); } + +@HostApi(dartHostTestHandler: 'TestCamera2CameraInfoHostApi') +abstract class Camera2CameraInfoHostApi { + int createFrom(int cameraInfoIdentifier); + + int getSupportedHardwareLevel(int identifier); + + String getCameraId(int identifier); +} + +@FlutterApi() +abstract class Camera2CameraInfoFlutterApi { + void create(int identifier); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index b3ce1dfca29..8f1b5d11bbc 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.6.3 +version: 0.6.4 environment: sdk: ^3.1.0 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index da1bd76b03d..f0caef74a24 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -11,8 +11,10 @@ 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/camera2_camera_info.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_metadata.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'; @@ -59,10 +61,12 @@ import 'test_camerax_library.g.dart'; @GenerateNiceMocks(>[ MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -82,10 +86,9 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), - MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), ]) @GenerateMocks([], customMocks: >[ @@ -384,6 +387,8 @@ void main() { createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), ); + camera.processCameraProvider = mockProcessCameraProvider; + when(mockPreview.setSurfaceProvider()) .thenAnswer((_) async => testSurfaceTextureId); when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, @@ -392,7 +397,6 @@ void main() { when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => mockLiveCameraState); - camera.processCameraProvider = mockProcessCameraProvider; expect( await camera.createCameraWithSettings( @@ -1259,6 +1263,8 @@ void main() { final MockCameraControl mockCameraControl = MockCameraControl(); final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); final MockLiveCameraState newMockLiveCameraState = MockLiveCameraState(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); final TestSystemServicesHostApi mockSystemServicesApi = MockTestSystemServicesHostApi(); TestSystemServicesHostApi.setup(mockSystemServicesApi); @@ -1270,6 +1276,8 @@ void main() { camera.videoCapture = MockVideoCapture(); camera.cameraSelector = MockCameraSelector(); camera.liveCameraState = mockLiveCameraState; + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -1277,7 +1285,9 @@ void main() { // Tell plugin to create detached Observer when camera info updated. camera.proxy = CameraXProxy( createCameraStateObserver: (void Function(Object) onChanged) => - Observer.detached(onChanged: onChanged)); + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); const int cameraId = 17; const String outputPath = '/temp/MOV123.temp'; @@ -1299,6 +1309,8 @@ void main() { .thenAnswer((_) async => mockCameraControl); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => newMockLiveCameraState); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelLimited); await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); @@ -1332,6 +1344,8 @@ void main() { final MockRecording mockRecording = MockRecording(); final MockCamera mockCamera = MockCamera(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); final TestSystemServicesHostApi mockSystemServicesApi = MockTestSystemServicesHostApi(); TestSystemServicesHostApi.setup(mockSystemServicesApi); @@ -1341,6 +1355,8 @@ void main() { camera.recorder = MockRecorder(); camera.videoCapture = MockVideoCapture(); camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -1348,7 +1364,9 @@ void main() { // Tell plugin to create detached Observer when camera info updated. camera.proxy = CameraXProxy( createCameraStateObserver: (void Function(Object) onChanged) => - Observer.detached(onChanged: onChanged)); + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); const int cameraId = 17; const String outputPath = '/temp/MOV123.temp'; @@ -1368,6 +1386,8 @@ void main() { .thenAnswer((_) => Future.value(mockCameraInfo)); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelLimited); await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); @@ -1392,21 +1412,27 @@ void main() { () async { // Set up mocks and constants. final AndroidCameraCameraX camera = AndroidCameraCameraX(); - - // Set directly for test versus calling createCamera. final MockProcessCameraProvider mockProcessCameraProvider = MockProcessCameraProvider(); + final Recorder mockRecorder = MockRecorder(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockCameraInfo initialCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + 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); + camera.cameraInfo = initialCameraInfo; + camera.imageCapture = MockImageCapture(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -1415,7 +1441,11 @@ void main() { camera.proxy = CameraXProxy( createAnalyzer: (Future Function(ImageProxy imageProxy) analyze) => - Analyzer.detached(analyze: analyze)); + Analyzer.detached(analyze: analyze), + getCamera2CameraInfo: (CameraInfo cameraInfo) async => + cameraInfo == initialCameraInfo + ? mockCamera2CameraInfo + : MockCamera2CameraInfo()); const int cameraId = 17; const String outputPath = '/temp/MOV123.temp'; @@ -1429,6 +1459,8 @@ void main() { // Mock method calls. when(camera.processCameraProvider!.isBound(camera.videoCapture!)) .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) .thenReturn(outputPath); when(camera.recorder!.prepareRecording(outputPath)) @@ -1437,6 +1469,8 @@ void main() { .thenAnswer((_) => Future.value(camera.camera)); when(camera.camera!.getCameraInfo()) .thenAnswer((_) => Future.value(MockCameraInfo())); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); await camera.startVideoCapturing(videoCaptureOptions); @@ -1455,10 +1489,13 @@ void main() { final MockPendingRecording mockPendingRecording = MockPendingRecording(); final MockRecording mockRecording = MockRecording(); final MockVideoCapture mockVideoCapture = MockVideoCapture(); + final MockCameraInfo initialCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); final TestSystemServicesHostApi mockSystemServicesApi = MockTestSystemServicesHostApi(); TestSystemServicesHostApi.setup(mockSystemServicesApi); - const int defaultTargetRotation = Surface.ROTATION_270; + const int defaultTargetRotation = Surface.rotation270; // Set directly for test versus calling createCamera. camera.processCameraProvider = MockProcessCameraProvider(); @@ -1466,11 +1503,17 @@ void main() { camera.recorder = MockRecorder(); camera.videoCapture = mockVideoCapture; camera.cameraSelector = MockCameraSelector(); + camera.imageAnalysis = MockImageAnalysis(); + camera.cameraInfo = initialCameraInfo; - // Tell plugin to mock call to get current video orientation. + // Tell plugin to mock call to get current video orientation and mock Camera2CameraInfo retrieval. camera.proxy = CameraXProxy( getDefaultDisplayRotation: () => - Future.value(defaultTargetRotation)); + Future.value(defaultTargetRotation), + getCamera2CameraInfo: (CameraInfo cameraInfo) async => + cameraInfo == initialCameraInfo + ? mockCamera2CameraInfo + : MockCamera2CameraInfo()); const int cameraId = 87; const String outputPath = '/temp/MOV123.temp'; @@ -1483,6 +1526,8 @@ void main() { when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); when(camera.processCameraProvider!.isBound(camera.videoCapture!)) .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => false); // Orientation is unlocked and plugin does not need to set default target // rotation manually. @@ -1578,67 +1623,114 @@ void main() { await camera.stopVideoRecording(0); }, throwsA(isA())); }); - }); - 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(); + 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; + // 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); + // 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())); - expect(camera.recording, null); - }); + await expectLater(() async { + await camera.stopVideoRecording(0); + }, throwsA(isA())); + 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'; + 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; + // 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); + final XFile file = await camera.stopVideoRecording(0); + expect(file.path, videoOutputPath); - await expectLater(() async { - await camera.stopVideoRecording(0); - }, throwsA(isA())); + await expectLater(() async { + await camera.stopVideoRecording(0); + }, throwsA(isA())); + }); + + test( + 'VideoCapture use case is unbound from lifecycle when video recording stops', + () 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); + + await camera.stopVideoRecording(90); + verify(processCameraProvider.unbind([videoCapture])); + + // Verify that recording stops. + verify(recording.close()); + verifyNoMoreInteractions(recording); + }); }); test( - 'takePicture binds and unbinds ImageCapture to lifecycle and makes call to take a picture', + 'takePicture binds ImageCapture to lifecycle and makes call to take a picture', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); const String testPicturePath = 'test/absolute/path/to/picture'; // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = MockCameraSelector(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; + // Tell plugin to create detached camera state observers. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged)); + + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => false); + when(mockProcessCameraProvider.bindToLifecycle( + camera.cameraSelector, [camera.imageCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); when(camera.imageCapture!.takePicture()) .thenAnswer((_) async => testPicturePath); @@ -1652,17 +1744,23 @@ void main() { () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); final MockImageCapture mockImageCapture = MockImageCapture(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + const int cameraId = 3; - const int defaultTargetRotation = Surface.ROTATION_180; + const int defaultTargetRotation = Surface.rotation180; // Set directly for test versus calling createCamera. camera.imageCapture = mockImageCapture; + camera.processCameraProvider = mockProcessCameraProvider; // Tell plugin to mock call to get current photo orientation. camera.proxy = CameraXProxy( getDefaultDisplayRotation: () => Future.value(defaultTargetRotation)); + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => true); when(camera.imageCapture!.takePicture()) .thenAnswer((_) async => 'test/absolute/path/to/picture'); @@ -1695,15 +1793,21 @@ void main() { test('takePicture turns non-torch flash mode off when torch mode enabled', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); const int cameraId = 77; // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); camera.cameraControl = MockCameraControl(); + camera.processCameraProvider = mockProcessCameraProvider; // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => true); + await camera.setFlashMode(cameraId, FlashMode.torch); await camera.takePicture(cameraId); verify(camera.imageCapture!.setFlashMode(ImageCapture.flashModeOff)); @@ -1715,6 +1819,8 @@ void main() { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 22; final MockCameraControl mockCameraControl = MockCameraControl(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); @@ -1722,6 +1828,10 @@ void main() { // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; + camera.processCameraProvider = mockProcessCameraProvider; + + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => true); for (final FlashMode flashMode in FlashMode.values) { await camera.setFlashMode(cameraId, flashMode); @@ -1939,6 +2049,8 @@ void main() { when(mockProcessCameraProvider.bindToLifecycle(any, any)) .thenAnswer((_) => Future.value(mockCamera)); + when(mockProcessCameraProvider.isBound(camera.imageAnalysis)) + .thenAnswer((_) async => true); when(mockCamera.getCameraInfo()) .thenAnswer((_) => Future.value(mockCameraInfo)); when(mockCameraInfo.getCameraState()) @@ -1962,8 +2074,6 @@ void main() { 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. @@ -1980,12 +2090,8 @@ void main() { // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; - when(mockProcessCameraProvider.bindToLifecycle(any, any)) - .thenAnswer((_) => Future.value(mockCamera)); - when(mockCamera.getCameraInfo()) - .thenAnswer((_) => Future.value(mockCameraInfo)); - when(mockCameraInfo.getCameraState()) - .thenAnswer((_) async => MockLiveCameraState()); + when(mockProcessCameraProvider.isBound(camera.imageAnalysis)) + .thenAnswer((_) async => true); final CameraImageData mockCameraImageData = MockCameraImageData(); final Stream imageStream = @@ -2034,7 +2140,9 @@ void main() { camera.proxy = CameraXProxy( createAnalyzer: (Future Function(ImageProxy imageProxy) analyze) => - Analyzer.detached(analyze: analyze)); + Analyzer.detached(analyze: analyze), + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged)); // Set directly for test versus calling createCamera. camera.processCameraProvider = mockProcessCameraProvider; @@ -2045,7 +2153,7 @@ void main() { camera.captureOrientationLocked = true; when(mockProcessCameraProvider.isBound(mockImageAnalysis)) - .thenAnswer((_) async => Future.value(false)); + .thenAnswer((_) async => false); when(mockProcessCameraProvider .bindToLifecycle(mockCameraSelector, [mockImageAnalysis])) .thenAnswer((_) async => mockCamera); @@ -2053,7 +2161,7 @@ void main() { when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => MockLiveCameraState()); when(mockImageProxy.getPlanes()) - .thenAnswer((_) => Future>.value(mockPlanes)); + .thenAnswer((_) async => Future>.value(mockPlanes)); when(mockPlane.buffer).thenReturn(buffer); when(mockPlane.rowStride).thenReturn(rowStride); when(mockPlane.pixelStride).thenReturn(pixelStride); @@ -2071,6 +2179,7 @@ void main() { }); // Test ImageAnalysis use case is bound to ProcessCameraProvider. + await untilCalled(mockImageAnalysis.setAnalyzer(any)); final Analyzer capturedAnalyzer = verify(mockImageAnalysis.setAnalyzer(captureAny)).captured.single as Analyzer; @@ -2097,9 +2206,12 @@ void main() { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 32; final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); // Set directly for test versus calling createCamera. camera.imageAnalysis = mockImageAnalysis; + camera.processCameraProvider = mockProcessCameraProvider; // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -2107,6 +2219,9 @@ void main() { // Tell plugin to create a detached analyzer for testing purposes. camera.proxy = CameraXProxy(createAnalyzer: (_) => MockAnalyzer()); + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) + .thenAnswer((_) async => true); + final StreamSubscription imageStreamSubscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData data) {}); @@ -2121,11 +2236,14 @@ void main() { () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 35; - const int defaultTargetRotation = Surface.ROTATION_90; + const int defaultTargetRotation = Surface.rotation90; final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); // Set directly for test versus calling createCamera. camera.imageAnalysis = mockImageAnalysis; + camera.processCameraProvider = mockProcessCameraProvider; // Tell plugin to create a detached analyzer for testing purposes and mock // call to get current photo orientation. @@ -2134,6 +2252,9 @@ void main() { getDefaultDisplayRotation: () => Future.value(defaultTargetRotation)); + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) + .thenAnswer((_) async => true); + // Orientation is unlocked and plugin does not need to set default target // rotation manually. StreamSubscription imageStreamSubscription = camera @@ -2195,13 +2316,13 @@ void main() { int? expectedTargetRotation; switch (orientation) { case DeviceOrientation.portraitUp: - expectedTargetRotation = Surface.ROTATION_0; + expectedTargetRotation = Surface.rotation0; case DeviceOrientation.landscapeLeft: - expectedTargetRotation = Surface.ROTATION_90; + expectedTargetRotation = Surface.rotation90; case DeviceOrientation.portraitDown: - expectedTargetRotation = Surface.ROTATION_180; + expectedTargetRotation = Surface.rotation180; case DeviceOrientation.landscapeRight: - expectedTargetRotation = Surface.ROTATION_270; + expectedTargetRotation = Surface.rotation270; } await camera.lockCaptureOrientation(cameraId, orientation); @@ -3496,4 +3617,427 @@ void main() { verificationResult.captured.single as FocusMeteringAction; expect(capturedAction.disableAutoCancel, isFalse); }); + + test( + 'onStreamedFrameAvailable binds ImageAnalysis use case when not already bound', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 22; + final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Set directly for test versus calling createCamera. + camera.imageAnalysis = mockImageAnalysis; + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = MockCameraSelector(); + + // 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(), + createCameraStateObserver: (_) => MockObserver(), + ); + + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) + .thenAnswer((_) async => false); + when(mockProcessCameraProvider.bindToLifecycle( + any, [mockImageAnalysis])).thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + + final StreamSubscription imageStreamSubscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData data) {}); + + await untilCalled(mockImageAnalysis.setAnalyzer(any)); + verify(mockProcessCameraProvider + .bindToLifecycle(camera.cameraSelector, [mockImageAnalysis])); + + await imageStreamSubscription.cancel(); + }); + + test( + 'startVideoCapturing unbinds ImageAnalysis use case when camera device is not at least level 3, no image streaming callback is specified, and preview is not paused', + () 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 MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + 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(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + + // 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.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 7; + 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!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevelFull); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback not specified, camera device is level 3, and preview is not paused', + () 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 MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + 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(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + + // 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.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 77; + 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!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback is specified, camera device is not at least level 3, and preview is not paused', + () 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 MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + 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(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + + // 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.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + 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 => false); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelExternal); + + await camera.startVideoCapturing(VideoCaptureOptions(cameraId, + streamCallback: (CameraImageData image) {})); + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageCapture use case when image streaming callback is specified, camera device is at least level 3, and preview is not paused', + () 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 MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + 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(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + camera.imageCapture = MockImageCapture(); + + // 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( + createAnalyzer: + (Future Function(ImageProxy imageProxy) analyze) => + Analyzer.detached(analyze: analyze), + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 107; + 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!.isBound(camera.imageCapture!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); + + await camera.startVideoCapturing(VideoCaptureOptions(cameraId, + streamCallback: (CameraImageData image) {})); + verify( + camera.processCameraProvider!.unbind([camera.imageCapture!])); + }); + + test( + 'startVideoCapturing does not unbind ImageCapture or ImageAnalysis use cases when preview is paused', + () 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 MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + 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(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + camera.imageCapture = MockImageCapture(); + camera.preview = MockPreview(); + + // 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.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 97; + 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!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + + await camera.pausePreview(cameraId); + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verifyNever( + camera.processCameraProvider!.unbind([camera.imageCapture!])); + verifyNever( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageCapture and ImageAnalysis use cases when running on a legacy hardware device', + () 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 MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + 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(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + camera.imageCapture = MockImageCapture(); + camera.preview = MockPreview(); + + // 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.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 44; + 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!.isBound(camera.imageCapture!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelLegacy); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verify( + camera.processCameraProvider!.unbind([camera.imageCapture!])); + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 5d6d2051212..0bdfc06af92 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -4,50 +4,51 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i17; -import 'dart:typed_data' as _i33; -import 'dart:ui' as _i11; +import 'dart:typed_data' as _i34; +import 'dart:ui' as _i14; import 'package:camera_android_camerax/src/analyzer.dart' as _i16; import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart' as _i19; -import 'package:camera_android_camerax/src/camera.dart' as _i9; +import 'package:camera_android_camerax/src/camera.dart' as _i12; import 'package:camera_android_camerax/src/camera2_camera_control.dart' as _i24; -import 'package:camera_android_camerax/src/camera_control.dart' as _i3; -import 'package:camera_android_camerax/src/camera_info.dart' as _i2; -import 'package:camera_android_camerax/src/camera_selector.dart' as _i26; +import 'package:camera_android_camerax/src/camera2_camera_info.dart' as _i26; +import 'package:camera_android_camerax/src/camera_control.dart' as _i6; +import 'package:camera_android_camerax/src/camera_info.dart' as _i5; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i28; import 'package:camera_android_camerax/src/camera_state.dart' as _i20; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i10; import 'package:camera_android_camerax/src/capture_request_options.dart' as _i25; -import 'package:camera_android_camerax/src/exposure_state.dart' as _i5; -import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i27; +import 'package:camera_android_camerax/src/exposure_state.dart' as _i8; +import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i29; import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i23; import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i22; -import 'package:camera_android_camerax/src/image_analysis.dart' as _i28; -import 'package:camera_android_camerax/src/image_capture.dart' as _i29; +import 'package:camera_android_camerax/src/image_analysis.dart' as _i30; +import 'package:camera_android_camerax/src/image_capture.dart' as _i31; import 'package:camera_android_camerax/src/image_proxy.dart' as _i18; -import 'package:camera_android_camerax/src/live_data.dart' as _i4; -import 'package:camera_android_camerax/src/observer.dart' as _i32; -import 'package:camera_android_camerax/src/pending_recording.dart' as _i10; -import 'package:camera_android_camerax/src/plane_proxy.dart' as _i31; -import 'package:camera_android_camerax/src/preview.dart' as _i34; +import 'package:camera_android_camerax/src/live_data.dart' as _i7; +import 'package:camera_android_camerax/src/observer.dart' as _i33; +import 'package:camera_android_camerax/src/pending_recording.dart' as _i13; +import 'package:camera_android_camerax/src/plane_proxy.dart' as _i32; +import 'package:camera_android_camerax/src/preview.dart' as _i35; import 'package:camera_android_camerax/src/process_camera_provider.dart' - as _i35; -import 'package:camera_android_camerax/src/quality_selector.dart' as _i37; -import 'package:camera_android_camerax/src/recorder.dart' as _i12; -import 'package:camera_android_camerax/src/recording.dart' as _i8; -import 'package:camera_android_camerax/src/resolution_filter.dart' as _i38; -import 'package:camera_android_camerax/src/resolution_selector.dart' as _i39; -import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i40; -import 'package:camera_android_camerax/src/use_case.dart' as _i36; -import 'package:camera_android_camerax/src/video_capture.dart' as _i41; + as _i36; +import 'package:camera_android_camerax/src/quality_selector.dart' as _i38; +import 'package:camera_android_camerax/src/recorder.dart' as _i15; +import 'package:camera_android_camerax/src/recording.dart' as _i11; +import 'package:camera_android_camerax/src/resolution_filter.dart' as _i39; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i40; +import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i41; +import 'package:camera_android_camerax/src/use_case.dart' as _i37; +import 'package:camera_android_camerax/src/video_capture.dart' as _i43; import 'package:camera_android_camerax/src/zoom_state.dart' as _i21; import 'package:camera_platform_interface/camera_platform_interface.dart' - as _i6; -import 'package:flutter/foundation.dart' as _i15; -import 'package:flutter/services.dart' as _i14; -import 'package:flutter/widgets.dart' as _i13; + as _i9; +import 'package:flutter/foundation.dart' as _i4; +import 'package:flutter/services.dart' as _i3; +import 'package:flutter/widgets.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i30; +import 'package:mockito/src/dummies.dart' as _i27; import 'test_camerax_library.g.dart' as _i42; @@ -64,39 +65,55 @@ import 'test_camerax_library.g.dart' as _i42; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo { - _FakeCameraInfo_0( +class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { + _FakeWidget_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); } -class _FakeCameraControl_1 extends _i1.SmartFake implements _i3.CameraControl { - _FakeCameraControl_1( +class _FakeInheritedWidget_1 extends _i1.SmartFake + implements _i2.InheritedWidget { + _FakeInheritedWidget_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); } -class _FakeLiveData_2 extends _i1.SmartFake - implements _i4.LiveData { - _FakeLiveData_2( +class _FakeDiagnosticsNode_2 extends _i1.SmartFake + implements _i4.DiagnosticsNode { + _FakeDiagnosticsNode_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); + + @override + String toString({ + _i4.TextTreeConfiguration? parentConfiguration, + _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, + }) => + super.toString(); } -class _FakeExposureState_3 extends _i1.SmartFake implements _i5.ExposureState { - _FakeExposureState_3( +class _FakeCameraInfo_3 extends _i1.SmartFake implements _i5.CameraInfo { + _FakeCameraInfo_3( Object parent, Invocation parentInvocation, ) : super( @@ -105,9 +122,8 @@ class _FakeExposureState_3 extends _i1.SmartFake implements _i5.ExposureState { ); } -class _FakeCameraImageFormat_4 extends _i1.SmartFake - implements _i6.CameraImageFormat { - _FakeCameraImageFormat_4( +class _FakeCameraControl_4 extends _i1.SmartFake implements _i6.CameraControl { + _FakeCameraControl_4( Object parent, Invocation parentInvocation, ) : super( @@ -116,9 +132,9 @@ class _FakeCameraImageFormat_4 extends _i1.SmartFake ); } -class _FakeExposureCompensationRange_5 extends _i1.SmartFake - implements _i7.ExposureCompensationRange { - _FakeExposureCompensationRange_5( +class _FakeLiveData_5 extends _i1.SmartFake + implements _i7.LiveData { + _FakeLiveData_5( Object parent, Invocation parentInvocation, ) : super( @@ -127,8 +143,8 @@ class _FakeExposureCompensationRange_5 extends _i1.SmartFake ); } -class _FakeRecording_6 extends _i1.SmartFake implements _i8.Recording { - _FakeRecording_6( +class _FakeExposureState_6 extends _i1.SmartFake implements _i8.ExposureState { + _FakeExposureState_6( Object parent, Invocation parentInvocation, ) : super( @@ -137,9 +153,9 @@ class _FakeRecording_6 extends _i1.SmartFake implements _i8.Recording { ); } -class _FakeResolutionInfo_7 extends _i1.SmartFake - implements _i7.ResolutionInfo { - _FakeResolutionInfo_7( +class _FakeCameraImageFormat_7 extends _i1.SmartFake + implements _i9.CameraImageFormat { + _FakeCameraImageFormat_7( Object parent, Invocation parentInvocation, ) : super( @@ -148,8 +164,9 @@ class _FakeResolutionInfo_7 extends _i1.SmartFake ); } -class _FakeCamera_8 extends _i1.SmartFake implements _i9.Camera { - _FakeCamera_8( +class _FakeExposureCompensationRange_8 extends _i1.SmartFake + implements _i10.ExposureCompensationRange { + _FakeExposureCompensationRange_8( Object parent, Invocation parentInvocation, ) : super( @@ -158,9 +175,8 @@ class _FakeCamera_8 extends _i1.SmartFake implements _i9.Camera { ); } -class _FakePendingRecording_9 extends _i1.SmartFake - implements _i10.PendingRecording { - _FakePendingRecording_9( +class _FakeRecording_9 extends _i1.SmartFake implements _i11.Recording { + _FakeRecording_9( Object parent, Invocation parentInvocation, ) : super( @@ -169,8 +185,9 @@ class _FakePendingRecording_9 extends _i1.SmartFake ); } -class _FakeSize_10 extends _i1.SmartFake implements _i11.Size { - _FakeSize_10( +class _FakeResolutionInfo_10 extends _i1.SmartFake + implements _i10.ResolutionInfo { + _FakeResolutionInfo_10( Object parent, Invocation parentInvocation, ) : super( @@ -179,8 +196,8 @@ class _FakeSize_10 extends _i1.SmartFake implements _i11.Size { ); } -class _FakeRecorder_11 extends _i1.SmartFake implements _i12.Recorder { - _FakeRecorder_11( +class _FakeCamera_11 extends _i1.SmartFake implements _i12.Camera { + _FakeCamera_11( Object parent, Invocation parentInvocation, ) : super( @@ -189,53 +206,35 @@ class _FakeRecorder_11 extends _i1.SmartFake implements _i12.Recorder { ); } -class _FakeWidget_12 extends _i1.SmartFake implements _i13.Widget { - _FakeWidget_12( +class _FakePendingRecording_12 extends _i1.SmartFake + implements _i13.PendingRecording { + _FakePendingRecording_12( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); - - @override - String toString( - {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => - super.toString(); } -class _FakeInheritedWidget_13 extends _i1.SmartFake - implements _i13.InheritedWidget { - _FakeInheritedWidget_13( +class _FakeSize_13 extends _i1.SmartFake implements _i14.Size { + _FakeSize_13( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); - - @override - String toString( - {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => - super.toString(); } -class _FakeDiagnosticsNode_14 extends _i1.SmartFake - implements _i15.DiagnosticsNode { - _FakeDiagnosticsNode_14( +class _FakeRecorder_14 extends _i1.SmartFake implements _i15.Recorder { + _FakeRecorder_14( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); - - @override - String toString({ - _i15.TextTreeConfiguration? parentConfiguration, - _i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info, - }) => - super.toString(); } /// A class which mocks [Analyzer]. @@ -274,18 +273,202 @@ class MockAspectRatioStrategy extends _i1.Mock ) as int); } +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i2.BuildContext { + @override + _i2.Widget get widget => (super.noSuchMethod( + Invocation.getter(#widget), + returnValue: _FakeWidget_0( + this, + Invocation.getter(#widget), + ), + returnValueForMissingStub: _FakeWidget_0( + this, + Invocation.getter(#widget), + ), + ) as _i2.Widget); + + @override + bool get mounted => (super.noSuchMethod( + Invocation.getter(#mounted), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + bool get debugDoingBuild => (super.noSuchMethod( + Invocation.getter(#debugDoingBuild), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i2.InheritedWidget dependOnInheritedElement( + _i2.InheritedElement? ancestor, { + Object? aspect, + }) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + returnValue: _FakeInheritedWidget_1( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + returnValueForMissingStub: _FakeInheritedWidget_1( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + ) as _i2.InheritedWidget); + + @override + void visitAncestorElements(_i2.ConditionalElementVisitor? visitor) => + super.noSuchMethod( + Invocation.method( + #visitAncestorElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + + @override + void visitChildElements(_i2.ElementVisitor? visitor) => super.noSuchMethod( + Invocation.method( + #visitChildElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + + @override + void dispatchNotification(_i2.Notification? notification) => + super.noSuchMethod( + Invocation.method( + #dispatchNotification, + [notification], + ), + returnValueForMissingStub: null, + ); + + @override + _i4.DiagnosticsNode describeElement( + String? name, { + _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + returnValueForMissingStub: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + ) as _i4.DiagnosticsNode); + + @override + _i4.DiagnosticsNode describeWidget( + String? name, { + _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + returnValueForMissingStub: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + ) as _i4.DiagnosticsNode); + + @override + List<_i4.DiagnosticsNode> describeMissingAncestor( + {required Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method( + #describeMissingAncestor, + [], + {#expectedAncestorType: expectedAncestorType}, + ), + returnValue: <_i4.DiagnosticsNode>[], + returnValueForMissingStub: <_i4.DiagnosticsNode>[], + ) as List<_i4.DiagnosticsNode>); + + @override + _i4.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod( + Invocation.method( + #describeOwnershipChain, + [name], + ), + returnValue: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), + ), + returnValueForMissingStub: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), + ), + ) as _i4.DiagnosticsNode); +} + /// A class which mocks [Camera]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCamera extends _i1.Mock implements _i9.Camera { +class MockCamera extends _i1.Mock implements _i12.Camera { @override - _i17.Future<_i2.CameraInfo> getCameraInfo() => (super.noSuchMethod( + _i17.Future<_i5.CameraInfo> getCameraInfo() => (super.noSuchMethod( Invocation.method( #getCameraInfo, [], ), - returnValue: _i17.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + returnValue: _i17.Future<_i5.CameraInfo>.value(_FakeCameraInfo_3( this, Invocation.method( #getCameraInfo, @@ -293,22 +476,22 @@ class MockCamera extends _i1.Mock implements _i9.Camera { ), )), returnValueForMissingStub: - _i17.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + _i17.Future<_i5.CameraInfo>.value(_FakeCameraInfo_3( this, Invocation.method( #getCameraInfo, [], ), )), - ) as _i17.Future<_i2.CameraInfo>); + ) as _i17.Future<_i5.CameraInfo>); @override - _i17.Future<_i3.CameraControl> getCameraControl() => (super.noSuchMethod( + _i17.Future<_i6.CameraControl> getCameraControl() => (super.noSuchMethod( Invocation.method( #getCameraControl, [], ), - returnValue: _i17.Future<_i3.CameraControl>.value(_FakeCameraControl_1( + returnValue: _i17.Future<_i6.CameraControl>.value(_FakeCameraControl_4( this, Invocation.method( #getCameraControl, @@ -316,21 +499,21 @@ class MockCamera extends _i1.Mock implements _i9.Camera { ), )), returnValueForMissingStub: - _i17.Future<_i3.CameraControl>.value(_FakeCameraControl_1( + _i17.Future<_i6.CameraControl>.value(_FakeCameraControl_4( this, Invocation.method( #getCameraControl, [], ), )), - ) as _i17.Future<_i3.CameraControl>); + ) as _i17.Future<_i6.CameraControl>); } /// A class which mocks [CameraInfo]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { +class MockCameraInfo extends _i1.Mock implements _i5.CameraInfo { @override _i17.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( @@ -342,14 +525,14 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ) as _i17.Future); @override - _i17.Future<_i4.LiveData<_i20.CameraState>> getCameraState() => + _i17.Future<_i7.LiveData<_i20.CameraState>> getCameraState() => (super.noSuchMethod( Invocation.method( #getCameraState, [], ), - returnValue: _i17.Future<_i4.LiveData<_i20.CameraState>>.value( - _FakeLiveData_2<_i20.CameraState>( + returnValue: _i17.Future<_i7.LiveData<_i20.CameraState>>.value( + _FakeLiveData_5<_i20.CameraState>( this, Invocation.method( #getCameraState, @@ -357,23 +540,23 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i17.Future<_i4.LiveData<_i20.CameraState>>.value( - _FakeLiveData_2<_i20.CameraState>( + _i17.Future<_i7.LiveData<_i20.CameraState>>.value( + _FakeLiveData_5<_i20.CameraState>( this, Invocation.method( #getCameraState, [], ), )), - ) as _i17.Future<_i4.LiveData<_i20.CameraState>>); + ) as _i17.Future<_i7.LiveData<_i20.CameraState>>); @override - _i17.Future<_i5.ExposureState> getExposureState() => (super.noSuchMethod( + _i17.Future<_i8.ExposureState> getExposureState() => (super.noSuchMethod( Invocation.method( #getExposureState, [], ), - returnValue: _i17.Future<_i5.ExposureState>.value(_FakeExposureState_3( + returnValue: _i17.Future<_i8.ExposureState>.value(_FakeExposureState_6( this, Invocation.method( #getExposureState, @@ -381,24 +564,24 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i17.Future<_i5.ExposureState>.value(_FakeExposureState_3( + _i17.Future<_i8.ExposureState>.value(_FakeExposureState_6( this, Invocation.method( #getExposureState, [], ), )), - ) as _i17.Future<_i5.ExposureState>); + ) as _i17.Future<_i8.ExposureState>); @override - _i17.Future<_i4.LiveData<_i21.ZoomState>> getZoomState() => + _i17.Future<_i7.LiveData<_i21.ZoomState>> getZoomState() => (super.noSuchMethod( Invocation.method( #getZoomState, [], ), - returnValue: _i17.Future<_i4.LiveData<_i21.ZoomState>>.value( - _FakeLiveData_2<_i21.ZoomState>( + returnValue: _i17.Future<_i7.LiveData<_i21.ZoomState>>.value( + _FakeLiveData_5<_i21.ZoomState>( this, Invocation.method( #getZoomState, @@ -406,22 +589,22 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i17.Future<_i4.LiveData<_i21.ZoomState>>.value( - _FakeLiveData_2<_i21.ZoomState>( + _i17.Future<_i7.LiveData<_i21.ZoomState>>.value( + _FakeLiveData_5<_i21.ZoomState>( this, Invocation.method( #getZoomState, [], ), )), - ) as _i17.Future<_i4.LiveData<_i21.ZoomState>>); + ) as _i17.Future<_i7.LiveData<_i21.ZoomState>>); } /// A class which mocks [CameraControl]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraControl extends _i1.Mock implements _i3.CameraControl { +class MockCameraControl extends _i1.Mock implements _i6.CameraControl { @override _i17.Future enableTorch(bool? torch) => (super.noSuchMethod( Invocation.method( @@ -484,17 +667,17 @@ class MockCameraControl extends _i1.Mock implements _i3.CameraControl { class MockCamera2CameraControl extends _i1.Mock implements _i24.Camera2CameraControl { @override - _i3.CameraControl get cameraControl => (super.noSuchMethod( + _i6.CameraControl get cameraControl => (super.noSuchMethod( Invocation.getter(#cameraControl), - returnValue: _FakeCameraControl_1( + returnValue: _FakeCameraControl_4( this, Invocation.getter(#cameraControl), ), - returnValueForMissingStub: _FakeCameraControl_1( + returnValueForMissingStub: _FakeCameraControl_4( this, Invocation.getter(#cameraControl), ), - ) as _i3.CameraControl); + ) as _i6.CameraControl); @override _i17.Future addCaptureRequestOptions( @@ -509,23 +692,62 @@ class MockCamera2CameraControl extends _i1.Mock ) as _i17.Future); } +/// A class which mocks [Camera2CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCamera2CameraInfo extends _i1.Mock implements _i26.Camera2CameraInfo { + @override + _i17.Future getSupportedHardwareLevel() => (super.noSuchMethod( + Invocation.method( + #getSupportedHardwareLevel, + [], + ), + returnValue: _i17.Future.value(0), + returnValueForMissingStub: _i17.Future.value(0), + ) as _i17.Future); + + @override + _i17.Future getCameraId() => (super.noSuchMethod( + Invocation.method( + #getCameraId, + [], + ), + returnValue: _i17.Future.value(_i27.dummyValue( + this, + Invocation.method( + #getCameraId, + [], + ), + )), + returnValueForMissingStub: + _i17.Future.value(_i27.dummyValue( + this, + Invocation.method( + #getCameraId, + [], + ), + )), + ) as _i17.Future); +} + /// A class which mocks [CameraImageData]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData { +class MockCameraImageData extends _i1.Mock implements _i9.CameraImageData { @override - _i6.CameraImageFormat get format => (super.noSuchMethod( + _i9.CameraImageFormat get format => (super.noSuchMethod( Invocation.getter(#format), - returnValue: _FakeCameraImageFormat_4( + returnValue: _FakeCameraImageFormat_7( this, Invocation.getter(#format), ), - returnValueForMissingStub: _FakeCameraImageFormat_4( + returnValueForMissingStub: _FakeCameraImageFormat_7( this, Invocation.getter(#format), ), - ) as _i6.CameraImageFormat); + ) as _i9.CameraImageFormat); @override int get height => (super.noSuchMethod( @@ -542,50 +764,50 @@ class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData { ) as int); @override - List<_i6.CameraImagePlane> get planes => (super.noSuchMethod( + List<_i9.CameraImagePlane> get planes => (super.noSuchMethod( Invocation.getter(#planes), - returnValue: <_i6.CameraImagePlane>[], - returnValueForMissingStub: <_i6.CameraImagePlane>[], - ) as List<_i6.CameraImagePlane>); + returnValue: <_i9.CameraImagePlane>[], + returnValueForMissingStub: <_i9.CameraImagePlane>[], + ) as List<_i9.CameraImagePlane>); } /// A class which mocks [CameraSelector]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraSelector extends _i1.Mock implements _i26.CameraSelector { +class MockCameraSelector extends _i1.Mock implements _i28.CameraSelector { @override - _i17.Future> filter(List<_i2.CameraInfo>? cameraInfos) => + _i17.Future> filter(List<_i5.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), returnValue: - _i17.Future>.value(<_i2.CameraInfo>[]), + _i17.Future>.value(<_i5.CameraInfo>[]), returnValueForMissingStub: - _i17.Future>.value(<_i2.CameraInfo>[]), - ) as _i17.Future>); + _i17.Future>.value(<_i5.CameraInfo>[]), + ) as _i17.Future>); } /// A class which mocks [ExposureState]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockExposureState extends _i1.Mock implements _i5.ExposureState { +class MockExposureState extends _i1.Mock implements _i8.ExposureState { @override - _i7.ExposureCompensationRange get exposureCompensationRange => + _i10.ExposureCompensationRange get exposureCompensationRange => (super.noSuchMethod( Invocation.getter(#exposureCompensationRange), - returnValue: _FakeExposureCompensationRange_5( + returnValue: _FakeExposureCompensationRange_8( this, Invocation.getter(#exposureCompensationRange), ), - returnValueForMissingStub: _FakeExposureCompensationRange_5( + returnValueForMissingStub: _FakeExposureCompensationRange_8( this, Invocation.getter(#exposureCompensationRange), ), - ) as _i7.ExposureCompensationRange); + ) as _i10.ExposureCompensationRange); @override double get exposureCompensationStep => (super.noSuchMethod( @@ -599,21 +821,21 @@ class MockExposureState extends _i1.Mock implements _i5.ExposureState { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockFallbackStrategy extends _i1.Mock implements _i27.FallbackStrategy { +class MockFallbackStrategy extends _i1.Mock implements _i29.FallbackStrategy { @override - _i7.VideoQuality get quality => (super.noSuchMethod( + _i10.VideoQuality get quality => (super.noSuchMethod( Invocation.getter(#quality), - returnValue: _i7.VideoQuality.SD, - returnValueForMissingStub: _i7.VideoQuality.SD, - ) as _i7.VideoQuality); + returnValue: _i10.VideoQuality.SD, + returnValueForMissingStub: _i10.VideoQuality.SD, + ) as _i10.VideoQuality); @override - _i7.VideoResolutionFallbackRule get fallbackRule => (super.noSuchMethod( + _i10.VideoResolutionFallbackRule get fallbackRule => (super.noSuchMethod( Invocation.getter(#fallbackRule), - returnValue: _i7.VideoResolutionFallbackRule.higherQualityOrLowerThan, + returnValue: _i10.VideoResolutionFallbackRule.higherQualityOrLowerThan, returnValueForMissingStub: - _i7.VideoResolutionFallbackRule.higherQualityOrLowerThan, - ) as _i7.VideoResolutionFallbackRule); + _i10.VideoResolutionFallbackRule.higherQualityOrLowerThan, + ) as _i10.VideoResolutionFallbackRule); } /// A class which mocks [FocusMeteringResult]. @@ -637,7 +859,7 @@ class MockFocusMeteringResult extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageAnalysis extends _i1.Mock implements _i28.ImageAnalysis { +class MockImageAnalysis extends _i1.Mock implements _i30.ImageAnalysis { @override _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -673,7 +895,7 @@ class MockImageAnalysis extends _i1.Mock implements _i28.ImageAnalysis { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { +class MockImageCapture extends _i1.Mock implements _i31.ImageCapture { @override _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -700,7 +922,7 @@ class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { #takePicture, [], ), - returnValue: _i17.Future.value(_i30.dummyValue( + returnValue: _i17.Future.value(_i27.dummyValue( this, Invocation.method( #takePicture, @@ -708,7 +930,7 @@ class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { ), )), returnValueForMissingStub: - _i17.Future.value(_i30.dummyValue( + _i17.Future.value(_i27.dummyValue( this, Invocation.method( #takePicture, @@ -745,16 +967,16 @@ class MockImageProxy extends _i1.Mock implements _i18.ImageProxy { ) as int); @override - _i17.Future> getPlanes() => (super.noSuchMethod( + _i17.Future> getPlanes() => (super.noSuchMethod( Invocation.method( #getPlanes, [], ), returnValue: - _i17.Future>.value(<_i31.PlaneProxy>[]), + _i17.Future>.value(<_i32.PlaneProxy>[]), returnValueForMissingStub: - _i17.Future>.value(<_i31.PlaneProxy>[]), - ) as _i17.Future>); + _i17.Future>.value(<_i32.PlaneProxy>[]), + ) as _i17.Future>); @override _i17.Future close() => (super.noSuchMethod( @@ -771,7 +993,7 @@ class MockImageProxy extends _i1.Mock implements _i18.ImageProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockObserver extends _i1.Mock implements _i32.Observer<_i20.CameraState> { +class MockObserver extends _i1.Mock implements _i33.Observer<_i20.CameraState> { @override void Function(Object) get onChanged => (super.noSuchMethod( Invocation.getter(#onChanged), @@ -793,14 +1015,14 @@ class MockObserver extends _i1.Mock implements _i32.Observer<_i20.CameraState> { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { +class MockPendingRecording extends _i1.Mock implements _i13.PendingRecording { @override - _i17.Future<_i8.Recording> start() => (super.noSuchMethod( + _i17.Future<_i11.Recording> start() => (super.noSuchMethod( Invocation.method( #start, [], ), - returnValue: _i17.Future<_i8.Recording>.value(_FakeRecording_6( + returnValue: _i17.Future<_i11.Recording>.value(_FakeRecording_9( this, Invocation.method( #start, @@ -808,27 +1030,27 @@ class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { ), )), returnValueForMissingStub: - _i17.Future<_i8.Recording>.value(_FakeRecording_6( + _i17.Future<_i11.Recording>.value(_FakeRecording_9( this, Invocation.method( #start, [], ), )), - ) as _i17.Future<_i8.Recording>); + ) as _i17.Future<_i11.Recording>); } /// A class which mocks [PlaneProxy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPlaneProxy extends _i1.Mock implements _i31.PlaneProxy { +class MockPlaneProxy extends _i1.Mock implements _i32.PlaneProxy { @override - _i33.Uint8List get buffer => (super.noSuchMethod( + _i34.Uint8List get buffer => (super.noSuchMethod( Invocation.getter(#buffer), - returnValue: _i33.Uint8List(0), - returnValueForMissingStub: _i33.Uint8List(0), - ) as _i33.Uint8List); + returnValue: _i34.Uint8List(0), + returnValueForMissingStub: _i34.Uint8List(0), + ) as _i34.Uint8List); @override int get pixelStride => (super.noSuchMethod( @@ -849,7 +1071,7 @@ class MockPlaneProxy extends _i1.Mock implements _i31.PlaneProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPreview extends _i1.Mock implements _i34.Preview { +class MockPreview extends _i1.Mock implements _i35.Preview { @override _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -880,13 +1102,13 @@ class MockPreview extends _i1.Mock implements _i34.Preview { ); @override - _i17.Future<_i7.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( + _i17.Future<_i10.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [], ), returnValue: - _i17.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( + _i17.Future<_i10.ResolutionInfo>.value(_FakeResolutionInfo_10( this, Invocation.method( #getResolutionInfo, @@ -894,14 +1116,14 @@ class MockPreview extends _i1.Mock implements _i34.Preview { ), )), returnValueForMissingStub: - _i17.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( + _i17.Future<_i10.ResolutionInfo>.value(_FakeResolutionInfo_10( this, Invocation.method( #getResolutionInfo, [], ), )), - ) as _i17.Future<_i7.ResolutionInfo>); + ) as _i17.Future<_i10.ResolutionInfo>); } /// A class which mocks [ProcessCameraProvider]. @@ -909,24 +1131,24 @@ class MockPreview extends _i1.Mock implements _i34.Preview { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockProcessCameraProvider extends _i1.Mock - implements _i35.ProcessCameraProvider { + implements _i36.ProcessCameraProvider { @override - _i17.Future> getAvailableCameraInfos() => + _i17.Future> getAvailableCameraInfos() => (super.noSuchMethod( Invocation.method( #getAvailableCameraInfos, [], ), returnValue: - _i17.Future>.value(<_i2.CameraInfo>[]), + _i17.Future>.value(<_i5.CameraInfo>[]), returnValueForMissingStub: - _i17.Future>.value(<_i2.CameraInfo>[]), - ) as _i17.Future>); + _i17.Future>.value(<_i5.CameraInfo>[]), + ) as _i17.Future>); @override - _i17.Future<_i9.Camera> bindToLifecycle( - _i26.CameraSelector? cameraSelector, - List<_i36.UseCase>? useCases, + _i17.Future<_i12.Camera> bindToLifecycle( + _i28.CameraSelector? cameraSelector, + List<_i37.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( @@ -936,7 +1158,7 @@ class MockProcessCameraProvider extends _i1.Mock useCases, ], ), - returnValue: _i17.Future<_i9.Camera>.value(_FakeCamera_8( + returnValue: _i17.Future<_i12.Camera>.value(_FakeCamera_11( this, Invocation.method( #bindToLifecycle, @@ -946,7 +1168,8 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - returnValueForMissingStub: _i17.Future<_i9.Camera>.value(_FakeCamera_8( + returnValueForMissingStub: + _i17.Future<_i12.Camera>.value(_FakeCamera_11( this, Invocation.method( #bindToLifecycle, @@ -956,10 +1179,10 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - ) as _i17.Future<_i9.Camera>); + ) as _i17.Future<_i12.Camera>); @override - _i17.Future isBound(_i36.UseCase? useCase) => (super.noSuchMethod( + _i17.Future isBound(_i37.UseCase? useCase) => (super.noSuchMethod( Invocation.method( #isBound, [useCase], @@ -969,7 +1192,7 @@ class MockProcessCameraProvider extends _i1.Mock ) as _i17.Future); @override - void unbind(List<_i36.UseCase>? useCases) => super.noSuchMethod( + void unbind(List<_i37.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], @@ -991,29 +1214,29 @@ class MockProcessCameraProvider extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockQualitySelector extends _i1.Mock implements _i37.QualitySelector { +class MockQualitySelector extends _i1.Mock implements _i38.QualitySelector { @override - List<_i7.VideoQualityData> get qualityList => (super.noSuchMethod( + List<_i10.VideoQualityData> get qualityList => (super.noSuchMethod( Invocation.getter(#qualityList), - returnValue: <_i7.VideoQualityData>[], - returnValueForMissingStub: <_i7.VideoQualityData>[], - ) as List<_i7.VideoQualityData>); + returnValue: <_i10.VideoQualityData>[], + returnValueForMissingStub: <_i10.VideoQualityData>[], + ) as List<_i10.VideoQualityData>); } /// A class which mocks [Recorder]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockRecorder extends _i1.Mock implements _i12.Recorder { +class MockRecorder extends _i1.Mock implements _i15.Recorder { @override - _i17.Future<_i10.PendingRecording> prepareRecording(String? path) => + _i17.Future<_i13.PendingRecording> prepareRecording(String? path) => (super.noSuchMethod( Invocation.method( #prepareRecording, [path], ), returnValue: - _i17.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( + _i17.Future<_i13.PendingRecording>.value(_FakePendingRecording_12( this, Invocation.method( #prepareRecording, @@ -1021,33 +1244,33 @@ class MockRecorder extends _i1.Mock implements _i12.Recorder { ), )), returnValueForMissingStub: - _i17.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( + _i17.Future<_i13.PendingRecording>.value(_FakePendingRecording_12( this, Invocation.method( #prepareRecording, [path], ), )), - ) as _i17.Future<_i10.PendingRecording>); + ) as _i17.Future<_i13.PendingRecording>); } /// A class which mocks [ResolutionFilter]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockResolutionFilter extends _i1.Mock implements _i38.ResolutionFilter { +class MockResolutionFilter extends _i1.Mock implements _i39.ResolutionFilter { @override - _i11.Size get preferredResolution => (super.noSuchMethod( + _i14.Size get preferredResolution => (super.noSuchMethod( Invocation.getter(#preferredResolution), - returnValue: _FakeSize_10( + returnValue: _FakeSize_13( this, Invocation.getter(#preferredResolution), ), - returnValueForMissingStub: _FakeSize_10( + returnValueForMissingStub: _FakeSize_13( this, Invocation.getter(#preferredResolution), ), - ) as _i11.Size); + ) as _i14.Size); } /// A class which mocks [ResolutionSelector]. @@ -1055,20 +1278,20 @@ class MockResolutionFilter extends _i1.Mock implements _i38.ResolutionFilter { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionSelector extends _i1.Mock - implements _i39.ResolutionSelector {} + implements _i40.ResolutionSelector {} /// A class which mocks [ResolutionStrategy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionStrategy extends _i1.Mock - implements _i40.ResolutionStrategy {} + implements _i41.ResolutionStrategy {} /// A class which mocks [Recording]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockRecording extends _i1.Mock implements _i8.Recording { +class MockRecording extends _i1.Mock implements _i11.Recording { @override _i17.Future close() => (super.noSuchMethod( Invocation.method( @@ -1110,229 +1333,6 @@ class MockRecording extends _i1.Mock implements _i8.Recording { ) as _i17.Future); } -/// A class which mocks [VideoCapture]. -/// -/// See the documentation for Mockito's code generation for more information. -// ignore: must_be_immutable -class MockVideoCapture extends _i1.Mock implements _i41.VideoCapture { - @override - _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( - Invocation.method( - #setTargetRotation, - [rotation], - ), - returnValue: _i17.Future.value(), - returnValueForMissingStub: _i17.Future.value(), - ) as _i17.Future); - - @override - _i17.Future<_i12.Recorder> getOutput() => (super.noSuchMethod( - Invocation.method( - #getOutput, - [], - ), - returnValue: _i17.Future<_i12.Recorder>.value(_FakeRecorder_11( - this, - Invocation.method( - #getOutput, - [], - ), - )), - returnValueForMissingStub: - _i17.Future<_i12.Recorder>.value(_FakeRecorder_11( - this, - Invocation.method( - #getOutput, - [], - ), - )), - ) as _i17.Future<_i12.Recorder>); -} - -/// A class which mocks [BuildContext]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockBuildContext extends _i1.Mock implements _i13.BuildContext { - @override - _i13.Widget get widget => (super.noSuchMethod( - Invocation.getter(#widget), - returnValue: _FakeWidget_12( - this, - Invocation.getter(#widget), - ), - returnValueForMissingStub: _FakeWidget_12( - this, - Invocation.getter(#widget), - ), - ) as _i13.Widget); - - @override - bool get mounted => (super.noSuchMethod( - Invocation.getter(#mounted), - returnValue: false, - returnValueForMissingStub: false, - ) as bool); - - @override - bool get debugDoingBuild => (super.noSuchMethod( - Invocation.getter(#debugDoingBuild), - returnValue: false, - returnValueForMissingStub: false, - ) as bool); - - @override - _i13.InheritedWidget dependOnInheritedElement( - _i13.InheritedElement? ancestor, { - Object? aspect, - }) => - (super.noSuchMethod( - Invocation.method( - #dependOnInheritedElement, - [ancestor], - {#aspect: aspect}, - ), - returnValue: _FakeInheritedWidget_13( - this, - Invocation.method( - #dependOnInheritedElement, - [ancestor], - {#aspect: aspect}, - ), - ), - returnValueForMissingStub: _FakeInheritedWidget_13( - this, - Invocation.method( - #dependOnInheritedElement, - [ancestor], - {#aspect: aspect}, - ), - ), - ) as _i13.InheritedWidget); - - @override - void visitAncestorElements(_i13.ConditionalElementVisitor? visitor) => - super.noSuchMethod( - Invocation.method( - #visitAncestorElements, - [visitor], - ), - returnValueForMissingStub: null, - ); - - @override - void visitChildElements(_i13.ElementVisitor? visitor) => super.noSuchMethod( - Invocation.method( - #visitChildElements, - [visitor], - ), - returnValueForMissingStub: null, - ); - - @override - void dispatchNotification(_i13.Notification? notification) => - super.noSuchMethod( - Invocation.method( - #dispatchNotification, - [notification], - ), - returnValueForMissingStub: null, - ); - - @override - _i15.DiagnosticsNode describeElement( - String? name, { - _i15.DiagnosticsTreeStyle? style = _i15.DiagnosticsTreeStyle.errorProperty, - }) => - (super.noSuchMethod( - Invocation.method( - #describeElement, - [name], - {#style: style}, - ), - returnValue: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeElement, - [name], - {#style: style}, - ), - ), - returnValueForMissingStub: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeElement, - [name], - {#style: style}, - ), - ), - ) as _i15.DiagnosticsNode); - - @override - _i15.DiagnosticsNode describeWidget( - String? name, { - _i15.DiagnosticsTreeStyle? style = _i15.DiagnosticsTreeStyle.errorProperty, - }) => - (super.noSuchMethod( - Invocation.method( - #describeWidget, - [name], - {#style: style}, - ), - returnValue: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeWidget, - [name], - {#style: style}, - ), - ), - returnValueForMissingStub: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeWidget, - [name], - {#style: style}, - ), - ), - ) as _i15.DiagnosticsNode); - - @override - List<_i15.DiagnosticsNode> describeMissingAncestor( - {required Type? expectedAncestorType}) => - (super.noSuchMethod( - Invocation.method( - #describeMissingAncestor, - [], - {#expectedAncestorType: expectedAncestorType}, - ), - returnValue: <_i15.DiagnosticsNode>[], - returnValueForMissingStub: <_i15.DiagnosticsNode>[], - ) as List<_i15.DiagnosticsNode>); - - @override - _i15.DiagnosticsNode describeOwnershipChain(String? name) => - (super.noSuchMethod( - Invocation.method( - #describeOwnershipChain, - [name], - ), - returnValue: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeOwnershipChain, - [name], - ), - ), - returnValueForMissingStub: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeOwnershipChain, - [name], - ), - ), - ) as _i15.DiagnosticsNode); -} - /// A class which mocks [TestInstanceManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. @@ -1354,17 +1354,17 @@ class MockTestInstanceManagerHostApi extends _i1.Mock class MockTestSystemServicesHostApi extends _i1.Mock implements _i42.TestSystemServicesHostApi { @override - _i17.Future<_i7.CameraPermissionsErrorData?> requestCameraPermissions( + _i17.Future<_i10.CameraPermissionsErrorData?> requestCameraPermissions( bool? enableAudio) => (super.noSuchMethod( Invocation.method( #requestCameraPermissions, [enableAudio], ), - returnValue: _i17.Future<_i7.CameraPermissionsErrorData?>.value(), + returnValue: _i17.Future<_i10.CameraPermissionsErrorData?>.value(), returnValueForMissingStub: - _i17.Future<_i7.CameraPermissionsErrorData?>.value(), - ) as _i17.Future<_i7.CameraPermissionsErrorData?>); + _i17.Future<_i10.CameraPermissionsErrorData?>.value(), + ) as _i17.Future<_i10.CameraPermissionsErrorData?>); @override String getTempFilePath( @@ -1379,7 +1379,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock suffix, ], ), - returnValue: _i30.dummyValue( + returnValue: _i27.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1389,7 +1389,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock ], ), ), - returnValueForMissingStub: _i30.dummyValue( + returnValueForMissingStub: _i27.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1402,6 +1402,45 @@ class MockTestSystemServicesHostApi extends _i1.Mock ) as String); } +/// A class which mocks [VideoCapture]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockVideoCapture extends _i1.Mock implements _i43.VideoCapture { + @override + _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( + Invocation.method( + #setTargetRotation, + [rotation], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + + @override + _i17.Future<_i15.Recorder> getOutput() => (super.noSuchMethod( + Invocation.method( + #getOutput, + [], + ), + returnValue: _i17.Future<_i15.Recorder>.value(_FakeRecorder_14( + this, + Invocation.method( + #getOutput, + [], + ), + )), + returnValueForMissingStub: + _i17.Future<_i15.Recorder>.value(_FakeRecorder_14( + this, + Invocation.method( + #getOutput, + [], + ), + )), + ) as _i17.Future<_i15.Recorder>); +} + /// A class which mocks [ZoomState]. /// /// See the documentation for Mockito's code generation for more information. @@ -1427,13 +1466,13 @@ class MockZoomState extends _i1.Mock implements _i21.ZoomState { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockLiveCameraState extends _i1.Mock - implements _i4.LiveData<_i20.CameraState> { + implements _i7.LiveData<_i20.CameraState> { MockLiveCameraState() { _i1.throwOnMissingStub(this); } @override - _i17.Future observe(_i32.Observer<_i20.CameraState>? observer) => + _i17.Future observe(_i33.Observer<_i20.CameraState>? observer) => (super.noSuchMethod( Invocation.method( #observe, @@ -1459,13 +1498,13 @@ class MockLiveCameraState extends _i1.Mock /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockLiveZoomState extends _i1.Mock - implements _i4.LiveData<_i21.ZoomState> { + implements _i7.LiveData<_i21.ZoomState> { MockLiveZoomState() { _i1.throwOnMissingStub(this); } @override - _i17.Future observe(_i32.Observer<_i21.ZoomState>? observer) => + _i17.Future observe(_i33.Observer<_i21.ZoomState>? observer) => (super.noSuchMethod( Invocation.method( #observe, diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart new file mode 100644 index 00000000000..0766eee37e8 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart @@ -0,0 +1,155 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/camera2_camera_info.dart'; +import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_metadata.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'camera2_camera_info_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + TestCamera2CameraInfoHostApi, + TestInstanceManagerHostApi, + CameraInfo +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('Camera2CameraInfo', () { + tearDown(() => TestCamera2CameraInfoHostApi.setup(null)); + + test('from returns expected Camera2CameraInfo instance', () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + final CameraInfo mockCameraInfo = MockCameraInfo(); + const int camera2CameraInfoId = 33; + const int mockCameraInfoId = 44; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + instanceManager.addHostCreatedInstance( + mockCameraInfo, + mockCameraInfoId, + onCopy: (_) => CameraInfo.detached(), + ); + + when(mockApi.createFrom(mockCameraInfoId)) + .thenAnswer((_) => camera2CameraInfoId); + expect( + await Camera2CameraInfo.from(mockCameraInfo, + instanceManager: instanceManager), + equals(camera2CameraInfo)); + verify(mockApi.createFrom(mockCameraInfoId)); + }); + + test('detached constructor does not create Camera2CameraInfo on Java side', + () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + + verifyNever(mockApi.createFrom(argThat(isA()))); + }); + + test( + 'getSupportedHardwareLevel makes call to retrieve supported hardware level', + () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + const int camera2CameraInfoId = 9; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + + const int expectedSupportedHardwareLevel = + CameraMetadata.infoSupportedHardwareLevelExternal; + when(mockApi.getSupportedHardwareLevel(camera2CameraInfoId)) + .thenReturn(expectedSupportedHardwareLevel); + expect(await camera2CameraInfo.getSupportedHardwareLevel(), + equals(expectedSupportedHardwareLevel)); + + verify(mockApi.getSupportedHardwareLevel(camera2CameraInfoId)); + }); + + test('getCameraId makes call to retrieve camera ID', () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + const int camera2CameraInfoId = 19; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + + const String expectedCameraId = 'testCameraId'; + when(mockApi.getCameraId(camera2CameraInfoId)) + .thenReturn(expectedCameraId); + expect(await camera2CameraInfo.getCameraId(), equals(expectedCameraId)); + + verify(mockApi.getCameraId(camera2CameraInfoId)); + }); + + test('flutterApi create makes call to create expected instance type', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfoFlutterApi flutterApi = + Camera2CameraInfoFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.create(0); + + expect(instanceManager.getInstanceWithWeakReference(0), + isA()); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart new file mode 100644 index 00000000000..9060c51874a --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart @@ -0,0 +1,179 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in camera_android_camerax/test/camera2_camera_info_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i7; + +import 'package:camera_android_camerax/src/camera_info.dart' as _i6; +import 'package:camera_android_camerax/src/camera_state.dart' as _i8; +import 'package:camera_android_camerax/src/exposure_state.dart' as _i3; +import 'package:camera_android_camerax/src/live_data.dart' as _i2; +import 'package:camera_android_camerax/src/zoom_state.dart' as _i9; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +import 'test_camerax_library.g.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeLiveData_0 extends _i1.SmartFake + implements _i2.LiveData { + _FakeLiveData_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeExposureState_1 extends _i1.SmartFake implements _i3.ExposureState { + _FakeExposureState_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [TestCamera2CameraInfoHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestCamera2CameraInfoHostApi extends _i1.Mock + implements _i4.TestCamera2CameraInfoHostApi { + MockTestCamera2CameraInfoHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + int createFrom(int? cameraInfoIdentifier) => (super.noSuchMethod( + Invocation.method( + #createFrom, + [cameraInfoIdentifier], + ), + returnValue: 0, + ) as int); + + @override + int getSupportedHardwareLevel(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getSupportedHardwareLevel, + [identifier], + ), + returnValue: 0, + ) as int); + + @override + String getCameraId(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getCameraId, + [identifier], + ), + returnValue: _i5.dummyValue( + this, + Invocation.method( + #getCameraId, + [identifier], + ), + ), + ) as String); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i4.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCameraInfo extends _i1.Mock implements _i6.CameraInfo { + MockCameraInfo() { + _i1.throwOnMissingStub(this); + } + + @override + _i7.Future getSensorRotationDegrees() => (super.noSuchMethod( + Invocation.method( + #getSensorRotationDegrees, + [], + ), + returnValue: _i7.Future.value(0), + ) as _i7.Future); + + @override + _i7.Future<_i2.LiveData<_i8.CameraState>> getCameraState() => + (super.noSuchMethod( + Invocation.method( + #getCameraState, + [], + ), + returnValue: _i7.Future<_i2.LiveData<_i8.CameraState>>.value( + _FakeLiveData_0<_i8.CameraState>( + this, + Invocation.method( + #getCameraState, + [], + ), + )), + ) as _i7.Future<_i2.LiveData<_i8.CameraState>>); + + @override + _i7.Future<_i3.ExposureState> getExposureState() => (super.noSuchMethod( + Invocation.method( + #getExposureState, + [], + ), + returnValue: _i7.Future<_i3.ExposureState>.value(_FakeExposureState_1( + this, + Invocation.method( + #getExposureState, + [], + ), + )), + ) as _i7.Future<_i3.ExposureState>); + + @override + _i7.Future<_i2.LiveData<_i9.ZoomState>> getZoomState() => (super.noSuchMethod( + Invocation.method( + #getZoomState, + [], + ), + returnValue: _i7.Future<_i2.LiveData<_i9.ZoomState>>.value( + _FakeLiveData_0<_i9.ZoomState>( + this, + Invocation.method( + #getZoomState, + [], + ), + )), + ) as _i7.Future<_i2.LiveData<_i9.ZoomState>>); +} diff --git a/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart b/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart index 145ceeb278a..de24bb0b3f4 100644 --- a/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart +++ b/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart @@ -52,7 +52,7 @@ void main() { final MockTestDeviceOrientationManagerHostApi mockApi = MockTestDeviceOrientationManagerHostApi(); TestDeviceOrientationManagerHostApi.setup(mockApi); - const int expectedRotation = Surface.ROTATION_180; + const int expectedRotation = Surface.rotation180; when(mockApi.getDefaultDisplayRotation()).thenReturn(expectedRotation); diff --git a/packages/camera/camera_android_camerax/test/image_analysis_test.dart b/packages/camera/camera_android_camerax/test/image_analysis_test.dart index 1db792cf6d3..df220357508 100644 --- a/packages/camera/camera_android_camerax/test/image_analysis_test.dart +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.dart @@ -41,7 +41,7 @@ void main() { ); ImageAnalysis.detached( - initialTargetRotation: Surface.ROTATION_270, + initialTargetRotation: Surface.rotation270, resolutionSelector: MockResolutionSelector(), instanceManager: instanceManager, ); @@ -58,7 +58,7 @@ void main() { onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_90; + const int targetRotation = Surface.rotation90; final MockResolutionSelector mockResolutionSelector = MockResolutionSelector(); const int mockResolutionSelectorId = 24; @@ -91,7 +91,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final ImageAnalysis imageAnalysis = ImageAnalysis.detached( instanceManager: instanceManager, ); diff --git a/packages/camera/camera_android_camerax/test/image_capture_test.dart b/packages/camera/camera_android_camerax/test/image_capture_test.dart index e9e9f0c1855..56cbd8fcc18 100644 --- a/packages/camera/camera_android_camerax/test/image_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/image_capture_test.dart @@ -36,7 +36,7 @@ void main() { ); ImageCapture.detached( instanceManager: instanceManager, - initialTargetRotation: Surface.ROTATION_180, + initialTargetRotation: Surface.rotation180, targetFlashMode: ImageCapture.flashModeOn, resolutionSelector: MockResolutionSelector(), ); @@ -53,7 +53,7 @@ void main() { onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_270; + const int targetRotation = Surface.rotation270; const int targetFlashMode = ImageCapture.flashModeAuto; final MockResolutionSelector mockResolutionSelector = MockResolutionSelector(); @@ -112,7 +112,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final ImageCapture imageCapture = ImageCapture.detached( instanceManager: instanceManager, ); diff --git a/packages/camera/camera_android_camerax/test/preview_test.dart b/packages/camera/camera_android_camerax/test/preview_test.dart index cda1252d538..b3cbc257b0c 100644 --- a/packages/camera/camera_android_camerax/test/preview_test.dart +++ b/packages/camera/camera_android_camerax/test/preview_test.dart @@ -34,7 +34,7 @@ void main() { ); Preview.detached( instanceManager: instanceManager, - initialTargetRotation: Surface.ROTATION_90, + initialTargetRotation: Surface.rotation90, resolutionSelector: MockResolutionSelector(), ); @@ -49,7 +49,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_90; + const int targetRotation = Surface.rotation90; final MockResolutionSelector mockResolutionSelector = MockResolutionSelector(); const int mockResolutionSelectorId = 24; @@ -81,7 +81,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final Preview preview = Preview.detached( instanceManager: instanceManager, ); diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 8080e4276fc..8a659e460cd 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -2428,3 +2428,86 @@ abstract class TestResolutionFilterHostApi { } } } + +abstract class TestCamera2CameraInfoHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + int createFrom(int cameraInfoIdentifier); + + int getSupportedHardwareLevel(int identifier); + + String getCameraId(int identifier); + + static void setup(TestCamera2CameraInfoHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom was null.'); + final List args = (message as List?)!; + final int? arg_cameraInfoIdentifier = (args[0] as int?); + assert(arg_cameraInfoIdentifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom was null, expected non-null int.'); + final int output = api.createFrom(arg_cameraInfoIdentifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel was null, expected non-null int.'); + final int output = api.getSupportedHardwareLevel(arg_identifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId was null, expected non-null int.'); + final String output = api.getCameraId(arg_identifier!); + return [output]; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/test/video_capture_test.dart b/packages/camera/camera_android_camerax/test/video_capture_test.dart index 6954b352ade..c5fd1f078b9 100644 --- a/packages/camera/camera_android_camerax/test/video_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/video_capture_test.dart @@ -60,7 +60,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final VideoCapture videoCapture = VideoCapture.detached( instanceManager: instanceManager, );