diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 647a57559951..2fd0eb9ed3d5 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6.0 + +* Implements `setFocusMode`, which makes this plugin reach feature parity with camera_android. +* Fixes `setExposureCompensationIndex` return value to use index returned by CameraX. + ## 0.5.0+36 * Implements `setExposureMode`. diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index fdc50955de43..d60a71a46c84 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -3,7 +3,7 @@ An Android implementation of [`camera`][1] that uses the [CameraX library][2]. *Note*: This package is under development, so please note the -[missing features and limitations](#missing-features-and-limitations), but +[missing features and limitations](#limitations), but otherwise feel free to try out the current implementation and provide any feedback by filing issues under [`flutter/flutter`][5] with `[camerax]` in the title, which will be actively triaged. @@ -22,7 +22,7 @@ dependencies: camera_android_camerax: ^0.5.0 ``` -## Missing features and limitations +## Limitations ### 240p resolution configuration for video recording @@ -30,10 +30,6 @@ dependencies: and thus, the plugin will fall back to 480p if configured with a `ResolutionPreset`. -### Focus mode configuration \[[Issue #120467][120467]\] - -`setFocusMode` is unimplemented. - ### Setting maximum duration and stream options for video capture Calling `startVideoCapturing` with `VideoCaptureOptions` configured with diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java index 5eeedd15211c..dcda333c2e90 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java @@ -5,6 +5,7 @@ package io.flutter.plugins.camerax; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.camera.core.FocusMeteringAction; import androidx.camera.core.MeteringPoint; @@ -29,7 +30,9 @@ public class FocusMeteringActionHostApiImpl implements FocusMeteringActionHostAp public static class FocusMeteringActionProxy { /** Creates an instance of {@link FocusMeteringAction}. */ public @NonNull FocusMeteringAction create( - @NonNull List meteringPoints, @NonNull List meteringPointModes) { + @NonNull List meteringPoints, + @NonNull List meteringPointModes, + @Nullable Boolean disableAutoCancel) { if (meteringPoints.size() >= 1 && meteringPoints.size() != meteringPointModes.size()) { throw new IllegalArgumentException( "One metering point must be specified and the number of specified metering points must match the number of specified metering point modes."); @@ -59,6 +62,10 @@ public static class FocusMeteringActionProxy { } } + if (disableAutoCancel != null && disableAutoCancel == true) { + focusMeteringActionBuilder.disableAutoCancel(); + } + return focusMeteringActionBuilder.build(); } @@ -100,7 +107,9 @@ public FocusMeteringActionHostApiImpl(@NonNull InstanceManager instanceManager) @Override public void create( - @NonNull Long identifier, @NonNull List meteringPointInfos) { + @NonNull Long identifier, + @NonNull List meteringPointInfos, + @Nullable Boolean disableAutoCancel) { final List meteringPoints = new ArrayList(); final List meteringPointModes = new ArrayList(); for (MeteringPointInfo meteringPointInfo : meteringPointInfos) { @@ -110,6 +119,6 @@ public void create( } instanceManager.addDartCreatedInstance( - proxy.create(meteringPoints, meteringPointModes), identifier); + proxy.create(meteringPoints, meteringPointModes, disableAutoCancel), identifier); } } 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 ac92427314f9..75b4a8c276bc 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 @@ -3796,7 +3796,10 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface FocusMeteringActionHostApi { - void create(@NonNull Long identifier, @NonNull List meteringPointInfos); + void create( + @NonNull Long identifier, + @NonNull List meteringPointInfos, + @Nullable Boolean disableAutoCancel); /** The codec used by FocusMeteringActionHostApi. */ static @NonNull MessageCodec getCodec() { @@ -3822,10 +3825,12 @@ static void setup( Number identifierArg = (Number) args.get(0); List meteringPointInfosArg = (List) args.get(1); + Boolean disableAutoCancelArg = (Boolean) args.get(2); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), - meteringPointInfosArg); + meteringPointInfosArg, + disableAutoCancelArg); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java index 64db80541619..6d96d3036cd7 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,7 +44,7 @@ public void tearDown() { } @Test - public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatHasMode() { + public void hostApiCreate_createsExpectedFocusMeteringActionWithInitialPointThatHasMode() { FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy = spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy()); FocusMeteringActionHostApiImpl hostApi = @@ -89,7 +90,7 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatH List mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3); - hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos); + hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos, null); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3); @@ -98,7 +99,8 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatH } @Test - public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() { + public void + hostApiCreate_createsExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() { FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy = spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy()); FocusMeteringActionHostApiImpl hostApi = @@ -142,11 +144,49 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatD List mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3); - hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos); + hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos, null); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode); verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3); assertEquals( testInstanceManager.getInstance(focusMeteringActionIdentifier), focusMeteringAction); } + + @Test + public void hostApiCreate_disablesAutoCancelAsExpected() { + FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy = + spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy()); + FocusMeteringActionHostApiImpl hostApi = + new FocusMeteringActionHostApiImpl(testInstanceManager, proxySpy); + + FocusMeteringAction.Builder mockFocusMeteringActionBuilder = + mock(FocusMeteringAction.Builder.class); + final MeteringPoint mockMeteringPoint = mock(MeteringPoint.class); + final Long mockMeteringPointId = 47L; + + MeteringPointInfo fakeMeteringPointInfo = + new MeteringPointInfo.Builder() + .setMeteringPointId(mockMeteringPointId) + .setMeteringMode(null) + .build(); + + testInstanceManager.addDartCreatedInstance(mockMeteringPoint, mockMeteringPointId); + + when(proxySpy.getFocusMeteringActionBuilder(mockMeteringPoint)) + .thenReturn(mockFocusMeteringActionBuilder); + when(mockFocusMeteringActionBuilder.build()).thenReturn(focusMeteringAction); + + List mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo); + + // Test not disabling auto cancel. + hostApi.create(73L, mockMeteringPointInfos, /* disableAutoCancel */ null); + verify(mockFocusMeteringActionBuilder, never()).disableAutoCancel(); + + hostApi.create(74L, mockMeteringPointInfos, /* disableAutoCancel */ false); + verify(mockFocusMeteringActionBuilder, never()).disableAutoCancel(); + + // Test disabling auto cancel. + hostApi.create(75L, mockMeteringPointInfos, /* disableAutoCancel */ true); + verify(mockFocusMeteringActionBuilder).disableAutoCancel(); + } } diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart index ff1f620e03fa..22221fd1de96 100644 --- a/packages/camera/camera_android_camerax/example/lib/main.dart +++ b/packages/camera/camera_android_camerax/example/lib/main.dart @@ -477,8 +477,9 @@ class _CameraExampleHomeState extends State children: [ TextButton( style: styleAuto, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.auto) + : null, onLongPress: () { if (controller != null) { CameraPlatform.instance @@ -490,8 +491,9 @@ class _CameraExampleHomeState extends State ), TextButton( style: styleLocked, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.locked) + : null, child: const Text('LOCKED'), ), ], 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 261525113d45..1612ab512d32 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 @@ -26,6 +26,7 @@ import 'device_orientation_manager.dart'; import 'exposure_state.dart'; import 'fallback_strategy.dart'; import 'focus_metering_action.dart'; +import 'focus_metering_result.dart'; import 'image_analysis.dart'; import 'image_capture.dart'; import 'image_proxy.dart'; @@ -189,11 +190,33 @@ class AndroidCameraCameraX extends CameraPlatform { /// for an example on how setting target rotations for [UseCase]s works. bool shouldSetDefaultRotation = false; + /// Error code indicating that an exposure offset value failed to be set. + static const String setExposureOffsetFailedErrorCode = + 'setExposureOffsetFailed'; + /// The currently set [FocusMeteringAction] used to enable auto-focus and /// auto-exposure. @visibleForTesting FocusMeteringAction? currentFocusMeteringAction; + /// Current focus mode set via [setFocusMode]. + /// + /// CameraX defaults to auto focus mode. + FocusMode _currentFocusMode = FocusMode.auto; + + /// Current exposure mode set via [setExposureMode]. + /// + /// CameraX defaults to auto exposure mode. + ExposureMode _currentExposureMode = ExposureMode.auto; + + /// Whether or not a default focus point of the entire sensor area was focused + /// and locked. + /// + /// This should only be true if [setExposureMode] was called to set + /// [FocusMode.locked] and no previous focus point was set via + /// [setFocusPoint]. + bool _defaultFocusPointLocked = false; + /// Error code indicating that exposure compensation is not supported by /// CameraX for the device. static const String exposureCompensationNotSupported = @@ -451,11 +474,21 @@ class AndroidCameraCameraX extends CameraPlatform { /// Supplying `null` for the [point] argument will result in resetting to the /// original exposure point value. /// + /// Supplied non-null point must be mapped to the entire un-altered preview + /// surface for the exposure point to be applied accurately. + /// /// [cameraId] is not used. @override Future setExposurePoint(int cameraId, Point? point) async { - await _startFocusAndMeteringFor( - point: point, meteringMode: FocusMeteringAction.flagAe); + // We lock the new focus and metering action if focus mode has been locked + // to ensure that the current focus point remains locked. Any exposure mode + // setting will not be impacted by this lock (setting an exposure mode + // is implemented with Camera2 interop that will override settings to + // achieve the expected exposure mode as needed). + await _startFocusAndMeteringForPoint( + point: point, + meteringMode: FocusMeteringAction.flagAe, + disableAutoCancel: _currentFocusMode == FocusMode.locked); } /// Gets the minimum supported exposure offset for the selected camera in EV units. @@ -478,6 +511,86 @@ class AndroidCameraCameraX extends CameraPlatform { exposureState.exposureCompensationStep; } + /// Sets the focus mode for taking pictures. + /// + /// Setting [FocusMode.locked] will lock the current focus point if one exists + /// or the center of entire sensor area if not, and will stay locked until + /// either: + /// * Another focus point is set via [setFocusPoint] (which will then become + /// the locked focus point), or + /// * Locked focus mode is unset by setting [FocusMode.auto]. + @override + Future setFocusMode(int cameraId, FocusMode mode) async { + if (_currentFocusMode == mode) { + // Desired focus mode is already set. + return; + } + + MeteringPoint? autoFocusPoint; + bool? disableAutoCancel; + switch (mode) { + case FocusMode.auto: + // Determine auto-focus point to restore, if any. We do not restore + // default auto-focus point if set previously to lock focus. + final MeteringPoint? unLockedFocusPoint = _defaultFocusPointLocked + ? null + : currentFocusMeteringAction!.meteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList() + .first + .$1; + _defaultFocusPointLocked = false; + autoFocusPoint = unLockedFocusPoint; + disableAutoCancel = false; + case FocusMode.locked: + MeteringPoint? lockedFocusPoint; + + // Determine if there is an auto-focus point set currently to lock. + if (currentFocusMeteringAction != null) { + final List<(MeteringPoint, int?)> possibleCurrentAfPoints = + currentFocusMeteringAction!.meteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList(); + lockedFocusPoint = possibleCurrentAfPoints.isEmpty + ? null + : possibleCurrentAfPoints.first.$1; + } + + // If there isn't, lock center of entire sensor area by default. + if (lockedFocusPoint == null) { + lockedFocusPoint = + proxy.createMeteringPoint(0.5, 0.5, 1, cameraInfo!); + _defaultFocusPointLocked = true; + } + + autoFocusPoint = lockedFocusPoint; + disableAutoCancel = true; + } + // Start appropriate focus and metering action. + final bool focusAndMeteringWasSuccessful = await _startFocusAndMeteringFor( + meteringPoint: autoFocusPoint, + meteringMode: FocusMeteringAction.flagAf, + disableAutoCancel: disableAutoCancel); + + if (!focusAndMeteringWasSuccessful) { + // Do not update current focus mode. + return; + } + + // Update current focus mode. + _currentFocusMode = mode; + + // If focus mode was just locked and exposure mode is not, set auto exposure + // mode to ensure that disabling auto-cancel does not interfere with + // automatic exposure metering. + if (_currentExposureMode == ExposureMode.auto && + _currentFocusMode == FocusMode.locked) { + await setExposureMode(cameraId, _currentExposureMode); + } + } + /// Gets the supported step size for exposure offset for the selected camera in EV units. /// /// Returns -1 if exposure compensation is not supported for the device. @@ -524,15 +637,20 @@ class AndroidCameraCameraX extends CameraPlatform { (offset / exposureOffsetStepSize).round(); try { - await cameraControl + final int? newIndex = await cameraControl .setExposureCompensationIndex(roundedExposureCompensationIndex); + if (newIndex == null) { + throw CameraException(setExposureOffsetFailedErrorCode, + 'Setting exposure compensation index was canceled due to the camera being closed or a new request being submitted.'); + } + + return newIndex.toDouble(); } on PlatformException catch (e) { throw CameraException( - 'setExposureOffsetFailed', + setExposureOffsetFailedErrorCode, e.message ?? 'Setting the camera exposure compensation index failed.'); } - return roundedExposureCompensationIndex * exposureOffsetStepSize; } /// Sets the focus point for automatically determining the focus values. @@ -540,11 +658,21 @@ class AndroidCameraCameraX extends CameraPlatform { /// Supplying `null` for the [point] argument will result in resetting to the /// original focus point value. /// + /// Supplied non-null point must be mapped to the entire un-altered preview + /// surface for the focus point to be applied accurately. + /// /// [cameraId] is not used. @override Future setFocusPoint(int cameraId, Point? point) async { - await _startFocusAndMeteringFor( - point: point, meteringMode: FocusMeteringAction.flagAf); + // We lock the new focus and metering action if focus mode has been locked + // to ensure that the current focus point remains locked. Any exposure mode + // setting will not be impacted by this lock (setting an exposure mode + // is implemented with Camera2 interop that will override settings to + // achieve the expected exposure mode as needed). + await _startFocusAndMeteringForPoint( + point: point, + meteringMode: FocusMeteringAction.flagAf, + disableAutoCancel: _currentFocusMode == FocusMode.locked); } /// Sets the exposure mode for taking pictures. @@ -566,6 +694,7 @@ class AndroidCameraCameraX extends CameraPlatform { )>[(CaptureRequestKeySupportedType.controlAeLock, lockExposureMode)]); await camera2Control.addCaptureRequestOptions(captureRequestOptions); + _currentExposureMode = mode; } /// Gets the maximum supported zoom level for the selected camera. @@ -1088,33 +1217,51 @@ class AndroidCameraCameraX extends CameraPlatform { // Methods for configuring auto-focus and auto-exposure: - /// Starts a focus and metering action. + Future _startFocusAndMeteringForPoint( + {required Point? point, + required int meteringMode, + bool disableAutoCancel = false}) async { + return _startFocusAndMeteringFor( + meteringPoint: point == null + ? null + : proxy.createMeteringPoint( + point.x, point.y, /* size */ null, cameraInfo!), + meteringMode: meteringMode, + disableAutoCancel: disableAutoCancel); + } + + /// Starts a focus and metering action and returns whether or not it was + /// successful. /// - /// This method will modify and start the current action's metering points - /// overriden with the [point] provided for the specified [meteringMode] type - /// only, with all other points of other modes left untouched. Thus, the - /// focus and metering action started will contain only the one most recently - /// set point for each metering mode: AF, AE, AWB. + /// This method will modify and start the current action's [MeteringPoint]s + /// overriden with the [meteringPoint] provided for the specified + /// [meteringMode] type only, with all other metering points of other modes + /// left untouched. If no current action exists, only the specified + /// [meteringPoint] will be set. Thus, the focus and metering action started + /// will only contain at most the one most recently set metering point for + /// each metering mode: AF, AE, AWB. /// - /// Thus, if [point] is non-null, this action includes: + /// Thus, if [meteringPoint] is non-null, this action includes: /// * metering points and their modes previously added to /// [currentFocusMeteringAction] that do not share a metering mode with - /// [point] and - /// * [point] with the specified [meteringMode]. - /// If [point] is null, this action includes only metering points and - /// their modes previously added to [currentFocusMeteringAction] that do not - /// share a metering mode with [point]. If there are no such metering - /// points, then the previously enabled focus and metering actions will be - /// canceled. - Future _startFocusAndMeteringFor( - {required Point? point, required int meteringMode}) async { - if (point == null) { + /// [meteringPoint] (if [currentFocusMeteringAction] is non-null) and + /// * [meteringPoint] with the specified [meteringMode]. + /// If [meteringPoint] is null and [currentFocusMeteringAction] is non-null, + /// this action includes only metering points and their modes previously added + /// to [currentFocusMeteringAction] that do not share a metering mode with + /// [meteringPoint]. If [meteringPoint] and [currentFocusMeteringAction] are + /// null, then focus and metering will be canceled. + Future _startFocusAndMeteringFor( + {required MeteringPoint? meteringPoint, + required int meteringMode, + bool disableAutoCancel = false}) async { + if (meteringPoint == null) { // Try to clear any metering point from previous action with the specified // meteringMode. if (currentFocusMeteringAction == null) { // Attempting to clear a metering point from a previous action, but no // such action exists. - return; + return false; } // Remove metering point with specified meteringMode from current focus @@ -1135,13 +1282,16 @@ class AndroidCameraCameraX extends CameraPlatform { // started focus and metering actions. await cameraControl.cancelFocusAndMetering(); currentFocusMeteringAction = null; - return; + return true; } - currentFocusMeteringAction = - proxy.createFocusMeteringAction(newMeteringPointInfos); - } else if (point.x < 0 || point.x > 1 || point.y < 0 || point.y > 1) { + currentFocusMeteringAction = proxy.createFocusMeteringAction( + newMeteringPointInfos, disableAutoCancel); + } else if (meteringPoint.x < 0 || + meteringPoint.x > 1 || + meteringPoint.y < 0 || + meteringPoint.y > 1) { throw CameraException('pointInvalid', - 'The coordinates of a metering point for an auto-focus or auto-exposure action must be within (0,0) and (1,1), but point $point was provided for metering mode $meteringMode.'); + 'The coordinates of a metering point for an auto-focus or auto-exposure action must be within (0,0) and (1,1), but a point with coordinates (${meteringPoint.x}, ${meteringPoint.y}) was provided for metering mode $meteringMode.'); } else { // Add new metering point with specified meteringMode, which may involve // replacing a metering point with the same specified meteringMode from @@ -1159,13 +1309,13 @@ class AndroidCameraCameraX extends CameraPlatform { meteringPointInfo.$2 != meteringMode) .toList(); } - final MeteringPoint newMeteringPoint = - proxy.createMeteringPoint(point.x, point.y, cameraInfo!); - newMeteringPointInfos.add((newMeteringPoint, meteringMode)); - currentFocusMeteringAction = - proxy.createFocusMeteringAction(newMeteringPointInfos); + newMeteringPointInfos.add((meteringPoint, meteringMode)); + currentFocusMeteringAction = proxy.createFocusMeteringAction( + newMeteringPointInfos, disableAutoCancel); } - await cameraControl.startFocusAndMetering(currentFocusMeteringAction!); + final FocusMeteringResult? result = + await cameraControl.startFocusAndMetering(currentFocusMeteringAction!); + return await result?.isFocusSuccessful() ?? false; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera_control.dart b/packages/camera/camera_android_camerax/lib/src/camera_control.dart index a233011975f0..0a307f3afc4a 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_control.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_control.dart @@ -153,7 +153,9 @@ class _CameraControlHostApiImpl extends CameraControlHostApi { } on PlatformException catch (e) { SystemServices.cameraErrorStreamController .add(e.message ?? 'Starting focus and metering failed.'); - return Future.value(); + // Surfacing error to differentiate an operation cancellation from an + // illegal argument exception at a plugin layer. + rethrow; } } 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 14239dd01b69..78c167bc4a11 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 @@ -3035,14 +3035,18 @@ class FocusMeteringActionHostApi { static const MessageCodec codec = _FocusMeteringActionHostApiCodec(); - Future create(int arg_identifier, - List arg_meteringPointInfos) async { + Future create( + int arg_identifier, + List arg_meteringPointInfos, + bool? arg_disableAutoCancel) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FocusMeteringActionHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_identifier, arg_meteringPointInfos]) - as List?; + final List? replyList = await channel.send([ + arg_identifier, + arg_meteringPointInfos, + arg_disableAutoCancel + ]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', 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 2ccb353aa66c..fb100ddb20a2 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart @@ -56,9 +56,9 @@ class CameraXProxy { this.setPreviewSurfaceProvider = _setPreviewSurfaceProvider, this.getDefaultDisplayRotation = _getDefaultDisplayRotation, this.getCamera2CameraControl = _getCamera2CameraControl, - this.createCaptureRequestOptions = _createCaptureRequestOptions, - this.createMeteringPoint = _createMeteringPoint, - this.createFocusMeteringAction = _createFocusMeteringAction, + this.createCaptureRequestOptions = _createAttachedCaptureRequestOptions, + this.createMeteringPoint = _createAttachedMeteringPoint, + this.createFocusMeteringAction = _createAttachedFocusMeteringAction, }); /// Returns a [ProcessCameraProvider] instance. @@ -158,13 +158,14 @@ class CameraXProxy { /// Returns a [MeteringPoint] with the specified coordinates based on /// [cameraInfo]. - MeteringPoint Function(double x, double y, CameraInfo cameraInfo) + MeteringPoint Function( + double x, double y, double? size, CameraInfo cameraInfo) createMeteringPoint; /// Returns a [FocusMeteringAction] based on the specified metering points /// and their modes. - FocusMeteringAction Function(List<(MeteringPoint, int?)> meteringPointInfos) - createFocusMeteringAction; + FocusMeteringAction Function(List<(MeteringPoint, int?)> meteringPointInfos, + bool? disableAutoCancel) createFocusMeteringAction; static Future _getProcessCameraProvider() { return ProcessCameraProvider.getInstance(); @@ -274,18 +275,20 @@ class CameraXProxy { return Camera2CameraControl(cameraControl: cameraControl); } - static CaptureRequestOptions _createCaptureRequestOptions( + static CaptureRequestOptions _createAttachedCaptureRequestOptions( List<(CaptureRequestKeySupportedType, Object?)> options) { return CaptureRequestOptions(requestedOptions: options); } - static MeteringPoint _createMeteringPoint( - double x, double y, CameraInfo cameraInfo) { - return MeteringPoint(x: x, y: y, cameraInfo: cameraInfo); + static MeteringPoint _createAttachedMeteringPoint( + double x, double y, double? size, CameraInfo cameraInfo) { + return MeteringPoint(x: x, y: y, size: size, cameraInfo: cameraInfo); } - static FocusMeteringAction _createFocusMeteringAction( - List<(MeteringPoint, int?)> meteringPointInfos) { - return FocusMeteringAction(meteringPointInfos: meteringPointInfos); + static FocusMeteringAction _createAttachedFocusMeteringAction( + List<(MeteringPoint, int?)> meteringPointInfos, bool? disableAutoCancel) { + return FocusMeteringAction( + meteringPointInfos: meteringPointInfos, + disableAutoCancel: disableAutoCancel); } } diff --git a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart index fe09ebc0443e..81fa6f5abf14 100644 --- a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart +++ b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart @@ -20,13 +20,14 @@ class FocusMeteringAction extends JavaObject { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, required this.meteringPointInfos, + this.disableAutoCancel, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ) { _api = _FocusMeteringActionHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); - _api.createFromInstance(this, meteringPointInfos); + _api.createFromInstance(this, meteringPointInfos, disableAutoCancel); } /// Creates a [FocusMeteringAction] that is not automatically attached to a @@ -35,6 +36,7 @@ class FocusMeteringAction extends JavaObject { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, required this.meteringPointInfos, + this.disableAutoCancel, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, @@ -50,6 +52,12 @@ class FocusMeteringAction extends JavaObject { final List<(MeteringPoint meteringPoint, int? meteringMode)> meteringPointInfos; + /// Disables the auto-cancel. + /// + /// By default (and if set to false), auto-cancel is enabled with 5 seconds + /// duration. + final bool? disableAutoCancel; + /// Flag for metering mode that indicates the auto focus region is enabled. /// /// An autofocus scan is also triggered when [flagAf] is assigned. @@ -97,8 +105,8 @@ class _FocusMeteringActionHostApiImpl extends FocusMeteringActionHostApi { /// [MeteringPoint]s and their modes in order of descending priority. void createFromInstance( FocusMeteringAction instance, - List<(MeteringPoint meteringPoint, int? meteringMode)> - meteringPointInfos) { + List<(MeteringPoint meteringPoint, int? meteringMode)> meteringPointInfos, + bool? disableAutoCancel) { final int identifier = instanceManager.addDartCreatedInstance(instance, onCopy: (FocusMeteringAction original) { return FocusMeteringAction.detached( @@ -118,6 +126,6 @@ class _FocusMeteringActionHostApiImpl extends FocusMeteringActionHostApi { meteringMode: meteringPointInfo.$2)); } - create(identifier, meteringPointInfosWithIds); + create(identifier, meteringPointInfosWithIds, disableAutoCancel); } } diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index fc78cae75d24..103c260c25c1 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -501,7 +501,8 @@ abstract class CameraControlFlutterApi { @HostApi(dartHostTestHandler: 'TestFocusMeteringActionHostApi') abstract class FocusMeteringActionHostApi { - void create(int identifier, List meteringPointInfos); + void create(int identifier, List meteringPointInfos, + bool? disableAutoCancel); } @HostApi(dartHostTestHandler: 'TestFocusMeteringResultHostApi') diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 660997d5945f..a1a44f06f366 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.5.0+36 +version: 0.6.0 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 05ee82abb4ab..d9ff081383e9 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 @@ -22,6 +22,7 @@ import 'package:camera_android_camerax/src/device_orientation_manager.dart'; import 'package:camera_android_camerax/src/exposure_state.dart'; import 'package:camera_android_camerax/src/fallback_strategy.dart'; import 'package:camera_android_camerax/src/focus_metering_action.dart'; +import 'package:camera_android_camerax/src/focus_metering_result.dart'; import 'package:camera_android_camerax/src/image_analysis.dart'; import 'package:camera_android_camerax/src/image_capture.dart'; import 'package:camera_android_camerax/src/image_proxy.dart'; @@ -58,10 +59,12 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -80,7 +83,6 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), ]) @GenerateMocks([], customMocks: >[ MockSpec>(as: #MockLiveCameraState), @@ -122,14 +124,42 @@ void main() { /// CameraXProxy for testing exposure and focus related controls. /// - /// Modifies the creation of MeteringPoints and FocusMeteringActions to return - /// objects detached from a native object. + /// Modifies the creation of [MeteringPoint]s and [FocusMeteringAction]s to + /// return objects detached from a native object. CameraXProxy getProxyForExposureAndFocus() => CameraXProxy( - createMeteringPoint: (double x, double y, CameraInfo cameraInfo) => - MeteringPoint.detached(x: x, y: y, cameraInfo: cameraInfo), - createFocusMeteringAction: (List<(MeteringPoint, int?)> - meteringPointInfos) => - FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos)); + createMeteringPoint: + (double x, double y, double? size, CameraInfo cameraInfo) => + MeteringPoint.detached( + x: x, y: y, size: size, cameraInfo: cameraInfo), + createFocusMeteringAction: + (List<(MeteringPoint, int?)> meteringPointInfos, + bool? disableAutoCancel) => + FocusMeteringAction.detached( + meteringPointInfos: meteringPointInfos, + disableAutoCancel: disableAutoCancel), + ); + + /// CameraXProxy for testing setting focus and exposure points. + /// + /// Modifies the retrieval of a [Camera2CameraControl] instance to depend on + /// interaction with expected [cameraControl] instance and modifies creation + /// of [CaptureRequestOptions] to return objects detached from a native object. + CameraXProxy getProxyForSettingFocusandExposurePoints( + CameraControl cameraControlForComparison, + Camera2CameraControl camera2cameraControl) { + final CameraXProxy proxy = getProxyForExposureAndFocus(); + + proxy.getCamera2CameraControl = (CameraControl cameraControl) => + cameraControl == cameraControlForComparison + ? camera2cameraControl + : Camera2CameraControl.detached(cameraControl: cameraControl); + + proxy.createCaptureRequestOptions = + (List<(CaptureRequestKeySupportedType, Object?)> options) => + CaptureRequestOptions.detached(requestedOptions: options); + + return proxy; + } test('Should fetch CameraDescription instances for available cameras', () async { @@ -2139,6 +2169,7 @@ void main() { // Set directly for test versus calling createCamera. camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); camera.proxy = getProxyForExposureAndFocus(); @@ -2253,6 +2284,51 @@ void main() { equals(FocusMeteringAction.flagAe)); }); + test( + 'setExposurePoint disables auto-cancel for focus and metering as expected', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 2; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.1, 0.2); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Test not disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + await camera.setExposurePoint(cameraId, exposurePoint); + VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + + clearInteractions(mockCameraControl); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + await camera.setExposurePoint(cameraId, exposurePoint); + verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + capturedAction = verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + test( 'setExposureOffset throws exception if exposure compensation not supported', () async { @@ -2276,7 +2352,7 @@ void main() { }); test( - 'setExposureOffset throws exception if exposure compensation could not be set', + 'setExposureOffset throws exception if exposure compensation could not be set for unknown reason', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 11; @@ -2304,6 +2380,35 @@ void main() { throwsA(isA())); }); + test( + 'setExposureOffset throws exception if exposure compensation could not be set due to camera being closed or newer value being set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 21; + const double offset = 5; + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final CameraControl mockCameraControl = MockCameraControl(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.1); + final int expectedExposureCompensationIndex = + (offset / exposureState.exposureCompensationStep).round(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = mockCameraInfo; + camera.cameraControl = mockCameraControl; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + when(mockCameraControl + .setExposureCompensationIndex(expectedExposureCompensationIndex)) + .thenAnswer((_) async => Future.value()); + + expect(() => camera.setExposureOffset(cameraId, offset), + throwsA(isA())); + }); + test( 'setExposureOffset behaves as expected to successful attempt to set exposure compensation index', () async { @@ -2316,6 +2421,8 @@ void main() { exposureCompensationRange: ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), exposureCompensationStep: 0.2); + final int expectedExposureCompensationIndex = + (offset / exposureState.exposureCompensationStep).round(); // Set directly for test versus calling createCamera. camera.cameraInfo = mockCameraInfo; @@ -2323,6 +2430,12 @@ void main() { when(mockCameraInfo.getExposureState()) .thenAnswer((_) async => exposureState); + when(mockCameraControl + .setExposureCompensationIndex(expectedExposureCompensationIndex)) + .thenAnswer((_) async => Future.value( + (expectedExposureCompensationIndex * + exposureState.exposureCompensationStep) + .round())); // Exposure index * exposure offset step size = exposure offset, i.e. // 15 * 0.2 = 3. @@ -2401,6 +2514,7 @@ void main() { // Set directly for test versus calling createCamera. camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); camera.proxy = getProxyForExposureAndFocus(); @@ -2513,4 +2627,687 @@ void main() { expect(capturedMeteringPointInfos.first.$2, equals(FocusMeteringAction.flagAf)); }); + + test('setFocusPoint disables auto-cancel for focus and metering as expected', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 2; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.1, 0.2); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Test not disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, exposurePoint); + VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + + clearInteractions(mockCameraControl); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, exposurePoint); + verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + capturedAction = verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setFocusMode does nothing if setting auto-focus mode and is already using auto-focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 4; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set locked focus mode and then try to re-set it. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setFocusMode(cameraId, FocusMode.locked); + verifyNoMoreInteractions(mockCameraControl); + }); + + test( + 'setFocusMode does nothing if setting locked focus mode and is already using locked focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 4; + final MockCameraControl mockCameraControl = MockCameraControl(); + + // Camera uses auto-focus by default, so try setting auto mode again. + await camera.setFocusMode(cameraId, FocusMode.auto); + + verifyNoMoreInteractions(mockCameraControl); + }); + + test( + 'setFocusMode removes default auto-focus point if previously set and setting auto-focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 5; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double exposurePointX = 0.2; + const double exposurePointY = 0.7; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure points. + await camera.setExposurePoint( + cameraId, const Point(exposurePointX, exposurePointY)); + + // Lock focus default focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + clearInteractions(mockCameraControl); + + // Test removal of default focus point. + await camera.setFocusMode(cameraId, FocusMode.auto); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + + // We expect only the previously set exposure point to be re-set. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(exposurePointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(exposurePointY)); + expect(capturedMeteringPointInfos.first.$1.size, isNull); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAe)); + }); + + test( + 'setFocusMode cancels focus and metering if only focus point previously set is a focus point', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 5; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Lock focus default focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Test removal of default focus point. + await camera.setFocusMode(cameraId, FocusMode.auto); + + verify(mockCameraControl.cancelFocusAndMetering()); + }); + + test( + 'setFocusMode re-focuses on previously set auto-focus point with auto-canceled enabled if setting auto-focus mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 6; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double focusPointX = 0.1; + const double focusPointY = 0.2; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Lock a focus point. + await camera.setFocusPoint( + cameraId, const Point(focusPointX, focusPointY)); + await camera.setFocusMode(cameraId, FocusMode.locked); + + clearInteractions(mockCameraControl); + + // Test re-focusing on previously set auto-focus point with auto-cancel enabled. + await camera.setFocusMode(cameraId, FocusMode.auto); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); + expect(capturedMeteringPointInfos.first.$1.size, isNull); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusMode starts expected focus and metering action with previously set auto-focus point if setting locked focus mode and current focus and metering action has auto-focus point', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 7; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double focusPointX = 0.88; + const double focusPointY = 0.33; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Set a focus point. + await camera.setFocusPoint( + cameraId, const Point(focusPointX, focusPointY)); + clearInteractions(mockCameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect the set focus point to be locked. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY)); + expect(capturedMeteringPointInfos.first.$1.size, isNull); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusMode starts expected focus and metering action with previously set auto-focus point if setting locked focus mode and current focus and metering action has auto-focus point amongst others', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 8; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double focusPointX = 0.38; + const double focusPointY = 0.38; + const double exposurePointX = 0.54; + const double exposurePointY = 0.45; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Set focus and exposure points. + await camera.setFocusPoint( + cameraId, const Point(focusPointX, focusPointY)); + await camera.setExposurePoint( + cameraId, const Point(exposurePointX, exposurePointY)); + clearInteractions(mockCameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect two MeteringPoints, the set focus point and the set exposure + // point. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + + final List<(MeteringPoint, int?)> focusPoints = capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList(); + expect(focusPoints.length, equals(1)); + expect(focusPoints.first.$1.x, equals(focusPointX)); + expect(focusPoints.first.$1.y, equals(focusPointY)); + expect(focusPoints.first.$1.size, isNull); + + final List<(MeteringPoint, int?)> exposurePoints = + capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAe) + .toList(); + expect(exposurePoints.length, equals(1)); + expect(exposurePoints.first.$1.x, equals(exposurePointX)); + expect(exposurePoints.first.$1.y, equals(exposurePointY)); + expect(exposurePoints.first.$1.size, isNull); + }); + + test( + 'setFocusMode starts expected focus and metering action if setting locked focus mode and current focus and metering action does not contain an auto-focus point', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 9; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double exposurePointX = 0.8; + const double exposurePointY = 0.3; + const double defaultFocusPointX = 0.5; + const double defaultFocusPointY = 0.5; + const double defaultFocusPointSize = 1; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Set an exposure point (creates a current focus and metering action + // without a focus point). + await camera.setExposurePoint( + cameraId, const Point(exposurePointX, exposurePointY)); + clearInteractions(mockCameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect two MeteringPoints, the default focus point and the set + //exposure point. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(2)); + + final List<(MeteringPoint, int?)> focusPoints = capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAf) + .toList(); + expect(focusPoints.length, equals(1)); + expect(focusPoints.first.$1.x, equals(defaultFocusPointX)); + expect(focusPoints.first.$1.y, equals(defaultFocusPointY)); + expect(focusPoints.first.$1.size, equals(defaultFocusPointSize)); + + final List<(MeteringPoint, int?)> exposurePoints = + capturedMeteringPointInfos + .where(((MeteringPoint, int?) meteringPointInfo) => + meteringPointInfo.$2 == FocusMeteringAction.flagAe) + .toList(); + expect(exposurePoints.length, equals(1)); + expect(exposurePoints.first.$1.x, equals(exposurePointX)); + expect(exposurePoints.first.$1.y, equals(exposurePointY)); + expect(exposurePoints.first.$1.size, isNull); + }); + + test( + 'setFocusMode starts expected focus and metering action if there is no current focus and metering action', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 10; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + const double defaultFocusPointX = 0.5; + const double defaultFocusPointY = 0.5; + const double defaultFocusPointSize = 1; + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + + // We expect only the default focus point to be set. + final List<(MeteringPoint, int?)> capturedMeteringPointInfos = + capturedAction.meteringPointInfos; + expect(capturedMeteringPointInfos.length, equals(1)); + expect(capturedMeteringPointInfos.first.$1.x, equals(defaultFocusPointX)); + expect(capturedMeteringPointInfos.first.$1.y, equals(defaultFocusPointY)); + expect(capturedMeteringPointInfos.first.$1.size, + equals(defaultFocusPointSize)); + expect(capturedMeteringPointInfos.first.$2, + equals(FocusMeteringAction.flagAf)); + }); + + test( + 'setFocusMode re-sets exposure mode if setting locked focus mode while using auto exposure mode', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 11; + final MockCameraControl mockCameraControl = MockCameraControl(); + final FocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.cameraInfo = MockCameraInfo(); + camera.cameraControl = mockCameraControl; + + when(mockCamera2CameraControl.addCaptureRequestOptions(any)) + .thenAnswer((_) async => Future.value()); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, mockCamera2CameraControl); + + // Make setting focus and metering action successful for test. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set auto exposure mode. + await camera.setExposureMode(cameraId, ExposureMode.auto); + clearInteractions(mockCamera2CameraControl); + + // Lock focus point. + await camera.setFocusMode(cameraId, FocusMode.locked); + + final VerificationResult verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + final CaptureRequestOptions capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + final List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = + capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlAeLock)); + expect(requestedOptions.first.$2, equals(false)); + }); + + test( + 'setFocusPoint disables auto-cancel if auto focus mode fails to be set after locked focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 22; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point focusPoint = Point(0.21, 0.21); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful to set locked focus + // mode. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point ( + // otherwise, focus and metering will be canceled altogether, which is + //considered a successful call). + await camera.setExposurePoint(cameraId, const Point(0.3, 0.4)); + + // Set locked focus mode so we can set auto mode (cannot set auto mode + // directly since it is the default). + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + // Make setting focus and metering action fail to test that auto-cancel is + // still disabled. + reset(mockFocusMeteringResult); + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, focusPoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setExposurePoint disables auto-cancel if auto focus mode fails to be set after locked focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 342; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.23, 0.32); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action successful to set locked focus + // mode. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(true)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point ( + // otherwise, focus and metering will be canceled altogether, which is + //considered a successful call). + await camera.setExposurePoint(cameraId, const Point(0.4, 0.3)); + + // Set locked focus mode so we can set auto mode (cannot set auto mode + // directly since it is the default). + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + // Make setting focus and metering action fail to test that auto-cancel is + // still disabled. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + + // Test disabling auto cancel. + await camera.setFocusMode(cameraId, FocusMode.auto); + clearInteractions(mockCameraControl); + + await camera.setExposurePoint(cameraId, exposurePoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isTrue); + }); + + test( + 'setFocusPoint enables auto-cancel if locked focus mode fails to be set after auto focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 232; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point focusPoint = Point(0.221, 0.211); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action fail to test auto-cancel is not + // disabled. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point. + await camera.setExposurePoint(cameraId, const Point(0.43, 0.34)); + + // Test failing to set locked focus mode. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setFocusPoint(cameraId, focusPoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + }); + + test( + 'setExposurePoint enables auto-cancel if locked focus mode fails to be set after auto focus mode is set', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 323; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockFocusMeteringResult mockFocusMeteringResult = + MockFocusMeteringResult(); + const Point exposurePoint = Point(0.223, 0.332); + + // Set directly for test versus calling createCamera. + camera.cameraControl = mockCameraControl; + camera.cameraInfo = MockCameraInfo(); + + camera.proxy = getProxyForSettingFocusandExposurePoints( + mockCameraControl, MockCamera2CameraControl()); + + // Make setting focus and metering action fail to test auto-cancel is not + // disabled. + when(mockFocusMeteringResult.isFocusSuccessful()) + .thenAnswer((_) async => Future.value(false)); + when(mockCameraControl.startFocusAndMetering(any)).thenAnswer((_) async => + Future.value(mockFocusMeteringResult)); + + // Set exposure point to later mock failed call to set an exposure point. + await camera.setExposurePoint(cameraId, const Point(0.5, 0.2)); + + // Test failing to set locked focus mode. + await camera.setFocusMode(cameraId, FocusMode.locked); + clearInteractions(mockCameraControl); + + await camera.setExposurePoint(cameraId, exposurePoint); + final VerificationResult verificationResult = + verify(mockCameraControl.startFocusAndMetering(captureAny)); + final FocusMeteringAction capturedAction = + verificationResult.captured.single as FocusMeteringAction; + expect(capturedAction.disableAutoCancel, isFalse); + }); } 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 be2541b8f92b..80d76d8339ca 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,39 +4,39 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i16; -import 'dart:typed_data' as _i29; +import 'dart:typed_data' as _i31; import 'package:camera_android_camerax/src/analyzer.dart' as _i15; import 'package:camera_android_camerax/src/camera.dart' as _i9; -import 'package:camera_android_camerax/src/camera2_camera_control.dart' as _i38; +import 'package:camera_android_camerax/src/camera2_camera_control.dart' as _i22; 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 _i22; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i24; import 'package:camera_android_camerax/src/camera_state.dart' as _i18; import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; import 'package:camera_android_camerax/src/capture_request_options.dart' - as _i39; + as _i23; import 'package:camera_android_camerax/src/exposure_state.dart' as _i5; -import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i23; +import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i25; import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i21; import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i20; -import 'package:camera_android_camerax/src/image_analysis.dart' as _i24; -import 'package:camera_android_camerax/src/image_capture.dart' as _i25; +import 'package:camera_android_camerax/src/image_analysis.dart' as _i26; +import 'package:camera_android_camerax/src/image_capture.dart' as _i27; import 'package:camera_android_camerax/src/image_proxy.dart' as _i17; import 'package:camera_android_camerax/src/live_data.dart' as _i4; -import 'package:camera_android_camerax/src/observer.dart' as _i28; +import 'package:camera_android_camerax/src/observer.dart' as _i30; import 'package:camera_android_camerax/src/pending_recording.dart' as _i10; -import 'package:camera_android_camerax/src/plane_proxy.dart' as _i27; -import 'package:camera_android_camerax/src/preview.dart' as _i30; +import 'package:camera_android_camerax/src/plane_proxy.dart' as _i29; +import 'package:camera_android_camerax/src/preview.dart' as _i32; import 'package:camera_android_camerax/src/process_camera_provider.dart' - as _i31; -import 'package:camera_android_camerax/src/quality_selector.dart' as _i33; + as _i33; +import 'package:camera_android_camerax/src/quality_selector.dart' as _i35; import 'package:camera_android_camerax/src/recorder.dart' as _i11; import 'package:camera_android_camerax/src/recording.dart' as _i8; -import 'package:camera_android_camerax/src/resolution_selector.dart' as _i34; -import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i35; -import 'package:camera_android_camerax/src/use_case.dart' as _i32; -import 'package:camera_android_camerax/src/video_capture.dart' as _i36; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i36; +import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i37; +import 'package:camera_android_camerax/src/use_case.dart' as _i34; +import 'package:camera_android_camerax/src/video_capture.dart' as _i38; import 'package:camera_android_camerax/src/zoom_state.dart' as _i19; import 'package:camera_platform_interface/camera_platform_interface.dart' as _i6; @@ -44,9 +44,9 @@ import 'package:flutter/foundation.dart' as _i14; import 'package:flutter/services.dart' as _i13; import 'package:flutter/widgets.dart' as _i12; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i26; +import 'package:mockito/src/dummies.dart' as _i28; -import 'test_camerax_library.g.dart' as _i37; +import 'test_camerax_library.g.dart' as _i39; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -443,6 +443,38 @@ class MockCameraControl extends _i1.Mock implements _i3.CameraControl { ) as _i16.Future); } +/// A class which mocks [Camera2CameraControl]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCamera2CameraControl extends _i1.Mock + implements _i22.Camera2CameraControl { + @override + _i3.CameraControl get cameraControl => (super.noSuchMethod( + Invocation.getter(#cameraControl), + returnValue: _FakeCameraControl_1( + this, + Invocation.getter(#cameraControl), + ), + returnValueForMissingStub: _FakeCameraControl_1( + this, + Invocation.getter(#cameraControl), + ), + ) as _i3.CameraControl); + + @override + _i16.Future addCaptureRequestOptions( + _i23.CaptureRequestOptions? captureRequestOptions) => + (super.noSuchMethod( + Invocation.method( + #addCaptureRequestOptions, + [captureRequestOptions], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); +} + /// A class which mocks [CameraImageData]. /// /// See the documentation for Mockito's code generation for more information. @@ -487,7 +519,7 @@ class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraSelector extends _i1.Mock implements _i22.CameraSelector { +class MockCameraSelector extends _i1.Mock implements _i24.CameraSelector { @override _i16.Future> filter(List<_i2.CameraInfo>? cameraInfos) => (super.noSuchMethod( @@ -533,7 +565,7 @@ 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 _i23.FallbackStrategy { +class MockFallbackStrategy extends _i1.Mock implements _i25.FallbackStrategy { @override _i7.VideoQuality get quality => (super.noSuchMethod( Invocation.getter(#quality), @@ -550,11 +582,28 @@ class MockFallbackStrategy extends _i1.Mock implements _i23.FallbackStrategy { ) as _i7.VideoResolutionFallbackRule); } +/// A class which mocks [FocusMeteringResult]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockFocusMeteringResult extends _i1.Mock + implements _i20.FocusMeteringResult { + @override + _i16.Future isFocusSuccessful() => (super.noSuchMethod( + Invocation.method( + #isFocusSuccessful, + [], + ), + returnValue: _i16.Future.value(false), + returnValueForMissingStub: _i16.Future.value(false), + ) as _i16.Future); +} + /// A class which mocks [ImageAnalysis]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageAnalysis extends _i1.Mock implements _i24.ImageAnalysis { +class MockImageAnalysis extends _i1.Mock implements _i26.ImageAnalysis { @override _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -590,7 +639,7 @@ class MockImageAnalysis extends _i1.Mock implements _i24.ImageAnalysis { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageCapture extends _i1.Mock implements _i25.ImageCapture { +class MockImageCapture extends _i1.Mock implements _i27.ImageCapture { @override _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -617,7 +666,7 @@ class MockImageCapture extends _i1.Mock implements _i25.ImageCapture { #takePicture, [], ), - returnValue: _i16.Future.value(_i26.dummyValue( + returnValue: _i16.Future.value(_i28.dummyValue( this, Invocation.method( #takePicture, @@ -625,7 +674,7 @@ class MockImageCapture extends _i1.Mock implements _i25.ImageCapture { ), )), returnValueForMissingStub: - _i16.Future.value(_i26.dummyValue( + _i16.Future.value(_i28.dummyValue( this, Invocation.method( #takePicture, @@ -662,16 +711,16 @@ class MockImageProxy extends _i1.Mock implements _i17.ImageProxy { ) as int); @override - _i16.Future> getPlanes() => (super.noSuchMethod( + _i16.Future> getPlanes() => (super.noSuchMethod( Invocation.method( #getPlanes, [], ), returnValue: - _i16.Future>.value(<_i27.PlaneProxy>[]), + _i16.Future>.value(<_i29.PlaneProxy>[]), returnValueForMissingStub: - _i16.Future>.value(<_i27.PlaneProxy>[]), - ) as _i16.Future>); + _i16.Future>.value(<_i29.PlaneProxy>[]), + ) as _i16.Future>); @override _i16.Future close() => (super.noSuchMethod( @@ -688,7 +737,7 @@ class MockImageProxy extends _i1.Mock implements _i17.ImageProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockObserver extends _i1.Mock implements _i28.Observer<_i18.CameraState> { +class MockObserver extends _i1.Mock implements _i30.Observer<_i18.CameraState> { @override void Function(Object) get onChanged => (super.noSuchMethod( Invocation.getter(#onChanged), @@ -739,13 +788,13 @@ class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPlaneProxy extends _i1.Mock implements _i27.PlaneProxy { +class MockPlaneProxy extends _i1.Mock implements _i29.PlaneProxy { @override - _i29.Uint8List get buffer => (super.noSuchMethod( + _i31.Uint8List get buffer => (super.noSuchMethod( Invocation.getter(#buffer), - returnValue: _i29.Uint8List(0), - returnValueForMissingStub: _i29.Uint8List(0), - ) as _i29.Uint8List); + returnValue: _i31.Uint8List(0), + returnValueForMissingStub: _i31.Uint8List(0), + ) as _i31.Uint8List); @override int get pixelStride => (super.noSuchMethod( @@ -766,7 +815,7 @@ class MockPlaneProxy extends _i1.Mock implements _i27.PlaneProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPreview extends _i1.Mock implements _i30.Preview { +class MockPreview extends _i1.Mock implements _i32.Preview { @override _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -826,7 +875,7 @@ class MockPreview extends _i1.Mock implements _i30.Preview { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockProcessCameraProvider extends _i1.Mock - implements _i31.ProcessCameraProvider { + implements _i33.ProcessCameraProvider { @override _i16.Future> getAvailableCameraInfos() => (super.noSuchMethod( @@ -842,8 +891,8 @@ class MockProcessCameraProvider extends _i1.Mock @override _i16.Future<_i9.Camera> bindToLifecycle( - _i22.CameraSelector? cameraSelector, - List<_i32.UseCase>? useCases, + _i24.CameraSelector? cameraSelector, + List<_i34.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( @@ -876,7 +925,7 @@ class MockProcessCameraProvider extends _i1.Mock ) as _i16.Future<_i9.Camera>); @override - _i16.Future isBound(_i32.UseCase? useCase) => (super.noSuchMethod( + _i16.Future isBound(_i34.UseCase? useCase) => (super.noSuchMethod( Invocation.method( #isBound, [useCase], @@ -886,7 +935,7 @@ class MockProcessCameraProvider extends _i1.Mock ) as _i16.Future); @override - void unbind(List<_i32.UseCase>? useCases) => super.noSuchMethod( + void unbind(List<_i34.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], @@ -908,7 +957,7 @@ 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 _i33.QualitySelector { +class MockQualitySelector extends _i1.Mock implements _i35.QualitySelector { @override List<_i7.VideoQualityData> get qualityList => (super.noSuchMethod( Invocation.getter(#qualityList), @@ -953,14 +1002,14 @@ class MockRecorder extends _i1.Mock implements _i11.Recorder { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionSelector extends _i1.Mock - implements _i34.ResolutionSelector {} + implements _i36.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 _i35.ResolutionStrategy {} + implements _i37.ResolutionStrategy {} /// A class which mocks [Recording]. /// @@ -1012,7 +1061,7 @@ class MockRecording extends _i1.Mock implements _i8.Recording { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockVideoCapture extends _i1.Mock implements _i36.VideoCapture { +class MockVideoCapture extends _i1.Mock implements _i38.VideoCapture { @override _i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -1235,7 +1284,7 @@ class MockBuildContext extends _i1.Mock implements _i12.BuildContext { /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i37.TestInstanceManagerHostApi { + implements _i39.TestInstanceManagerHostApi { @override void clear() => super.noSuchMethod( Invocation.method( @@ -1250,7 +1299,7 @@ class MockTestInstanceManagerHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestSystemServicesHostApi extends _i1.Mock - implements _i37.TestSystemServicesHostApi { + implements _i39.TestSystemServicesHostApi { @override _i16.Future<_i7.CameraPermissionsErrorData?> requestCameraPermissions( bool? enableAudio) => @@ -1277,7 +1326,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock suffix, ], ), - returnValue: _i26.dummyValue( + returnValue: _i28.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1287,7 +1336,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock ], ), ), - returnValueForMissingStub: _i26.dummyValue( + returnValueForMissingStub: _i28.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1320,38 +1369,6 @@ class MockZoomState extends _i1.Mock implements _i19.ZoomState { ) as double); } -/// A class which mocks [Camera2CameraControl]. -/// -/// See the documentation for Mockito's code generation for more information. -// ignore: must_be_immutable -class MockCamera2CameraControl extends _i1.Mock - implements _i38.Camera2CameraControl { - @override - _i3.CameraControl get cameraControl => (super.noSuchMethod( - Invocation.getter(#cameraControl), - returnValue: _FakeCameraControl_1( - this, - Invocation.getter(#cameraControl), - ), - returnValueForMissingStub: _FakeCameraControl_1( - this, - Invocation.getter(#cameraControl), - ), - ) as _i3.CameraControl); - - @override - _i16.Future addCaptureRequestOptions( - _i39.CaptureRequestOptions? captureRequestOptions) => - (super.noSuchMethod( - Invocation.method( - #addCaptureRequestOptions, - [captureRequestOptions], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); -} - /// A class which mocks [LiveData]. /// /// See the documentation for Mockito's code generation for more information. @@ -1363,7 +1380,7 @@ class MockLiveCameraState extends _i1.Mock } @override - _i16.Future observe(_i28.Observer<_i18.CameraState>? observer) => + _i16.Future observe(_i30.Observer<_i18.CameraState>? observer) => (super.noSuchMethod( Invocation.method( #observe, @@ -1395,7 +1412,7 @@ class MockLiveZoomState extends _i1.Mock } @override - _i16.Future observe(_i28.Observer<_i19.ZoomState>? observer) => + _i16.Future observe(_i30.Observer<_i19.ZoomState>? observer) => (super.noSuchMethod( Invocation.method( #observe, diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart index a74338e2735c..8a7dcdc6c117 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart @@ -43,8 +43,8 @@ void main() { instanceManager: instanceManager, ); - verifyNever( - mockApi.create(argThat(isA()), argThat(isA>()))); + verifyNever(mockApi.create(argThat(isA()), argThat(isA>()), + argThat(isA()))); }); test('create calls create on the Java side', () { final MockTestFocusMeteringActionHostApi mockApi = @@ -67,6 +67,7 @@ void main() { (mockMeteringPoint1, mockMeteringPoint1Mode), (mockMeteringPoint2, mockMeteringPoint2Mode) ]; + const bool disableAutoCancel = true; instanceManager .addHostCreatedInstance(mockMeteringPoint1, mockMeteringPoint1Id, @@ -81,12 +82,14 @@ void main() { final FocusMeteringAction instance = FocusMeteringAction( meteringPointInfos: meteringPointInfos, + disableAutoCancel: disableAutoCancel, instanceManager: instanceManager, ); final VerificationResult verificationResult = verify(mockApi.create( argThat(equals(instanceManager.getIdentifier(instance))), - captureAny)); + captureAny, + argThat(equals(disableAutoCancel)))); final List captureMeteringPointInfos = verificationResult.captured.single as List; expect(captureMeteringPointInfos.length, equals(2)); diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart index d1ceef9daadd..3b0e4c824076 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart @@ -77,6 +77,7 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock void create( int? identifier, List<_i5.MeteringPointInfo?>? meteringPointInfos, + bool? disableAutoCancel, ) => super.noSuchMethod( Invocation.method( @@ -84,6 +85,7 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock [ identifier, meteringPointInfos, + disableAutoCancel, ], ), returnValueForMissingStub: null, 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 c847327cce59..b69825928dd9 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 @@ -2065,7 +2065,8 @@ abstract class TestFocusMeteringActionHostApi { static const MessageCodec codec = _TestFocusMeteringActionHostApiCodec(); - void create(int identifier, List meteringPointInfos); + void create(int identifier, List meteringPointInfos, + bool? disableAutoCancel); static void setup(TestFocusMeteringActionHostApi? api, {BinaryMessenger? binaryMessenger}) { @@ -2090,7 +2091,9 @@ abstract class TestFocusMeteringActionHostApi { (args[1] as List?)?.cast(); assert(arg_meteringPointInfos != null, 'Argument for dev.flutter.pigeon.FocusMeteringActionHostApi.create was null, expected non-null List.'); - api.create(arg_identifier!, arg_meteringPointInfos!); + final bool? arg_disableAutoCancel = (args[2] as bool?); + api.create( + arg_identifier!, arg_meteringPointInfos!, arg_disableAutoCancel); return []; }); }