From 1a5a7ce56e87ac5ed91b66b79a5d8cde1430e982 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:10:15 -0800 Subject: [PATCH] [camerax] Wrap Android classes/methods required to set the exposure mode (#5966) Wraps classes/methods needed to set capture request options that will allow us to implement setting the exposure mode, namely: **Camera2CameraControl** - `create` method using [`from`](https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraControl#from(androidx.camera.core.CameraControl)) - [`setCaptureRequestOptions`](https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraControl#setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions)) **CaptureRequestOptions** - `create` method using its [builder](https://developer.android.com/reference/androidx/camera/camera2/interop/CaptureRequestOptions.Builder)* Part of https://github.com/flutter/flutter/issues/120468. *Note that this required that I specify types of supported capture request options due to the Dart/native split. I took inspiration from our previous implementation of supported `LiveData` types (see [pigeon file](https://github.com/flutter/packages/blob/73c9bdc59b8d51ee558bff113bcdcdb53485cd08/packages/camera/camera_android_camerax/pigeons/camerax_library.dart#L80) for details). --- .../camera_android_camerax/CHANGELOG.md | 4 + .../plugins/camerax/AnalyzerHostApiImpl.java | 4 +- .../AspectRatioStrategyHostApiImpl.java | 4 +- .../Camera2CameraControlHostApiImpl.java | 140 +++++++++++++ .../camerax/CameraControlHostApiImpl.java | 19 +- .../CaptureRequestOptionsHostApiImpl.java | 118 +++++++++++ .../camerax/FallbackStrategyHostApiImpl.java | 4 +- .../FocusMeteringActionHostApiImpl.java | 4 +- .../FocusMeteringResultHostApiImpl.java | 4 +- .../camerax/GeneratedCameraXLibrary.java | 186 +++++++++++++----- .../camerax/MeteringPointHostApiImpl.java | 4 +- .../plugins/camerax/ObserverHostApiImpl.java | 4 +- .../camerax/QualitySelectorHostApiImpl.java | 4 +- .../ResolutionSelectorHostApiImpl.java | 4 +- .../ResolutionStrategyHostApiImpl.java | 4 +- .../camerax/Camera2CameraControlTest.java | 130 ++++++++++++ .../camerax/CaptureRequestOptionsTest.java | 109 ++++++++++ .../lib/src/camera2_camera_control.dart | 135 +++++++++++++ .../lib/src/camerax_library.g.dart | 132 +++++++++---- .../lib/src/capture_request_options.dart | 139 +++++++++++++ .../pigeons/camerax_library.dart | 38 +++- .../camera_android_camerax/pubspec.yaml | 2 +- .../test/camera2_camera_control_test.dart | 126 ++++++++++++ .../camera2_camera_control_test.mocks.dart | 148 ++++++++++++++ .../test/capture_request_options_test.dart | 143 ++++++++++++++ .../capture_request_options_test.mocks.dart | 66 +++++++ .../test/test_camerax_library.g.dart | 143 ++++++++++---- 27 files changed, 1668 insertions(+), 150 deletions(-) create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraControlTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java create mode 100644 packages/camera/camera_android_camerax/lib/src/camera2_camera_control.dart create mode 100644 packages/camera/camera_android_camerax/lib/src/capture_request_options.dart create mode 100644 packages/camera/camera_android_camerax/test/camera2_camera_control_test.dart create mode 100644 packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart create mode 100644 packages/camera/camera_android_camerax/test/capture_request_options_test.dart create mode 100644 packages/camera/camera_android_camerax/test/capture_request_options_test.mocks.dart diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 18e031732fb7..9db3eadff531 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0+31 + +* Wraps CameraX classes needed to set capture request options, which is needed to implement setting the exposure mode. + ## 0.5.0+30 * Adds documentation to clarify how the plugin uses resolution presets as target resolutions for CameraX. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java index 6f6f7552ad41..fa788a244f97 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java @@ -22,7 +22,7 @@ public class AnalyzerHostApiImpl implements AnalyzerHostApi { private final InstanceManager instanceManager; private final AnalyzerProxy proxy; - /** Proxy for constructors and static method of {@link ImageAnalysis.Analyzer}. */ + /** Proxy for constructor of {@link ImageAnalysis.Analyzer}. */ @VisibleForTesting public static class AnalyzerProxy { @@ -95,7 +95,7 @@ public AnalyzerHostApiImpl( * * @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 constructors and static method of {@link ImageAnalysis.Analyzer} + * @param proxy proxy for constructor of {@link ImageAnalysis.Analyzer} */ @VisibleForTesting AnalyzerHostApiImpl( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java index 91e0def66eaf..7775ffe598db 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java @@ -19,7 +19,7 @@ public class AspectRatioStrategyHostApiImpl implements AspectRatioStrategyHostAp private final InstanceManager instanceManager; private final AspectRatioStrategyProxy proxy; - /** Proxy for constructors and static method of {@link AspectRatioStrategy}. */ + /** Proxy for constructor of {@link AspectRatioStrategy}. */ @VisibleForTesting public static class AspectRatioStrategyProxy { /** Creates an instance of {@link AspectRatioStrategy}. */ @@ -43,7 +43,7 @@ public AspectRatioStrategyHostApiImpl(@NonNull InstanceManager instanceManager) * Constructs an {@link AspectRatioStrategyHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link AspectRatioStrategy} + * @param proxy proxy for constructor of {@link AspectRatioStrategy} */ @VisibleForTesting AspectRatioStrategyHostApiImpl( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlHostApiImpl.java new file mode 100644 index 000000000000..8d10005f7d75 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlHostApiImpl.java @@ -0,0 +1,140 @@ +// 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 androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.annotation.VisibleForTesting; +import androidx.camera.camera2.interop.Camera2CameraControl; +import androidx.camera.camera2.interop.CaptureRequestOptions; +import androidx.camera.core.CameraControl; +import androidx.core.content.ContextCompat; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraControlHostApi; +import java.util.Objects; + +/** + * Host API implementation for {@link Camera2CameraControl}. + * + *

This class may handle 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 Camera2CameraControlHostApiImpl implements Camera2CameraControlHostApi { + private final InstanceManager instanceManager; + private final Camera2CameraControlProxy proxy; + + /** Proxy for constructor and methods of {@link Camera2CameraControl}. */ + @VisibleForTesting + public static class Camera2CameraControlProxy { + Context context; + + /** + * Creates an instance of {@link Camera2CameraControl} derived from specified {@link + * CameraControl} instance. + */ + @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) + public @NonNull Camera2CameraControl create(@NonNull CameraControl cameraControl) { + return Camera2CameraControl.from(cameraControl); + } + + /** + * Adds a {@link CaptureRequestOptions} to update the capture session with the options it + * contains. + */ + @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) + public void addCaptureRequestOptions( + @NonNull Camera2CameraControl camera2CameraControl, + @NonNull CaptureRequestOptions bundle, + @NonNull GeneratedCameraXLibrary.Result result) { + if (context == null) { + throw new IllegalStateException("Context must be set to add capture request options."); + } + + ListenableFuture addCaptureRequestOptionsFuture = + camera2CameraControl.addCaptureRequestOptions(bundle); + + Futures.addCallback( + addCaptureRequestOptionsFuture, + new FutureCallback() { + public void onSuccess(Void voidResult) { + result.success(null); + } + + public void onFailure(Throwable t) { + result.error(t); + } + }, + ContextCompat.getMainExecutor(context)); + } + } + + /** + * Constructs a {@link Camera2CameraControlHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param context {@link Context} used to retrieve {@code Executor} + */ + public Camera2CameraControlHostApiImpl( + @NonNull InstanceManager instanceManager, @NonNull Context context) { + this(instanceManager, new Camera2CameraControlProxy(), context); + } + + /** + * Constructs a {@link Camera2CameraControlHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructor and methods of {@link Camera2CameraControl} + * @param context {@link Context} used to retrieve {@code Executor} + */ + @VisibleForTesting + Camera2CameraControlHostApiImpl( + @NonNull InstanceManager instanceManager, + @NonNull Camera2CameraControlProxy proxy, + @NonNull Context context) { + this.instanceManager = instanceManager; + this.proxy = proxy; + proxy.context = context; + } + + /** + * Sets the context that the {@code Camera2CameraControl} will use to listen for the result of + * setting capture request options. + * + *

If using the camera plugin in an add-to-app context, ensure that this is called anytime that + * the context changes. + */ + public void setContext(@NonNull Context context) { + this.proxy.context = context; + } + + @Override + public void create(@NonNull Long identifier, @NonNull Long cameraControlIdentifier) { + instanceManager.addDartCreatedInstance( + proxy.create(Objects.requireNonNull(instanceManager.getInstance(cameraControlIdentifier))), + identifier); + } + + @Override + public void addCaptureRequestOptions( + @NonNull Long identifier, + @NonNull Long captureRequestOptionsIdentifier, + @NonNull GeneratedCameraXLibrary.Result result) { + proxy.addCaptureRequestOptions( + getCamera2CameraControlInstance(identifier), + Objects.requireNonNull(instanceManager.getInstance(captureRequestOptionsIdentifier)), + result); + } + + /** + * Retrieves the {@link Camera2CameraControl} instance associated with the specified {@code + * identifier}. + */ + private Camera2CameraControl getCamera2CameraControlInstance(@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/CameraControlHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java index 9d9778c69211..ba26e91c9622 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlHostApiImpl.java @@ -29,7 +29,7 @@ public class CameraControlHostApiImpl implements CameraControlHostApi { private final InstanceManager instanceManager; private final CameraControlProxy proxy; - /** Proxy for constructors and static method of {@link CameraControl}. */ + /** Proxy for methods of {@link CameraControl}. */ @VisibleForTesting public static class CameraControlProxy { Context context; @@ -99,6 +99,10 @@ public void startFocusAndMetering( @NonNull CameraControl cameraControl, @NonNull FocusMeteringAction focusMeteringAction, @NonNull GeneratedCameraXLibrary.Result result) { + if (context == null) { + throw new IllegalStateException("Context must be set to set zoom ratio."); + } + ListenableFuture focusMeteringResultFuture = cameraControl.startFocusAndMetering(focusMeteringAction); @@ -173,6 +177,7 @@ public void onFailure(Throwable t) { * Constructs an {@link CameraControlHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param context {@link Context} used to retrieve {@code Executor} */ public CameraControlHostApiImpl( @NonNull BinaryMessenger binaryMessenger, @@ -185,8 +190,8 @@ public CameraControlHostApiImpl( * Constructs an {@link CameraControlHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link CameraControl} - * @param context {@link Context} used to retrieve {@code Executor} used to enable torch mode + * @param proxy proxy for methods of {@link CameraControl} + * @param context {@link Context} used to retrieve {@code Executor} */ @VisibleForTesting CameraControlHostApiImpl( @@ -203,11 +208,11 @@ public CameraControlHostApiImpl( } /** - * Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode - * and set the zoom ratio. + * Sets the context that the {@code CameraControl} will use to enable/disable torch mode and set + * the zoom ratio. * - *

If using the camera plugin in an add-to-app context, ensure that a new instance of the - * {@code CameraControl} is fetched via {@code #enableTorch} anytime the context changes. + *

If using the camera plugin in an add-to-app context, ensure that this is called anytime that + * the context changes. */ public void setContext(@NonNull Context context) { this.proxy.context = context; diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java new file mode 100644 index 000000000000..6f8d8c91dda6 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java @@ -0,0 +1,118 @@ +// 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.hardware.camera2.CaptureRequest; +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.annotation.VisibleForTesting; +import androidx.camera.camera2.interop.CaptureRequestOptions; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CaptureRequestKeySupportedType; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CaptureRequestOptionsHostApi; +import java.util.HashMap; +import java.util.Map; + +/** + * Host API implementation for {@link CaptureRequestOptions}. + * + *

This class may handle 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 CaptureRequestOptionsHostApiImpl implements CaptureRequestOptionsHostApi { + private final InstanceManager instanceManager; + private final CaptureRequestOptionsProxy proxy; + + /** Proxy for constructor of {@link CaptureRequestOptions}. */ + @VisibleForTesting + public static class CaptureRequestOptionsProxy { + /** Creates an instance of {@link CaptureRequestOptions}. */ + // Suppression is safe because the type shared between the key and value pairs that + // represent capture request options is checked on the Dart side. + @SuppressWarnings("unchecked") + @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) + public @NonNull CaptureRequestOptions create( + @NonNull Map options) { + CaptureRequestOptions.Builder builder = getCaptureRequestOptionsBuilder(); + + for (Map.Entry option : options.entrySet()) { + CaptureRequestKeySupportedType optionKeyType = option.getKey(); + CaptureRequest.Key optionKey = getCaptureRequestKey(optionKeyType); + Object optionValue = option.getValue(); + + if (optionValue == null) { + builder.clearCaptureRequestOption(optionKey); + continue; + } + + switch (optionKeyType) { + case CONTROL_AE_LOCK: + builder.setCaptureRequestOption( + (CaptureRequest.Key) optionKey, (Boolean) optionValue); + break; + default: + throw new IllegalArgumentException( + "The capture request key " + + optionKeyType.toString() + + "is not currently supported by the plugin."); + } + } + + return builder.build(); + } + + private CaptureRequest.Key getCaptureRequestKey( + CaptureRequestKeySupportedType type) { + CaptureRequest.Key key; + switch (type) { + case CONTROL_AE_LOCK: + key = CaptureRequest.CONTROL_AE_LOCK; + break; + default: + throw new IllegalArgumentException( + "The capture request key is not currently supported by the plugin."); + } + return key; + } + + @VisibleForTesting + @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) + public @NonNull CaptureRequestOptions.Builder getCaptureRequestOptionsBuilder() { + return new CaptureRequestOptions.Builder(); + } + } + + /** + * Constructs a {@link CaptureRequestOptionsHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public CaptureRequestOptionsHostApiImpl(@NonNull InstanceManager instanceManager) { + this(instanceManager, new CaptureRequestOptionsProxy()); + } + + /** + * Constructs a {@link CaptureRequestOptionsHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructor of {@link CaptureRequestOptions} + */ + @VisibleForTesting + CaptureRequestOptionsHostApiImpl( + @NonNull InstanceManager instanceManager, @NonNull CaptureRequestOptionsProxy proxy) { + this.instanceManager = instanceManager; + this.proxy = proxy; + } + + @Override + public void create(@NonNull Long identifier, @NonNull Map options) { + Map decodedOptions = + new HashMap(); + for (Map.Entry option : options.entrySet()) { + decodedOptions.put( + CaptureRequestKeySupportedType.values()[option.getKey().intValue()], option.getValue()); + } + instanceManager.addDartCreatedInstance(proxy.create(decodedOptions), identifier); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java index da9f58a3f719..ae67ed711ca9 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyHostApiImpl.java @@ -23,7 +23,7 @@ public class FallbackStrategyHostApiImpl implements FallbackStrategyHostApi { private final FallbackStrategyProxy proxy; - /** Proxy for constructors and static method of {@link FallbackStrategy}. */ + /** Proxy for constructor of {@link FallbackStrategy}. */ @VisibleForTesting public static class FallbackStrategyProxy { /** Creates an instance of {@link FallbackStrategy}. */ @@ -59,7 +59,7 @@ public FallbackStrategyHostApiImpl(@NonNull InstanceManager instanceManager) { * Constructs a {@link FallbackStrategyHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link FallbackStrategy} + * @param proxy proxy for constructor of {@link FallbackStrategy} */ FallbackStrategyHostApiImpl( @NonNull InstanceManager instanceManager, @NonNull FallbackStrategyProxy proxy) { 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 8c6222ff7b6e..5eeedd15211c 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 @@ -24,7 +24,7 @@ public class FocusMeteringActionHostApiImpl implements FocusMeteringActionHostAp private final FocusMeteringActionProxy proxy; - /** Proxy for constructors and static method of {@link FocusMeteringAction}. */ + /** Proxy for constructor of {@link FocusMeteringAction}. */ @VisibleForTesting public static class FocusMeteringActionProxy { /** Creates an instance of {@link FocusMeteringAction}. */ @@ -90,7 +90,7 @@ public FocusMeteringActionHostApiImpl(@NonNull InstanceManager instanceManager) * Constructs a {@link FocusMeteringActionHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link FocusMeteringAction} + * @param proxy proxy for constructor of {@link FocusMeteringAction} */ FocusMeteringActionHostApiImpl( @NonNull InstanceManager instanceManager, @NonNull FocusMeteringActionProxy proxy) { diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultHostApiImpl.java index 2c6c33e0c268..de4640347604 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultHostApiImpl.java @@ -20,7 +20,7 @@ public class FocusMeteringResultHostApiImpl implements FocusMeteringResultHostAp private final FocusMeteringResultProxy proxy; - /** Proxy for constructors and static method of {@link FocusMeteringResult}. */ + /** Proxy for methods of {@link FocusMeteringResult}. */ @VisibleForTesting public static class FocusMeteringResultProxy { @@ -49,7 +49,7 @@ public FocusMeteringResultHostApiImpl(@NonNull InstanceManager instanceManager) * Constructs a {@link FocusMeteringResultHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link FocusMeteringResult} + * @param proxy proxy for methods of {@link FocusMeteringResult} */ FocusMeteringResultHostApiImpl( @NonNull InstanceManager instanceManager, @NonNull FocusMeteringResultProxy proxy) { 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 0797d7bee8ff..08f8ea2da9ff 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 @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) @@ -145,6 +146,16 @@ private VideoResolutionFallbackRule(final int index) { } } + public enum CaptureRequestKeySupportedType { + CONTROL_AE_LOCK(0); + + final int index; + + private CaptureRequestKeySupportedType(final int index) { + this.index = index; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class ResolutionInfo { private @NonNull Long width; @@ -530,12 +541,12 @@ ArrayList toList() { } /** - * Convenience class for building [FocusMeteringAction] with multiple metering points. + * Convenience class for building [FocusMeteringAction]s with multiple metering points. * *

Generated class from Pigeon that represents data sent in messages. */ public static final class MeteringPointInfo { - /** Instance manager ID corresponding to [MeteringPoint] that relates to this info. */ + /** InstanceManager ID for a [MeteringPoint]. */ private @NonNull Long meteringPointId; public @NonNull Long getMeteringPointId() { @@ -549,7 +560,11 @@ public void setMeteringPointId(@NonNull Long setterArg) { this.meteringPointId = setterArg; } - /** Metering mode represented by one of the [FocusMeteringAction] constants. */ + /** + * The metering mode of the [MeteringPoint] whose ID is [meteringPointId]. + * + *

Metering mode should be one of the [FocusMeteringAction] constants. + */ private @Nullable Long meteringMode; public @Nullable Long getMeteringMode() { @@ -3954,37 +3969,84 @@ static void setup( } } } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ - public interface DisplayOrientedMeteringPointFactoryHostApi { - void create( - @NonNull Long identifier, - @NonNull Long cameraInfoId, - @NonNull Long width, - @NonNull Long height); + private static class CaptureRequestOptionsHostApiCodec extends StandardMessageCodec { + public static final CaptureRequestOptionsHostApiCodec INSTANCE = + new CaptureRequestOptionsHostApiCodec(); - @NonNull - Long createPoint(@NonNull Long x, @NonNull Long y, @Nullable Long size); + private CaptureRequestOptionsHostApiCodec() {} - @NonNull - Long getDefaultPointSize(); + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return CameraPermissionsErrorData.fromList((ArrayList) readValue(buffer)); + case (byte) 129: + return CameraStateTypeData.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + return ExposureCompensationRange.fromList((ArrayList) readValue(buffer)); + case (byte) 131: + return LiveDataSupportedTypeData.fromList((ArrayList) readValue(buffer)); + case (byte) 132: + return MeteringPointInfo.fromList((ArrayList) readValue(buffer)); + case (byte) 133: + return ResolutionInfo.fromList((ArrayList) readValue(buffer)); + case (byte) 134: + return VideoQualityData.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof CameraPermissionsErrorData) { + stream.write(128); + writeValue(stream, ((CameraPermissionsErrorData) value).toList()); + } else if (value instanceof CameraStateTypeData) { + stream.write(129); + writeValue(stream, ((CameraStateTypeData) value).toList()); + } else if (value instanceof ExposureCompensationRange) { + stream.write(130); + writeValue(stream, ((ExposureCompensationRange) value).toList()); + } else if (value instanceof LiveDataSupportedTypeData) { + stream.write(131); + writeValue(stream, ((LiveDataSupportedTypeData) value).toList()); + } else if (value instanceof MeteringPointInfo) { + stream.write(132); + writeValue(stream, ((MeteringPointInfo) value).toList()); + } else if (value instanceof ResolutionInfo) { + stream.write(133); + writeValue(stream, ((ResolutionInfo) value).toList()); + } else if (value instanceof VideoQualityData) { + stream.write(134); + writeValue(stream, ((VideoQualityData) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface CaptureRequestOptionsHostApi { + + void create(@NonNull Long identifier, @NonNull Map options); - /** The codec used by DisplayOrientedMeteringPointFactoryHostApi. */ + /** The codec used by CaptureRequestOptionsHostApi. */ static @NonNull MessageCodec getCodec() { - return new StandardMessageCodec(); + return CaptureRequestOptionsHostApiCodec.INSTANCE; } /** - * Sets up an instance of `DisplayOrientedMeteringPointFactoryHostApi` to handle messages - * through the `binaryMessenger`. + * Sets up an instance of `CaptureRequestOptionsHostApi` to handle messages through the + * `binaryMessenger`. */ static void setup( - @NonNull BinaryMessenger binaryMessenger, - @Nullable DisplayOrientedMeteringPointFactoryHostApi api) { + @NonNull BinaryMessenger binaryMessenger, @Nullable CaptureRequestOptionsHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create", + "dev.flutter.pigeon.CaptureRequestOptionsHostApi.create", getCodec()); if (api != null) { channel.setMessageHandler( @@ -3992,15 +4054,10 @@ static void setup( ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; Number identifierArg = (Number) args.get(0); - Number cameraInfoIdArg = (Number) args.get(1); - Number widthArg = (Number) args.get(2); - Number heightArg = (Number) args.get(3); + Map optionsArg = (Map) args.get(1); try { api.create( - (identifierArg == null) ? null : identifierArg.longValue(), - (cameraInfoIdArg == null) ? null : cameraInfoIdArg.longValue(), - (widthArg == null) ? null : widthArg.longValue(), - (heightArg == null) ? null : heightArg.longValue()); + (identifierArg == null) ? null : identifierArg.longValue(), optionsArg); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -4012,27 +4069,48 @@ static void setup( channel.setMessageHandler(null); } } + } + } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface Camera2CameraControlHostApi { + + void create(@NonNull Long identifier, @NonNull Long cameraControlIdentifier); + + void addCaptureRequestOptions( + @NonNull Long identifier, + @NonNull Long captureRequestOptionsIdentifier, + @NonNull Result result); + + /** The codec used by Camera2CameraControlHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `Camera2CameraControlHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable Camera2CameraControlHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint", + "dev.flutter.pigeon.Camera2CameraControlHostApi.create", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - Number xArg = (Number) args.get(0); - Number yArg = (Number) args.get(1); - Number sizeArg = (Number) args.get(2); + Number identifierArg = (Number) args.get(0); + Number cameraControlIdentifierArg = (Number) args.get(1); try { - Long output = - api.createPoint( - (xArg == null) ? null : xArg.longValue(), - (yArg == null) ? null : yArg.longValue(), - (sizeArg == null) ? null : sizeArg.longValue()); - wrapped.add(0, output); + api.create( + (identifierArg == null) ? null : identifierArg.longValue(), + (cameraControlIdentifierArg == null) + ? null + : cameraControlIdentifierArg.longValue()); + wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; @@ -4047,20 +4125,34 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.getDefaultPointSize", + "dev.flutter.pigeon.Camera2CameraControlHostApi.addCaptureRequestOptions", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); - try { - Long output = api.getDefaultPointSize(); - wrapped.add(0, output); - } catch (Throwable exception) { - ArrayList wrappedError = wrapError(exception); - wrapped = wrappedError; - } - reply.reply(wrapped); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + Number captureRequestOptionsIdentifierArg = (Number) args.get(1); + Result resultCallback = + new Result() { + public void success(Void result) { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.addCaptureRequestOptions( + (identifierArg == null) ? null : identifierArg.longValue(), + (captureRequestOptionsIdentifierArg == null) + ? null + : captureRequestOptionsIdentifierArg.longValue(), + resultCallback); }); } else { channel.setMessageHandler(null); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java index 343fbe731acd..56ffcfb2e391 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java @@ -22,7 +22,7 @@ public class MeteringPointHostApiImpl implements MeteringPointHostApi { private final InstanceManager instanceManager; private final MeteringPointProxy proxy; - /** Proxy for constructors and static method of {@link MeteringPoint}. */ + /** Proxy for constructor and static methods of {@link MeteringPoint}. */ @VisibleForTesting public static class MeteringPointProxy { @@ -72,7 +72,7 @@ public MeteringPointHostApiImpl(@NonNull InstanceManager instanceManager) { * Constructs a {@link MeteringPointHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link MeteringPoint} + * @param proxy proxy for constructor and static methods of {@link MeteringPoint} */ @VisibleForTesting MeteringPointHostApiImpl( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ObserverHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ObserverHostApiImpl.java index 333607e4165e..46ffe6c4b8ee 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ObserverHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ObserverHostApiImpl.java @@ -22,7 +22,7 @@ public class ObserverHostApiImpl implements ObserverHostApi { private final InstanceManager instanceManager; private final ObserverProxy observerProxy; - /** Proxy for constructors and static method of {@link Observer}. */ + /** Proxy for constructor of {@link Observer}. */ @VisibleForTesting public static class ObserverProxy { @@ -77,7 +77,7 @@ public ObserverHostApiImpl( * * @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 constructors and static method of {@link Observer} + * @param proxy proxy for constructor of {@link Observer} */ @VisibleForTesting ObserverHostApiImpl( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java index 675225454c57..87a00e629348 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorHostApiImpl.java @@ -30,7 +30,7 @@ public class QualitySelectorHostApiImpl implements QualitySelectorHostApi { private final QualitySelectorProxy proxy; - /** Proxy for constructors and static method of {@link QualitySelector}. */ + /** Proxy for constructor of {@link QualitySelector}. */ @VisibleForTesting public static class QualitySelectorProxy { /** Creates an instance of {@link QualitySelector}. */ @@ -73,7 +73,7 @@ public QualitySelectorHostApiImpl(@NonNull InstanceManager instanceManager) { * Constructs a {@link QualitySelectorHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link QualitySelector} + * @param proxy proxy for constructor of {@link QualitySelector} */ QualitySelectorHostApiImpl( @NonNull InstanceManager instanceManager, @NonNull QualitySelectorProxy proxy) { diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java index be05e1bb8bef..4aa11cd593f2 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java @@ -23,7 +23,7 @@ public class ResolutionSelectorHostApiImpl implements ResolutionSelectorHostApi private final InstanceManager instanceManager; private final ResolutionSelectorProxy proxy; - /** Proxy for constructors and static method of {@link ResolutionSelector}. */ + /** Proxy for constructor of {@link ResolutionSelector}. */ @VisibleForTesting public static class ResolutionSelectorProxy { /** Creates an instance of {@link ResolutionSelector}. */ @@ -55,7 +55,7 @@ public ResolutionSelectorHostApiImpl(@NonNull InstanceManager instanceManager) { * Constructs a {@link ResolutionSelectorHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link ResolutionSelector} + * @param proxy proxy for constructor of {@link ResolutionSelector} */ @VisibleForTesting ResolutionSelectorHostApiImpl( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java index c110c40446ef..5c93fe6bd238 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java @@ -21,7 +21,7 @@ public class ResolutionStrategyHostApiImpl implements ResolutionStrategyHostApi private final InstanceManager instanceManager; private final ResolutionStrategyProxy proxy; - /** Proxy for constructors and static method of {@link ResolutionStrategy}. */ + /** Proxy for constructor of {@link ResolutionStrategy}. */ @VisibleForTesting public static class ResolutionStrategyProxy { @@ -45,7 +45,7 @@ public ResolutionStrategyHostApiImpl(@NonNull InstanceManager instanceManager) { * Constructs a {@link ResolutionStrategyHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static method of {@link ResolutionStrategy} + * @param proxy proxy for constructor of {@link ResolutionStrategy} */ @VisibleForTesting ResolutionStrategyHostApiImpl( diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraControlTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraControlTest.java new file mode 100644 index 000000000000..bec9d9a22a72 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraControlTest.java @@ -0,0 +1,130 @@ +// 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.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import androidx.camera.camera2.interop.Camera2CameraControl; +import androidx.camera.camera2.interop.CaptureRequestOptions; +import androidx.camera.core.CameraControl; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +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 Camera2CameraControlTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public Camera2CameraControl mockCamera2CameraControl; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.stopFinalizationListener(); + } + + @Test + public void create_createsInstanceFromCameraControlInstance() { + final Camera2CameraControlHostApiImpl hostApi = + new Camera2CameraControlHostApiImpl(testInstanceManager, mock(Context.class)); + final long instanceIdentifier = 40; + final CameraControl mockCameraControl = mock(CameraControl.class); + final long cameraControlIdentifier = 29; + + testInstanceManager.addDartCreatedInstance(mockCameraControl, cameraControlIdentifier); + try (MockedStatic mockedCamera2CameraControl = + Mockito.mockStatic(Camera2CameraControl.class)) { + mockedCamera2CameraControl + .when(() -> Camera2CameraControl.from(mockCameraControl)) + .thenAnswer((Answer) invocation -> mockCamera2CameraControl); + + hostApi.create(instanceIdentifier, cameraControlIdentifier); + assertEquals(testInstanceManager.getInstance(instanceIdentifier), mockCamera2CameraControl); + } + } + + @Test + public void addCaptureRequestOptions_respondsAsExpectedToSuccessfulAndFailedAttempts() { + final Camera2CameraControlHostApiImpl hostApi = + new Camera2CameraControlHostApiImpl(testInstanceManager, mock(Context.class)); + final long instanceIdentifier = 0; + + final CaptureRequestOptions mockCaptureRequestOptions = mock(CaptureRequestOptions.class); + final long captureRequestOptionsIdentifier = 8; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraControl, instanceIdentifier); + testInstanceManager.addDartCreatedInstance( + mockCaptureRequestOptions, captureRequestOptionsIdentifier); + + try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) { + @SuppressWarnings("unchecked") + final ListenableFuture addCaptureRequestOptionsFuture = mock(ListenableFuture.class); + + when(mockCamera2CameraControl.addCaptureRequestOptions(mockCaptureRequestOptions)) + .thenReturn(addCaptureRequestOptionsFuture); + + @SuppressWarnings("unchecked") + final ArgumentCaptor> futureCallbackCaptor = + ArgumentCaptor.forClass(FutureCallback.class); + + // Test successfully adding capture request options. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result successfulMockResult = + mock(GeneratedCameraXLibrary.Result.class); + + hostApi.addCaptureRequestOptions( + instanceIdentifier, captureRequestOptionsIdentifier, successfulMockResult); + mockedFutures.verify( + () -> + Futures.addCallback( + eq(addCaptureRequestOptionsFuture), futureCallbackCaptor.capture(), any())); + mockedFutures.clearInvocations(); + + FutureCallback successfulCallback = futureCallbackCaptor.getValue(); + + successfulCallback.onSuccess(mock(Void.class)); + verify(successfulMockResult).success(null); + + // Test failed attempt to add capture request options. + @SuppressWarnings("unchecked") + final GeneratedCameraXLibrary.Result failedMockResult = + mock(GeneratedCameraXLibrary.Result.class); + final Throwable testThrowable = new Throwable(); + hostApi.addCaptureRequestOptions( + instanceIdentifier, captureRequestOptionsIdentifier, failedMockResult); + mockedFutures.verify( + () -> + Futures.addCallback( + eq(addCaptureRequestOptionsFuture), futureCallbackCaptor.capture(), any())); + + FutureCallback failedCallback = futureCallbackCaptor.getValue(); + + failedCallback.onFailure(testThrowable); + verify(failedMockResult).error(testThrowable); + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java new file mode 100644 index 000000000000..9c3329978d46 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java @@ -0,0 +1,109 @@ +// 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.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.CaptureRequest; +import androidx.camera.camera2.interop.CaptureRequestOptions; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CaptureRequestKeySupportedType; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class CaptureRequestOptionsTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public CaptureRequestOptions mockCaptureRequestOptions; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.stopFinalizationListener(); + } + + @Test + public void create_buildsExpectedCaptureKeyRequestOptionsWhenOptionsNonNull() { + final CaptureRequestOptionsHostApiImpl.CaptureRequestOptionsProxy proxySpy = + spy(new CaptureRequestOptionsHostApiImpl.CaptureRequestOptionsProxy()); + final CaptureRequestOptionsHostApiImpl hostApi = + new CaptureRequestOptionsHostApiImpl(testInstanceManager, proxySpy); + final CaptureRequestOptions.Builder mockBuilder = mock(CaptureRequestOptions.Builder.class); + final long instanceIdentifier = 44; + + // Map between CaptureRequestOptions indices and a test value for that option. + final Map options = + new HashMap() { + { + put(0L, false); + } + }; + + when(proxySpy.getCaptureRequestOptionsBuilder()).thenReturn(mockBuilder); + when(mockBuilder.build()).thenReturn(mockCaptureRequestOptions); + + hostApi.create(instanceIdentifier, options); + for (CaptureRequestKeySupportedType supportedType : CaptureRequestKeySupportedType.values()) { + final Long supportedTypeIndex = Long.valueOf(supportedType.index); + final Object testValueForSupportedType = options.get(supportedTypeIndex); + switch (supportedType) { + case CONTROL_AE_LOCK: + verify(mockBuilder) + .setCaptureRequestOption( + eq(CaptureRequest.CONTROL_AE_LOCK), eq((Boolean) testValueForSupportedType)); + break; + default: + throw new IllegalArgumentException( + "The capture request key is not currently supported by the plugin."); + } + } + + assertEquals(testInstanceManager.getInstance(instanceIdentifier), mockCaptureRequestOptions); + } + + @Test + public void create_buildsExpectedCaptureKeyRequestOptionsWhenAnOptionIsNull() { + final CaptureRequestOptionsHostApiImpl.CaptureRequestOptionsProxy proxySpy = + spy(new CaptureRequestOptionsHostApiImpl.CaptureRequestOptionsProxy()); + final CaptureRequestOptionsHostApiImpl hostApi = + new CaptureRequestOptionsHostApiImpl(testInstanceManager, proxySpy); + final CaptureRequestOptions.Builder mockBuilder = mock(CaptureRequestOptions.Builder.class); + final long instanceIdentifier = 44; + + // Map between CaptureRequestOptions.CONTROL_AE_LOCK index and test value. + final Map options = + new HashMap() { + { + put(0L, null); + } + }; + + when(proxySpy.getCaptureRequestOptionsBuilder()).thenReturn(mockBuilder); + when(mockBuilder.build()).thenReturn(mockCaptureRequestOptions); + + hostApi.create(instanceIdentifier, options); + + verify(mockBuilder).clearCaptureRequestOption(CaptureRequest.CONTROL_AE_LOCK); + + assertEquals(testInstanceManager.getInstance(instanceIdentifier), mockCaptureRequestOptions); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camera2_camera_control.dart b/packages/camera/camera_android_camerax/lib/src/camera2_camera_control.dart new file mode 100644 index 000000000000..87fa9734a5b0 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera2_camera_control.dart @@ -0,0 +1,135 @@ +// 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:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camera_control.dart'; +import 'camerax_library.g.dart'; +import 'capture_request_options.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; +import 'system_services.dart'; + +/// Class that provides ability to interoperate with android.hardware.camera2 +/// APIs and apply options to its specific controls like capture request +/// options. +/// +/// See https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraControl#from(androidx.camera.core.CameraControl). +@immutable +class Camera2CameraControl extends JavaObject { + /// Creates a [Camera2CameraControl]. + Camera2CameraControl( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.cameraControl}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _Camera2CameraControlHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + _api.createFromInstances(this, cameraControl); + } + + /// Constructs a [Camera2CameraControl] that is not automatically attached to a + /// native object. + Camera2CameraControl.detached( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.cameraControl}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _Camera2CameraControlHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + late final _Camera2CameraControlHostApiImpl _api; + + /// The [CameraControl] info that this instance is based on. + /// + /// Note that options specified with this [Camera2CameraControl] instance will + /// have higher priority than [cameraControl]. + final CameraControl cameraControl; + + /// Updates capture session with options that the specified + /// [CaptureRequestOptions] contains. + /// + /// Options will be merged with existing options, and if conflicting with what + /// was previously set, these options will override those pre-existing. Once + /// merged, these values will be submitted with every repeating and single + /// capture request issued by CameraX. + Future addCaptureRequestOptions( + CaptureRequestOptions captureRequestOptions) { + return _api.addCaptureRequestOptionsFromInstances( + this, captureRequestOptions); + } +} + +/// Host API implementation of [Camera2CameraControl]. +class _Camera2CameraControlHostApiImpl extends Camera2CameraControlHostApi { + /// Constructs a [_Camera2CameraControlHostApiImpl]. + /// + /// 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]. + _Camera2CameraControlHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// 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; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Creates a [Camera2CameraControl] instance derived from the specified + /// [CameraControl] instance. + Future createFromInstances( + Camera2CameraControl instance, + CameraControl cameraControl, + ) { + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (Camera2CameraControl original) => + Camera2CameraControl.detached( + cameraControl: original.cameraControl, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + instanceManager.getIdentifier(cameraControl)!, + ); + } + + /// Updates capture session corresponding to the specified + /// [Camera2CameraControl] instance with options that the specified + /// [CaptureRequestOptions] contains. + Future addCaptureRequestOptionsFromInstances( + Camera2CameraControl instance, + CaptureRequestOptions captureRequestOptions, + ) async { + try { + return addCaptureRequestOptions( + instanceManager.getIdentifier(instance)!, + instanceManager.getIdentifier(captureRequestOptions)!, + ); + } on PlatformException catch (e) { + SystemServices.cameraErrorStreamController.add(e.message ?? + 'The camera was unable to set new capture request options due to new options being unavailable or the camera being closed.'); + } + } +} 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 e57dc8222acb..07515c0c14fd 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 @@ -68,6 +68,10 @@ enum VideoResolutionFallbackRule { lowerQualityThan, } +enum CaptureRequestKeySupportedType { + controlAeLock, +} + class ResolutionInfo { ResolutionInfo({ required this.width, @@ -210,7 +214,7 @@ class VideoQualityData { } } -/// Convenience class for building [FocusMeteringAction] with multiple metering +/// Convenience class for building [FocusMeteringAction]s with multiple metering /// points. class MeteringPointInfo { MeteringPointInfo({ @@ -218,11 +222,12 @@ class MeteringPointInfo { this.meteringMode, }); - /// Instance manager ID corresponding to [MeteringPoint] that relates to this - /// info. + /// InstanceManager ID for a [MeteringPoint]. int meteringPointId; - /// Metering mode represented by one of the [FocusMeteringAction] constants. + /// The metering mode of the [MeteringPoint] whose ID is [meteringPointId]. + /// + /// Metering mode should be one of the [FocusMeteringAction] constants. int? meteringMode; Object encode() { @@ -3176,25 +3181,77 @@ class MeteringPointHostApi { } } -class DisplayOrientedMeteringPointFactoryHostApi { - /// Constructor for [DisplayOrientedMeteringPointFactoryHostApi]. The [binaryMessenger] named argument is +class _CaptureRequestOptionsHostApiCodec extends StandardMessageCodec { + const _CaptureRequestOptionsHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is CameraPermissionsErrorData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is CameraStateTypeData) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is ExposureCompensationRange) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is LiveDataSupportedTypeData) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is MeteringPointInfo) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else if (value is ResolutionInfo) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is VideoQualityData) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return CameraPermissionsErrorData.decode(readValue(buffer)!); + case 129: + return CameraStateTypeData.decode(readValue(buffer)!); + case 130: + return ExposureCompensationRange.decode(readValue(buffer)!); + case 131: + return LiveDataSupportedTypeData.decode(readValue(buffer)!); + case 132: + return MeteringPointInfo.decode(readValue(buffer)!); + case 133: + return ResolutionInfo.decode(readValue(buffer)!); + case 134: + return VideoQualityData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class CaptureRequestOptionsHostApi { + /// Constructor for [CaptureRequestOptionsHostApi]. 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. - DisplayOrientedMeteringPointFactoryHostApi({BinaryMessenger? binaryMessenger}) + CaptureRequestOptionsHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec codec = + _CaptureRequestOptionsHostApiCodec(); - Future create(int arg_identifier, int arg_cameraInfoId, int arg_width, - int arg_height) async { + Future create( + int arg_identifier, Map arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create', - codec, + 'dev.flutter.pigeon.CaptureRequestOptionsHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send( - [arg_identifier, arg_cameraInfoId, arg_width, arg_height]) - as List?; + final List? replyList = await channel + .send([arg_identifier, arg_options]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -3210,14 +3267,26 @@ class DisplayOrientedMeteringPointFactoryHostApi { return; } } +} - Future createPoint(int arg_x, int arg_y, int? arg_size) async { +class Camera2CameraControlHostApi { + /// Constructor for [Camera2CameraControlHostApi]. 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. + Camera2CameraControlHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future create( + int arg_identifier, int arg_cameraControlIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint', - codec, + 'dev.flutter.pigeon.Camera2CameraControlHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_x, arg_y, arg_size]) as List?; + final List? replyList = await channel + .send([arg_identifier, arg_cameraControlIdentifier]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -3229,22 +3298,20 @@ class DisplayOrientedMeteringPointFactoryHostApi { 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?)!; + return; } } - Future getDefaultPointSize() async { + Future addCaptureRequestOptions( + int arg_identifier, int arg_captureRequestOptionsIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.getDefaultPointSize', + 'dev.flutter.pigeon.Camera2CameraControlHostApi.addCaptureRequestOptions', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = await channel.send( + [arg_identifier, arg_captureRequestOptionsIdentifier]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -3256,13 +3323,8 @@ class DisplayOrientedMeteringPointFactoryHostApi { 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?)!; + return; } } } diff --git a/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart b/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart new file mode 100644 index 000000000000..777d4a43370f --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart @@ -0,0 +1,139 @@ +// 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:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// A bundle of Camera2 capture request options. +/// +/// See https://developer.android.com/reference/androidx/camera/camera2/interop/CaptureRequestOptions. +@immutable +class CaptureRequestOptions extends JavaObject { + /// Creates a [CaptureRequestOptions]. + /// + /// Any value specified as null for a particular + /// [CaptureRequestKeySupportedType] key will clear the pre-existing value. + CaptureRequestOptions({ + BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.requestedOptions, + }) : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _CaptureRequestOptionsHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + _api.createFromInstances(this, requestedOptions); + } + + /// Constructs a [CaptureRequestOptions] that is not automatically attached to a + /// native object. + CaptureRequestOptions.detached({ + BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.requestedOptions, + }) : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _CaptureRequestOptionsHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + } + + late final _CaptureRequestOptionsHostApiImpl _api; + + /// Capture request options this instance will be used to request. + final List<(CaptureRequestKeySupportedType type, Object? value)> + requestedOptions; + + /// Error message indicating a [CaptureRequestOption] was constructed with a + /// capture request key currently unsupported by the wrapping of this class. + static String getUnsupportedCaptureRequestKeyTypeErrorMessage( + CaptureRequestKeySupportedType captureRequestKeyType) => + 'The type of capture request key passed to this method ($captureRequestKeyType) is current unspported; please see CaptureRequestKeySupportedType in pigeons/camerax_library.dart if you wish to support a new type.'; +} + +/// Host API implementation of [CaptureRequestOptions]. +class _CaptureRequestOptionsHostApiImpl extends CaptureRequestOptionsHostApi { + /// Constructs a [_CaptureRequestOptionsHostApiImpl]. + /// + /// 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]. + _CaptureRequestOptionsHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// 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; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Creates a [CaptureRequestOptions] instance based on the specified + /// capture request key and value pairs. + Future createFromInstances( + CaptureRequestOptions instance, + List<(CaptureRequestKeySupportedType type, Object? value)> options, + ) { + if (options.isEmpty) { + throw ArgumentError( + 'At least one capture request option must be specified.'); + } + + final Map captureRequestOptions = {}; + + // Validate values have type that matches paired key that is supported by + // this plugin (CaptureRequestKeySupportedType). + for (final (CaptureRequestKeySupportedType key, Object? value) option + in options) { + final CaptureRequestKeySupportedType key = option.$1; + final Object? value = option.$2; + if (value == null) { + captureRequestOptions[key.index] = null; + continue; + } + + final Type valueRuntimeType = value.runtimeType; + switch (key) { + case CaptureRequestKeySupportedType.controlAeLock: + if (valueRuntimeType != bool) { + throw ArgumentError( + 'A controlAeLock value must be specified as a bool, but a $valueRuntimeType was specified.'); + } + // This ignore statement is safe beause this error will be useful when + // a new CaptureRequestKeySupportedType is being added, but the logic in + // this method has not yet been updated. + // ignore: no_default_cases + default: + throw ArgumentError(CaptureRequestOptions + .getUnsupportedCaptureRequestKeyTypeErrorMessage(key)); + } + + captureRequestOptions[key.index] = value; + } + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (CaptureRequestOptions original) => + CaptureRequestOptions.detached( + requestedOptions: original.requestedOptions, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + captureRequestOptions, + ); + } +} diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 7fbc7e1abaf5..db6164044d30 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -73,7 +73,7 @@ class CameraStateTypeData { /// /// On the native side, ensure the following is done: /// -/// * Update `LiveDataHostApiImpl#getValue` is updated to properly return +/// * Make sure `LiveDataHostApiImpl#getValue` is updated to properly return /// identifiers for instances of type S. /// * Update `ObserverFlutterApiWrapper#onChanged` to properly handle receiving /// calls with instances of type S if a LiveData instance is observed. @@ -143,6 +143,28 @@ class MeteringPointInfo { int? meteringMode; } +/// The types of capture request options this plugin currently supports. +/// +/// If you need to add another option to support, ensure the following is done +/// on the Dart side: +/// +/// * In `../lib/src/capture_request_options.dart`, add new cases for this +/// option in `_CaptureRequestOptionsHostApiImpl#createFromInstances` +/// to create the expected Map entry of option key index and value to send to +/// the native side. +/// +/// On the native side, ensure the following is done: +/// +/// * Update `CaptureRequestOptionsHostApiImpl#create` to set the correct +/// `CaptureRequest` key with a valid value type for this option. +/// +/// See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest +/// for the sorts of capture request options that can be supported via CameraX's +/// interoperability with Camera2. +enum CaptureRequestKeySupportedType { + controlAeLock, +} + @HostApi(dartHostTestHandler: 'TestInstanceManagerHostApi') abstract class InstanceManagerHostApi { /// Clear the native `InstanceManager`. @@ -498,3 +520,17 @@ abstract class MeteringPointHostApi { double getDefaultPointSize(); } + +@HostApi(dartHostTestHandler: 'TestCaptureRequestOptionsHostApi') +abstract class CaptureRequestOptionsHostApi { + void create(int identifier, Map options); +} + +@HostApi(dartHostTestHandler: 'TestCamera2CameraControlHostApi') +abstract class Camera2CameraControlHostApi { + void create(int identifier, int cameraControlIdentifier); + + @async + void addCaptureRequestOptions( + int identifier, int captureRequestOptionsIdentifier); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 8f34768cb96d..f40f6a3d55d9 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+30 +version: 0.5.0+31 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.dart b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.dart new file mode 100644 index 000000000000..6ec783a727c2 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.dart @@ -0,0 +1,126 @@ +// 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_control.dart'; +import 'package:camera_android_camerax/src/camera_control.dart'; +import 'package:camera_android_camerax/src/capture_request_options.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_control_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + CameraControl, + CaptureRequestOptions, + TestCamera2CameraControlHostApi, + TestInstanceManagerHostApi +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('Camera2CameraControl', () { + tearDown(() { + TestCamera2CameraControlHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test('detached create does not call create on the Java side', () { + final MockTestCamera2CameraControlHostApi mockApi = + MockTestCamera2CameraControlHostApi(); + TestCamera2CameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + Camera2CameraControl.detached( + cameraControl: MockCameraControl(), + instanceManager: instanceManager, + ); + + verifyNever(mockApi.create(argThat(isA()), argThat(isA()))); + }); + + test('create calls create on the Java side', () { + final MockTestCamera2CameraControlHostApi mockApi = + MockTestCamera2CameraControlHostApi(); + TestCamera2CameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraControl mockCameraControl = MockCameraControl(); + const int cameraControlIdentifier = 9; + instanceManager.addHostCreatedInstance( + mockCameraControl, + cameraControlIdentifier, + onCopy: (_) => CameraControl.detached( + instanceManager: instanceManager, + ), + ); + + final Camera2CameraControl instance = Camera2CameraControl( + cameraControl: mockCameraControl, + instanceManager: instanceManager, + ); + + verify(mockApi.create( + instanceManager.getIdentifier(instance), + cameraControlIdentifier, + )); + }); + + test( + 'addCaptureRequestOptions makes call on Java side to add capture request options', + () async { + final MockTestCamera2CameraControlHostApi mockApi = + MockTestCamera2CameraControlHostApi(); + TestCamera2CameraControlHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final Camera2CameraControl instance = Camera2CameraControl.detached( + cameraControl: MockCameraControl(), + instanceManager: instanceManager, + ); + const int instanceIdentifier = 30; + instanceManager.addHostCreatedInstance( + instance, + instanceIdentifier, + onCopy: (Camera2CameraControl original) => + Camera2CameraControl.detached( + cameraControl: original.cameraControl, + instanceManager: instanceManager, + ), + ); + + final CaptureRequestOptions mockCaptureRequestOptions = + MockCaptureRequestOptions(); + const int mockCaptureRequestOptionsIdentifier = 8; + instanceManager.addHostCreatedInstance( + mockCaptureRequestOptions, + mockCaptureRequestOptionsIdentifier, + onCopy: (_) => MockCaptureRequestOptions(), + ); + + await instance.addCaptureRequestOptions( + mockCaptureRequestOptions, + ); + + verify(mockApi.addCaptureRequestOptions( + instanceIdentifier, + mockCaptureRequestOptionsIdentifier, + )); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart new file mode 100644 index 000000000000..09eb9436c7ad --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart @@ -0,0 +1,148 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in camera_android_camerax/test/camera2_camera_control_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:camera_android_camerax/src/camera_control.dart' as _i2; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i5; +import 'package:camera_android_camerax/src/capture_request_options.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i6; + +// 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 + +/// 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 _i2.CameraControl { + MockCameraControl() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future enableTorch(bool? torch) => (super.noSuchMethod( + Invocation.method( + #enableTorch, + [torch], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setZoomRatio(double? ratio) => (super.noSuchMethod( + Invocation.method( + #setZoomRatio, + [ratio], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} + +/// A class which mocks [CaptureRequestOptions]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCaptureRequestOptions extends _i1.Mock + implements _i4.CaptureRequestOptions { + MockCaptureRequestOptions() { + _i1.throwOnMissingStub(this); + } + + @override + List<(_i5.CaptureRequestKeySupportedType, dynamic)> get requestedOptions => + (super.noSuchMethod( + Invocation.getter(#requestedOptions), + returnValue: <(_i5.CaptureRequestKeySupportedType, dynamic)>[], + ) as List<(_i5.CaptureRequestKeySupportedType, dynamic)>); + + @override + set requestedOptions( + List<(_i5.CaptureRequestKeySupportedType, dynamic)>? + _requestedOptions) => + super.noSuchMethod( + Invocation.setter( + #requestedOptions, + _requestedOptions, + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [TestCamera2CameraControlHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestCamera2CameraControlHostApi extends _i1.Mock + implements _i6.TestCamera2CameraControlHostApi { + MockTestCamera2CameraControlHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + int? cameraControlIdentifier, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + cameraControlIdentifier, + ], + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future addCaptureRequestOptions( + int? identifier, + int? captureRequestOptionsIdentifier, + ) => + (super.noSuchMethod( + Invocation.method( + #addCaptureRequestOptions, + [ + identifier, + captureRequestOptionsIdentifier, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i6.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/capture_request_options_test.dart b/packages/camera/camera_android_camerax/test/capture_request_options_test.dart new file mode 100644 index 000000000000..3649369c934b --- /dev/null +++ b/packages/camera/camera_android_camerax/test/capture_request_options_test.dart @@ -0,0 +1,143 @@ +// 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/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/capture_request_options.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 'capture_request_options_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks( + [TestCaptureRequestOptionsHostApi, TestInstanceManagerHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('CaptureRequestOptions', () { + tearDown(() { + TestCaptureRequestOptionsHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test('detached create does not make call on the Java side', () { + final MockTestCaptureRequestOptionsHostApi mockApi = + MockTestCaptureRequestOptionsHostApi(); + TestCaptureRequestOptionsHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final List<(CaptureRequestKeySupportedType, Object?)> options = + <(CaptureRequestKeySupportedType, Object?)>[ + (CaptureRequestKeySupportedType.controlAeLock, true), + ]; + + CaptureRequestOptions.detached( + requestedOptions: options, + instanceManager: instanceManager, + ); + + verifyNever(mockApi.create( + argThat(isA()), + argThat(isA>()), + )); + }); + + test( + 'create makes call on the Java side as expected for suppported null capture request options', + () { + final MockTestCaptureRequestOptionsHostApi mockApi = + MockTestCaptureRequestOptionsHostApi(); + TestCaptureRequestOptionsHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final List<(CaptureRequestKeySupportedType key, Object? value)> + supportedOptionsForTesting = <( + CaptureRequestKeySupportedType key, + Object? value + )>[(CaptureRequestKeySupportedType.controlAeLock, null)]; + + final CaptureRequestOptions instance = CaptureRequestOptions( + requestedOptions: supportedOptionsForTesting, + instanceManager: instanceManager, + ); + + final VerificationResult verificationResult = verify(mockApi.create( + instanceManager.getIdentifier(instance), + captureAny, + )); + final Map captureRequestOptions = + verificationResult.captured.single as Map; + + expect(captureRequestOptions.length, + equals(supportedOptionsForTesting.length)); + for (final (CaptureRequestKeySupportedType key, Object? value) option + in supportedOptionsForTesting) { + final CaptureRequestKeySupportedType optionKey = option.$1; + expect(captureRequestOptions[optionKey.index], isNull); + } + }); + + test( + 'create makes call on the Java side as expected for suppported non-null capture request options', + () { + final MockTestCaptureRequestOptionsHostApi mockApi = + MockTestCaptureRequestOptionsHostApi(); + TestCaptureRequestOptionsHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final List<(CaptureRequestKeySupportedType key, Object? value)> + supportedOptionsForTesting = <( + CaptureRequestKeySupportedType key, + Object? value + )>[(CaptureRequestKeySupportedType.controlAeLock, false)]; + + final CaptureRequestOptions instance = CaptureRequestOptions( + requestedOptions: supportedOptionsForTesting, + instanceManager: instanceManager, + ); + + final VerificationResult verificationResult = verify(mockApi.create( + instanceManager.getIdentifier(instance), + captureAny, + )); + final Map? captureRequestOptions = + verificationResult.captured.single as Map?; + + expect(captureRequestOptions!.length, + equals(supportedOptionsForTesting.length)); + for (final (CaptureRequestKeySupportedType key, Object? value) option + in supportedOptionsForTesting) { + final CaptureRequestKeySupportedType optionKey = option.$1; + final Object? optionValue = option.$2; + + switch (optionKey) { + case CaptureRequestKeySupportedType.controlAeLock: + expect(captureRequestOptions[optionKey.index], + equals(optionValue! as bool)); + // This ignore statement is safe beause this will test when + // a new CaptureRequestKeySupportedType is being added, but the logic in + // in the CaptureRequestOptions class has not yet been updated. + // ignore: no_default_cases + default: + fail( + 'Option $option contains unrecognized CaptureRequestKeySupportedType key ${option.$1}'); + } + } + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/capture_request_options_test.mocks.dart b/packages/camera/camera_android_camerax/test/capture_request_options_test.mocks.dart new file mode 100644 index 000000000000..da704f37cb99 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/capture_request_options_test.mocks.dart @@ -0,0 +1,66 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in camera_android_camerax/test/capture_request_options_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i2; + +// 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 + +/// A class which mocks [TestCaptureRequestOptionsHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestCaptureRequestOptionsHostApi extends _i1.Mock + implements _i2.TestCaptureRequestOptionsHostApi { + MockTestCaptureRequestOptionsHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + Map? options, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + options, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i2.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + 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 abb6fad29877..4450f97c82d5 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 @@ -2193,23 +2193,72 @@ abstract class TestMeteringPointHostApi { } } -abstract class TestDisplayOrientedMeteringPointFactoryHostApi { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = StandardMessageCodec(); +class _TestCaptureRequestOptionsHostApiCodec extends StandardMessageCodec { + const _TestCaptureRequestOptionsHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is CameraPermissionsErrorData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is CameraStateTypeData) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is ExposureCompensationRange) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is LiveDataSupportedTypeData) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is MeteringPointInfo) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else if (value is ResolutionInfo) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is VideoQualityData) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } - void create(int identifier, int cameraInfoId, int width, int height); + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return CameraPermissionsErrorData.decode(readValue(buffer)!); + case 129: + return CameraStateTypeData.decode(readValue(buffer)!); + case 130: + return ExposureCompensationRange.decode(readValue(buffer)!); + case 131: + return LiveDataSupportedTypeData.decode(readValue(buffer)!); + case 132: + return MeteringPointInfo.decode(readValue(buffer)!); + case 133: + return ResolutionInfo.decode(readValue(buffer)!); + case 134: + return VideoQualityData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} - int createPoint(int x, int y, int? size); +abstract class TestCaptureRequestOptionsHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = + _TestCaptureRequestOptionsHostApiCodec(); - int getDefaultPointSize(); + void create(int identifier, Map options); - static void setup(TestDisplayOrientedMeteringPointFactoryHostApi? api, + static void setup(TestCaptureRequestOptionsHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create', - codec, + 'dev.flutter.pigeon.CaptureRequestOptionsHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -2219,30 +2268,38 @@ abstract class TestDisplayOrientedMeteringPointFactoryHostApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create was null.'); + 'Argument for dev.flutter.pigeon.CaptureRequestOptionsHostApi.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.DisplayOrientedMeteringPointFactoryHostApi.create was null, expected non-null int.'); - final int? arg_cameraInfoId = (args[1] as int?); - assert(arg_cameraInfoId != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create was null, expected non-null int.'); - final int? arg_width = (args[2] as int?); - assert(arg_width != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create was null, expected non-null int.'); - final int? arg_height = (args[3] as int?); - assert(arg_height != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create was null, expected non-null int.'); - api.create( - arg_identifier!, arg_cameraInfoId!, arg_width!, arg_height!); + 'Argument for dev.flutter.pigeon.CaptureRequestOptionsHostApi.create was null, expected non-null int.'); + final Map? arg_options = + (args[1] as Map?)?.cast(); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.CaptureRequestOptionsHostApi.create was null, expected non-null Map.'); + api.create(arg_identifier!, arg_options!); return []; }); } } + } +} + +abstract class TestCamera2CameraControlHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier, int cameraControlIdentifier); + + Future addCaptureRequestOptions( + int identifier, int captureRequestOptionsIdentifier); + + static void setup(TestCamera2CameraControlHostApi? api, + {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint', - codec, + 'dev.flutter.pigeon.Camera2CameraControlHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger @@ -2252,23 +2309,22 @@ abstract class TestDisplayOrientedMeteringPointFactoryHostApi { .setMockDecodedMessageHandler(channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint was null.'); + 'Argument for dev.flutter.pigeon.Camera2CameraControlHostApi.create was null.'); final List args = (message as List?)!; - final int? arg_x = (args[0] as int?); - assert(arg_x != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint was null, expected non-null int.'); - final int? arg_y = (args[1] as int?); - assert(arg_y != null, - 'Argument for dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint was null, expected non-null int.'); - final int? arg_size = (args[2] as int?); - final int output = api.createPoint(arg_x!, arg_y!, arg_size); - return [output]; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraControlHostApi.create was null, expected non-null int.'); + final int? arg_cameraControlIdentifier = (args[1] as int?); + assert(arg_cameraControlIdentifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraControlHostApi.create was null, expected non-null int.'); + api.create(arg_identifier!, arg_cameraControlIdentifier!); + return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.getDefaultPointSize', + 'dev.flutter.pigeon.Camera2CameraControlHostApi.addCaptureRequestOptions', codec, binaryMessenger: binaryMessenger); if (api == null) { @@ -2278,9 +2334,18 @@ abstract class TestDisplayOrientedMeteringPointFactoryHostApi { _testBinaryMessengerBinding!.defaultBinaryMessenger .setMockDecodedMessageHandler(channel, (Object? message) async { - // ignore message - final int output = api.getDefaultPointSize(); - return [output]; + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraControlHostApi.addCaptureRequestOptions 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.Camera2CameraControlHostApi.addCaptureRequestOptions was null, expected non-null int.'); + final int? arg_captureRequestOptionsIdentifier = (args[1] as int?); + assert(arg_captureRequestOptionsIdentifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraControlHostApi.addCaptureRequestOptions was null, expected non-null int.'); + await api.addCaptureRequestOptions( + arg_identifier!, arg_captureRequestOptionsIdentifier!); + return []; }); } }