diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index 68b090e7bfb..7c1a948dc08 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.5.0+28
+* Wraps CameraX classes needed to implement setting focus and exposure points and exposure offset.
* Updates compileSdk version to 34.
## 0.5.0+27
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java
index 6781e85212e..b0e0493cbe2 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java
@@ -116,7 +116,8 @@ public void setUp(
binaryMessenger, new FallbackStrategyHostApiImpl(instanceManager));
GeneratedCameraXLibrary.QualitySelectorHostApi.setup(
binaryMessenger, new QualitySelectorHostApiImpl(instanceManager));
- cameraControlHostApiImpl = new CameraControlHostApiImpl(instanceManager, context);
+ cameraControlHostApiImpl =
+ new CameraControlHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
}
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 2524d503af4..9d9778c6921 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
@@ -8,11 +8,15 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.CameraControl;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringResult;
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.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraControlHostApi;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result;
import java.util.Objects;
/**
@@ -29,6 +33,8 @@ public class CameraControlHostApiImpl implements CameraControlHostApi {
@VisibleForTesting
public static class CameraControlProxy {
Context context;
+ BinaryMessenger binaryMessenger;
+ InstanceManager instanceManager;
/** Enables or disables the torch of the specified {@link CameraControl} instance. */
@NonNull
@@ -82,6 +88,85 @@ public void onFailure(Throwable t) {
},
ContextCompat.getMainExecutor(context));
}
+
+ /**
+ * Starts a focus and metering action configured by the {@code FocusMeteringAction}.
+ *
+ *
Will trigger an auto focus action and enable auto focus/auto exposure/auto white balance
+ * metering regions.
+ */
+ public void startFocusAndMetering(
+ @NonNull CameraControl cameraControl,
+ @NonNull FocusMeteringAction focusMeteringAction,
+ @NonNull GeneratedCameraXLibrary.Result result) {
+ ListenableFuture focusMeteringResultFuture =
+ cameraControl.startFocusAndMetering(focusMeteringAction);
+
+ Futures.addCallback(
+ focusMeteringResultFuture,
+ new FutureCallback() {
+ public void onSuccess(FocusMeteringResult focusMeteringResult) {
+ final FocusMeteringResultFlutterApiImpl flutterApi =
+ new FocusMeteringResultFlutterApiImpl(binaryMessenger, instanceManager);
+ flutterApi.create(focusMeteringResult, reply -> {});
+ result.success(instanceManager.getIdentifierForStrongReference(focusMeteringResult));
+ }
+
+ public void onFailure(Throwable t) {
+ result.error(t);
+ }
+ },
+ ContextCompat.getMainExecutor(context));
+ }
+
+ /**
+ * Cancels current {@code FocusMeteringAction} and clears auto focus/auto exposure/auto white
+ * balance regions.
+ */
+ public void cancelFocusAndMetering(
+ @NonNull CameraControl cameraControl, @NonNull Result result) {
+ ListenableFuture cancelFocusAndMeteringFuture = cameraControl.cancelFocusAndMetering();
+
+ Futures.addCallback(
+ cancelFocusAndMeteringFuture,
+ new FutureCallback() {
+ public void onSuccess(Void voidResult) {
+ result.success(null);
+ }
+
+ public void onFailure(Throwable t) {
+ result.error(t);
+ }
+ },
+ ContextCompat.getMainExecutor(context));
+ }
+
+ /**
+ * Sets the exposure compensation index for the specified {@link CameraControl} instance and
+ * returns the new target exposure value.
+ *
+ * The exposure compensation value set on the camera must be within the range of {@code
+ * ExposureState#getExposureCompensationRange()} for the current {@code ExposureState} for the
+ * call to succeed.
+ */
+ public void setExposureCompensationIndex(
+ @NonNull CameraControl cameraControl, @NonNull Long index, @NonNull Result result) {
+ ListenableFuture setExposureCompensationIndexFuture =
+ cameraControl.setExposureCompensationIndex(index.intValue());
+
+ Futures.addCallback(
+ setExposureCompensationIndexFuture,
+ new FutureCallback() {
+ public void onSuccess(Integer integerResult) {
+ result.success(integerResult.longValue());
+ }
+
+ public void onFailure(Throwable t) {
+ result.error(t);
+ }
+ },
+ ContextCompat.getMainExecutor(context));
+ }
}
/**
@@ -90,8 +175,10 @@ public void onFailure(Throwable t) {
* @param instanceManager maintains instances stored to communicate with attached Dart objects
*/
public CameraControlHostApiImpl(
- @NonNull InstanceManager instanceManager, @NonNull Context context) {
- this(instanceManager, new CameraControlProxy(), context);
+ @NonNull BinaryMessenger binaryMessenger,
+ @NonNull InstanceManager instanceManager,
+ @NonNull Context context) {
+ this(binaryMessenger, instanceManager, new CameraControlProxy(), context);
}
/**
@@ -103,12 +190,16 @@ public CameraControlHostApiImpl(
*/
@VisibleForTesting
CameraControlHostApiImpl(
+ @NonNull BinaryMessenger binaryMessenger,
@NonNull InstanceManager instanceManager,
@NonNull CameraControlProxy proxy,
@NonNull Context context) {
this.instanceManager = instanceManager;
this.proxy = proxy;
proxy.context = context;
+ // proxy.startFocusAndMetering needs to access these to create a FocusMeteringResult when it becomes available:
+ proxy.instanceManager = instanceManager;
+ proxy.binaryMessenger = binaryMessenger;
}
/**
@@ -127,8 +218,7 @@ public void enableTorch(
@NonNull Long identifier,
@NonNull Boolean torch,
@NonNull GeneratedCameraXLibrary.Result result) {
- proxy.enableTorch(
- Objects.requireNonNull(instanceManager.getInstance(identifier)), torch, result);
+ proxy.enableTorch(getCameraControlInstance(identifier), torch, result);
}
@Override
@@ -136,7 +226,30 @@ public void setZoomRatio(
@NonNull Long identifier,
@NonNull Double ratio,
@NonNull GeneratedCameraXLibrary.Result result) {
- proxy.setZoomRatio(
- Objects.requireNonNull(instanceManager.getInstance(identifier)), ratio, result);
+ proxy.setZoomRatio(getCameraControlInstance(identifier), ratio, result);
+ }
+
+ @Override
+ public void startFocusAndMetering(
+ @NonNull Long identifier, @NonNull Long focusMeteringActionId, @NonNull Result result) {
+ proxy.startFocusAndMetering(
+ getCameraControlInstance(identifier),
+ Objects.requireNonNull(instanceManager.getInstance(focusMeteringActionId)),
+ result);
+ }
+
+ @Override
+ public void cancelFocusAndMetering(@NonNull Long identifier, @NonNull Result result) {
+ proxy.cancelFocusAndMetering(getCameraControlInstance(identifier), result);
+ }
+
+ @Override
+ public void setExposureCompensationIndex(
+ @NonNull Long identifier, @NonNull Long index, @NonNull Result result) {
+ proxy.setExposureCompensationIndex(getCameraControlInstance(identifier), index, result);
+ }
+
+ private CameraControl getCameraControlInstance(@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/FocusMeteringActionHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java
new file mode 100644
index 00000000000..8c6222ff7b6
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionHostApiImpl.java
@@ -0,0 +1,115 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camerax;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.MeteringPoint;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FocusMeteringActionHostApi;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.MeteringPointInfo;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Host API implementation for {@link FocusMeteringAction}.
+ *
+ * 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 FocusMeteringActionHostApiImpl implements FocusMeteringActionHostApi {
+ private final InstanceManager instanceManager;
+
+ private final FocusMeteringActionProxy proxy;
+
+ /** Proxy for constructors and static method of {@link FocusMeteringAction}. */
+ @VisibleForTesting
+ public static class FocusMeteringActionProxy {
+ /** Creates an instance of {@link FocusMeteringAction}. */
+ public @NonNull FocusMeteringAction create(
+ @NonNull List meteringPoints, @NonNull List meteringPointModes) {
+ if (meteringPoints.size() >= 1 && meteringPoints.size() != meteringPointModes.size()) {
+ throw new IllegalArgumentException(
+ "One metering point must be specified and the number of specified metering points must match the number of specified metering point modes.");
+ }
+
+ FocusMeteringAction.Builder focusMeteringActionBuilder;
+
+ // Create builder to potentially add more MeteringPoints to.
+ MeteringPoint firstMeteringPoint = meteringPoints.get(0);
+ Integer firstMeteringPointMode = meteringPointModes.get(0);
+ if (firstMeteringPointMode == null) {
+ focusMeteringActionBuilder = getFocusMeteringActionBuilder(firstMeteringPoint);
+ } else {
+ focusMeteringActionBuilder =
+ getFocusMeteringActionBuilder(firstMeteringPoint, firstMeteringPointMode);
+ }
+
+ // Add any additional metering points in order as specified by input lists.
+ for (int i = 1; i < meteringPoints.size(); i++) {
+ MeteringPoint meteringPoint = meteringPoints.get(i);
+ Integer meteringMode = meteringPointModes.get(i);
+
+ if (meteringMode == null) {
+ focusMeteringActionBuilder.addPoint(meteringPoint);
+ } else {
+ focusMeteringActionBuilder.addPoint(meteringPoint, meteringMode);
+ }
+ }
+
+ return focusMeteringActionBuilder.build();
+ }
+
+ @VisibleForTesting
+ @NonNull
+ public FocusMeteringAction.Builder getFocusMeteringActionBuilder(
+ @NonNull MeteringPoint meteringPoint) {
+ return new FocusMeteringAction.Builder(meteringPoint);
+ }
+
+ @VisibleForTesting
+ @NonNull
+ public FocusMeteringAction.Builder getFocusMeteringActionBuilder(
+ @NonNull MeteringPoint meteringPoint, int meteringMode) {
+ return new FocusMeteringAction.Builder(meteringPoint, meteringMode);
+ }
+ }
+
+ /**
+ * Constructs a {@link FocusMeteringActionHostApiImpl}.
+ *
+ * @param instanceManager maintains instances stored to communicate with attached Dart objects
+ */
+ public FocusMeteringActionHostApiImpl(@NonNull InstanceManager instanceManager) {
+ this(instanceManager, new FocusMeteringActionProxy());
+ }
+
+ /**
+ * 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}
+ */
+ FocusMeteringActionHostApiImpl(
+ @NonNull InstanceManager instanceManager, @NonNull FocusMeteringActionProxy proxy) {
+ this.instanceManager = instanceManager;
+ this.proxy = proxy;
+ }
+
+ @Override
+ public void create(
+ @NonNull Long identifier, @NonNull List meteringPointInfos) {
+ final List meteringPoints = new ArrayList();
+ final List meteringPointModes = new ArrayList();
+ for (MeteringPointInfo meteringPointInfo : meteringPointInfos) {
+ meteringPoints.add(instanceManager.getInstance(meteringPointInfo.getMeteringPointId()));
+ Long meteringPointMode = meteringPointInfo.getMeteringMode();
+ meteringPointModes.add(meteringPointMode == null ? null : meteringPointMode.intValue());
+ }
+
+ instanceManager.addDartCreatedInstance(
+ proxy.create(meteringPoints, meteringPointModes), identifier);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultFlutterApiImpl.java
new file mode 100644
index 00000000000..3c0ed4c4482
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultFlutterApiImpl.java
@@ -0,0 +1,55 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camerax;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.FocusMeteringResult;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FocusMeteringResultFlutterApi;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FocusMeteringResultFlutterApi.Reply;
+
+/**
+ * Flutter API implementation for {@link FocusMeteringResult}.
+ *
+ * This class may handle adding native instances that are attached to a Dart instance or passing
+ * arguments of callbacks methods to a Dart instance.
+ */
+public class FocusMeteringResultFlutterApiImpl {
+ private final BinaryMessenger binaryMessenger;
+ private final InstanceManager instanceManager;
+ private FocusMeteringResultFlutterApi focusMeteringResultFlutterApi;
+
+ /**
+ * Constructs a {@link FocusMeteringResultFlutterApiImpl}.
+ *
+ * @param binaryMessenger used to communicate with Dart over asynchronous messages
+ * @param instanceManager maintains instances stored to communicate with attached Dart objects
+ */
+ public FocusMeteringResultFlutterApiImpl(
+ @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
+ this.binaryMessenger = binaryMessenger;
+ this.instanceManager = instanceManager;
+ focusMeteringResultFlutterApi = new FocusMeteringResultFlutterApi(binaryMessenger);
+ }
+
+ /**
+ * Stores the {@link FocusMeteringResult} instance and notifies Dart to create and store a new
+ * {@link FocusMeteringResult} instance that is attached to this one. If {@code instance} has
+ * already been added, this method does nothing.
+ */
+ public void create(@NonNull FocusMeteringResult instance, @NonNull Reply callback) {
+ if (!instanceManager.containsInstance(instance)) {
+ focusMeteringResultFlutterApi.create(
+ instanceManager.addHostCreatedInstance(instance), callback);
+ }
+ }
+
+ /** Sets the Flutter API used to send messages to Dart. */
+ @VisibleForTesting
+ void setApi(@NonNull FocusMeteringResultFlutterApi api) {
+ this.focusMeteringResultFlutterApi = api;
+ }
+}
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
new file mode 100644
index 00000000000..2c6c33e0c26
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultHostApiImpl.java
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camerax;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.FocusMeteringResult;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FocusMeteringResultHostApi;
+
+/**
+ * Host API implementation for {@link FocusMeteringResult}.
+ *
+ * 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 FocusMeteringResultHostApiImpl implements FocusMeteringResultHostApi {
+ private final InstanceManager instanceManager;
+
+ private final FocusMeteringResultProxy proxy;
+
+ /** Proxy for constructors and static method of {@link FocusMeteringResult}. */
+ @VisibleForTesting
+ public static class FocusMeteringResultProxy {
+
+ /**
+ * Returns whether or not auto focus was successful.
+ *
+ *
If the current camera does not support auto focus, it will return true. If auto focus is
+ * not requested, it will return false.
+ */
+ @NonNull
+ public Boolean isFocusSuccessful(@NonNull FocusMeteringResult focusMeteringResult) {
+ return focusMeteringResult.isFocusSuccessful();
+ }
+ }
+
+ /**
+ * Constructs a {@link FocusMeteringResultHostApiImpl}.
+ *
+ * @param instanceManager maintains instances stored to communicate with attached Dart objects
+ */
+ public FocusMeteringResultHostApiImpl(@NonNull InstanceManager instanceManager) {
+ this(instanceManager, new FocusMeteringResultProxy());
+ }
+
+ /**
+ * 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}
+ */
+ FocusMeteringResultHostApiImpl(
+ @NonNull InstanceManager instanceManager, @NonNull FocusMeteringResultProxy proxy) {
+ this.instanceManager = instanceManager;
+ this.proxy = proxy;
+ }
+
+ @Override
+ @NonNull
+ public Boolean isFocusSuccessful(@NonNull Long identifier) {
+ return proxy.isFocusSuccessful(instanceManager.getInstance(identifier));
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
index e8da1975336..0797d7bee8f 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
@@ -529,6 +529,90 @@ ArrayList toList() {
}
}
+ /**
+ * Convenience class for building [FocusMeteringAction] 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. */
+ private @NonNull Long meteringPointId;
+
+ public @NonNull Long getMeteringPointId() {
+ return meteringPointId;
+ }
+
+ public void setMeteringPointId(@NonNull Long setterArg) {
+ if (setterArg == null) {
+ throw new IllegalStateException("Nonnull field \"meteringPointId\" is null.");
+ }
+ this.meteringPointId = setterArg;
+ }
+
+ /** Metering mode represented by one of the [FocusMeteringAction] constants. */
+ private @Nullable Long meteringMode;
+
+ public @Nullable Long getMeteringMode() {
+ return meteringMode;
+ }
+
+ public void setMeteringMode(@Nullable Long setterArg) {
+ this.meteringMode = setterArg;
+ }
+
+ /** Constructor is non-public to enforce null safety; use Builder. */
+ MeteringPointInfo() {}
+
+ public static final class Builder {
+
+ private @Nullable Long meteringPointId;
+
+ public @NonNull Builder setMeteringPointId(@NonNull Long setterArg) {
+ this.meteringPointId = setterArg;
+ return this;
+ }
+
+ private @Nullable Long meteringMode;
+
+ public @NonNull Builder setMeteringMode(@Nullable Long setterArg) {
+ this.meteringMode = setterArg;
+ return this;
+ }
+
+ public @NonNull MeteringPointInfo build() {
+ MeteringPointInfo pigeonReturn = new MeteringPointInfo();
+ pigeonReturn.setMeteringPointId(meteringPointId);
+ pigeonReturn.setMeteringMode(meteringMode);
+ return pigeonReturn;
+ }
+ }
+
+ @NonNull
+ ArrayList toList() {
+ ArrayList toListResult = new ArrayList(2);
+ toListResult.add(meteringPointId);
+ toListResult.add(meteringMode);
+ return toListResult;
+ }
+
+ static @NonNull MeteringPointInfo fromList(@NonNull ArrayList list) {
+ MeteringPointInfo pigeonResult = new MeteringPointInfo();
+ Object meteringPointId = list.get(0);
+ pigeonResult.setMeteringPointId(
+ (meteringPointId == null)
+ ? null
+ : ((meteringPointId instanceof Integer)
+ ? (Integer) meteringPointId
+ : (Long) meteringPointId));
+ Object meteringMode = list.get(1);
+ pigeonResult.setMeteringMode(
+ (meteringMode == null)
+ ? null
+ : ((meteringMode instanceof Integer) ? (Integer) meteringMode : (Long) meteringMode));
+ return pigeonResult;
+ }
+ }
+
public interface Result {
@SuppressWarnings("UnknownNullness")
void success(T result);
@@ -3427,6 +3511,16 @@ void enableTorch(
void setZoomRatio(
@NonNull Long identifier, @NonNull Double ratio, @NonNull Result result);
+ void startFocusAndMetering(
+ @NonNull Long identifier,
+ @NonNull Long focusMeteringActionId,
+ @NonNull Result result);
+
+ void cancelFocusAndMetering(@NonNull Long identifier, @NonNull Result result);
+
+ void setExposureCompensationIndex(
+ @NonNull Long identifier, @NonNull Long index, @NonNull Result result);
+
/** The codec used by CameraControlHostApi. */
static @NonNull MessageCodec getCodec() {
return new StandardMessageCodec();
@@ -3505,6 +3599,110 @@ public void error(Throwable error) {
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.CameraControlHostApi.startFocusAndMetering",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ Number identifierArg = (Number) args.get(0);
+ Number focusMeteringActionIdArg = (Number) args.get(1);
+ Result resultCallback =
+ new Result() {
+ public void success(Long result) {
+ wrapped.add(0, result);
+ reply.reply(wrapped);
+ }
+
+ public void error(Throwable error) {
+ ArrayList wrappedError = wrapError(error);
+ reply.reply(wrappedError);
+ }
+ };
+
+ api.startFocusAndMetering(
+ (identifierArg == null) ? null : identifierArg.longValue(),
+ (focusMeteringActionIdArg == null)
+ ? null
+ : focusMeteringActionIdArg.longValue(),
+ resultCallback);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.CameraControlHostApi.cancelFocusAndMetering",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ Number identifierArg = (Number) args.get(0);
+ 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.cancelFocusAndMetering(
+ (identifierArg == null) ? null : identifierArg.longValue(), resultCallback);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.CameraControlHostApi.setExposureCompensationIndex",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ Number identifierArg = (Number) args.get(0);
+ Number indexArg = (Number) args.get(1);
+ Result resultCallback =
+ new Result() {
+ public void success(Long result) {
+ wrapped.add(0, result);
+ reply.reply(wrapped);
+ }
+
+ public void error(Throwable error) {
+ ArrayList wrappedError = wrapError(error);
+ reply.reply(wrappedError);
+ }
+ };
+
+ api.setExposureCompensationIndex(
+ (identifierArg == null) ? null : identifierArg.longValue(),
+ (indexArg == null) ? null : indexArg.longValue(),
+ resultCallback);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
@@ -3534,4 +3732,340 @@ public void create(@NonNull Long identifierArg, @NonNull Reply callback) {
channelReply -> callback.reply(null));
}
}
+
+ private static class FocusMeteringActionHostApiCodec extends StandardMessageCodec {
+ public static final FocusMeteringActionHostApiCodec INSTANCE =
+ new FocusMeteringActionHostApiCodec();
+
+ private FocusMeteringActionHostApiCodec() {}
+
+ @Override
+ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
+ switch (type) {
+ case (byte) 128:
+ return MeteringPointInfo.fromList((ArrayList) readValue(buffer));
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+
+ @Override
+ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
+ if (value instanceof MeteringPointInfo) {
+ stream.write(128);
+ writeValue(stream, ((MeteringPointInfo) value).toList());
+ } else {
+ super.writeValue(stream, value);
+ }
+ }
+ }
+
+ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+ public interface FocusMeteringActionHostApi {
+
+ void create(@NonNull Long identifier, @NonNull List meteringPointInfos);
+
+ /** The codec used by FocusMeteringActionHostApi. */
+ static @NonNull MessageCodec getCodec() {
+ return FocusMeteringActionHostApiCodec.INSTANCE;
+ }
+ /**
+ * Sets up an instance of `FocusMeteringActionHostApi` to handle messages through the
+ * `binaryMessenger`.
+ */
+ static void setup(
+ @NonNull BinaryMessenger binaryMessenger, @Nullable FocusMeteringActionHostApi api) {
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.FocusMeteringActionHostApi.create",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ Number identifierArg = (Number) args.get(0);
+ List meteringPointInfosArg =
+ (List) args.get(1);
+ try {
+ api.create(
+ (identifierArg == null) ? null : identifierArg.longValue(),
+ meteringPointInfosArg);
+ wrapped.add(0, null);
+ } catch (Throwable exception) {
+ ArrayList wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ }
+ }
+ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+ public interface FocusMeteringResultHostApi {
+
+ @NonNull
+ Boolean isFocusSuccessful(@NonNull Long identifier);
+
+ /** The codec used by FocusMeteringResultHostApi. */
+ static @NonNull MessageCodec getCodec() {
+ return new StandardMessageCodec();
+ }
+ /**
+ * Sets up an instance of `FocusMeteringResultHostApi` to handle messages through the
+ * `binaryMessenger`.
+ */
+ static void setup(
+ @NonNull BinaryMessenger binaryMessenger, @Nullable FocusMeteringResultHostApi api) {
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.FocusMeteringResultHostApi.isFocusSuccessful",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ Number identifierArg = (Number) args.get(0);
+ try {
+ Boolean output =
+ api.isFocusSuccessful(
+ (identifierArg == null) ? null : identifierArg.longValue());
+ wrapped.add(0, output);
+ } catch (Throwable exception) {
+ ArrayList wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ }
+ }
+ /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
+ public static class FocusMeteringResultFlutterApi {
+ private final @NonNull BinaryMessenger binaryMessenger;
+
+ public FocusMeteringResultFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
+ this.binaryMessenger = argBinaryMessenger;
+ }
+
+ /** Public interface for sending reply. */
+ @SuppressWarnings("UnknownNullness")
+ public interface Reply {
+ void reply(T reply);
+ }
+ /** The codec used by FocusMeteringResultFlutterApi. */
+ static @NonNull MessageCodec getCodec() {
+ return new StandardMessageCodec();
+ }
+
+ public void create(@NonNull Long identifierArg, @NonNull Reply callback) {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.FocusMeteringResultFlutterApi.create",
+ getCodec());
+ channel.send(
+ new ArrayList(Collections.singletonList(identifierArg)),
+ channelReply -> callback.reply(null));
+ }
+ }
+ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+ public interface MeteringPointHostApi {
+
+ void create(
+ @NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size);
+
+ @NonNull
+ Double getDefaultPointSize();
+
+ /** The codec used by MeteringPointHostApi. */
+ static @NonNull MessageCodec getCodec() {
+ return new StandardMessageCodec();
+ }
+ /**
+ * Sets up an instance of `MeteringPointHostApi` to handle messages through the
+ * `binaryMessenger`.
+ */
+ static void setup(
+ @NonNull BinaryMessenger binaryMessenger, @Nullable MeteringPointHostApi api) {
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger, "dev.flutter.pigeon.MeteringPointHostApi.create", getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ Number identifierArg = (Number) args.get(0);
+ Double xArg = (Double) args.get(1);
+ Double yArg = (Double) args.get(2);
+ Double sizeArg = (Double) args.get(3);
+ try {
+ api.create(
+ (identifierArg == null) ? null : identifierArg.longValue(),
+ xArg,
+ yArg,
+ sizeArg);
+ wrapped.add(0, null);
+ } catch (Throwable exception) {
+ ArrayList wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.MeteringPointHostApi.getDefaultPointSize",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ try {
+ Double output = api.getDefaultPointSize();
+ wrapped.add(0, output);
+ } catch (Throwable exception) {
+ ArrayList wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ }
+ }
+ /** 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);
+
+ @NonNull
+ Long createPoint(@NonNull Long x, @NonNull Long y, @Nullable Long size);
+
+ @NonNull
+ Long getDefaultPointSize();
+
+ /** The codec used by DisplayOrientedMeteringPointFactoryHostApi. */
+ static @NonNull MessageCodec getCodec() {
+ return new StandardMessageCodec();
+ }
+ /**
+ * Sets up an instance of `DisplayOrientedMeteringPointFactoryHostApi` to handle messages
+ * through the `binaryMessenger`.
+ */
+ static void setup(
+ @NonNull BinaryMessenger binaryMessenger,
+ @Nullable DisplayOrientedMeteringPointFactoryHostApi api) {
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ 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);
+ try {
+ api.create(
+ (identifierArg == null) ? null : identifierArg.longValue(),
+ (cameraInfoIdArg == null) ? null : cameraInfoIdArg.longValue(),
+ (widthArg == null) ? null : widthArg.longValue(),
+ (heightArg == null) ? null : heightArg.longValue());
+ wrapped.add(0, null);
+ } catch (Throwable exception) {
+ ArrayList wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint",
+ 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);
+ try {
+ Long output =
+ api.createPoint(
+ (xArg == null) ? null : xArg.longValue(),
+ (yArg == null) ? null : yArg.longValue(),
+ (sizeArg == null) ? null : sizeArg.longValue());
+ wrapped.add(0, output);
+ } catch (Throwable exception) {
+ ArrayList wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.getDefaultPointSize",
+ 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);
+ });
+ } 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
new file mode 100644
index 00000000000..343fbe731ac
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java
@@ -0,0 +1,96 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camerax;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.MeteringPointHostApi;
+
+/**
+ * Host API implementation for {@link MeteringPoint}.
+ *
+ * This class handles instantiating and adding native object instances that are attached to a
+ * Dart instance or handle method calls on the associated native class or an instance of the class.
+ */
+public class MeteringPointHostApiImpl implements MeteringPointHostApi {
+ private final InstanceManager instanceManager;
+ private final MeteringPointProxy proxy;
+
+ /** Proxy for constructors and static method of {@link MeteringPoint}. */
+ @VisibleForTesting
+ public static class MeteringPointProxy {
+
+ /**
+ * Creates a surface oriented {@link MeteringPoint} with the specified x, y, and size.
+ *
+ *
A {@link SurfaceOrientedMeteringPointFactory} is used to construct the {@link
+ * MeteringPoint} because underlying the camera preview that this plugin uses is a Flutter
+ * texture that is backed by a {@link Surface} created by the Flutter Android embedding.
+ */
+ @NonNull
+ public MeteringPoint create(@NonNull Double x, @NonNull Double y, @Nullable Double size) {
+ SurfaceOrientedMeteringPointFactory factory = getSurfaceOrientedMeteringPointFactory(1f, 1f);
+ if (size == null) {
+ return factory.createPoint(x.floatValue(), y.floatValue());
+ } else {
+ return factory.createPoint(x.floatValue(), y.floatValue(), size.floatValue());
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ public SurfaceOrientedMeteringPointFactory getSurfaceOrientedMeteringPointFactory(
+ float width, float height) {
+ return new SurfaceOrientedMeteringPointFactory(width, height);
+ }
+
+ /**
+ * Returns the default point size of the {@link MeteringPoint} width and height, which is a
+ * normalized percentage of the sensor width/height.
+ */
+ @NonNull
+ public float getDefaultPointSize() {
+ return MeteringPointFactory.getDefaultPointSize();
+ }
+ }
+ /**
+ * Constructs a {@link MeteringPointHostApiImpl}.
+ *
+ * @param instanceManager maintains instances stored to communicate with attached Dart objects
+ */
+ public MeteringPointHostApiImpl(@NonNull InstanceManager instanceManager) {
+ this(instanceManager, new MeteringPointProxy());
+ }
+
+ /**
+ * 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}
+ */
+ @VisibleForTesting
+ MeteringPointHostApiImpl(
+ @NonNull InstanceManager instanceManager, @NonNull MeteringPointProxy proxy) {
+ this.instanceManager = instanceManager;
+ this.proxy = proxy;
+ }
+
+ @Override
+ public void create(
+ @NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size) {
+ MeteringPoint meteringPoint = proxy.create(x, y, size);
+ instanceManager.addDartCreatedInstance(meteringPoint, identifier);
+ }
+
+ @Override
+ @NonNull
+ public Double getDefaultPointSize() {
+ return (double) proxy.getDefaultPointSize();
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java
index a01e3db8238..42ace7a51fb 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java
@@ -13,6 +13,8 @@
import android.content.Context;
import androidx.camera.core.CameraControl;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringResult;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -51,7 +53,8 @@ public void tearDown() {
public void enableTorch_turnsTorchModeOnAndOffAsExpected() {
try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) {
final CameraControlHostApiImpl cameraControlHostApiImpl =
- new CameraControlHostApiImpl(testInstanceManager, mock(Context.class));
+ new CameraControlHostApiImpl(
+ mockBinaryMessenger, testInstanceManager, mock(Context.class));
final Long cameraControlIdentifier = 88L;
final boolean enableTorch = true;
@@ -66,7 +69,7 @@ public void enableTorch_turnsTorchModeOnAndOffAsExpected() {
final ArgumentCaptor> futureCallbackCaptor =
ArgumentCaptor.forClass(FutureCallback.class);
- // Test turning on torch mode.
+ // Test successful behavior.
@SuppressWarnings("unchecked")
final GeneratedCameraXLibrary.Result successfulMockResult =
mock(GeneratedCameraXLibrary.Result.class);
@@ -81,7 +84,7 @@ public void enableTorch_turnsTorchModeOnAndOffAsExpected() {
successfulEnableTorchCallback.onSuccess(mock(Void.class));
verify(successfulMockResult).success(null);
- // Test turning off torch mode.
+ // Test failed behavior.
@SuppressWarnings("unchecked")
final GeneratedCameraXLibrary.Result failedMockResult =
mock(GeneratedCameraXLibrary.Result.class);
@@ -101,7 +104,8 @@ public void enableTorch_turnsTorchModeOnAndOffAsExpected() {
public void setZoomRatio_setsZoomAsExpected() {
try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) {
final CameraControlHostApiImpl cameraControlHostApiImpl =
- new CameraControlHostApiImpl(testInstanceManager, mock(Context.class));
+ new CameraControlHostApiImpl(
+ mockBinaryMessenger, testInstanceManager, mock(Context.class));
final Long cameraControlIdentifier = 33L;
final Double zoomRatio = 0.2D;
@@ -148,6 +152,183 @@ public void setZoomRatio_setsZoomAsExpected() {
}
}
+ @Test
+ public void startFocusAndMetering_startsFocusAndMeteringAsExpected() {
+ try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) {
+ final CameraControlHostApiImpl cameraControlHostApiImpl =
+ new CameraControlHostApiImpl(
+ mockBinaryMessenger, testInstanceManager, mock(Context.class));
+ final Long cameraControlIdentifier = 90L;
+ final FocusMeteringAction mockAction = mock(FocusMeteringAction.class);
+ final Long mockActionId = 44L;
+ final FocusMeteringResult mockResult = mock(FocusMeteringResult.class);
+ final Long mockResultId = 33L;
+
+ @SuppressWarnings("unchecked")
+ final ListenableFuture startFocusAndMeteringFuture =
+ mock(ListenableFuture.class);
+
+ testInstanceManager.addDartCreatedInstance(cameraControl, cameraControlIdentifier);
+ testInstanceManager.addDartCreatedInstance(mockResult, mockResultId);
+ testInstanceManager.addDartCreatedInstance(mockAction, mockActionId);
+
+ when(cameraControl.startFocusAndMetering(mockAction)).thenReturn(startFocusAndMeteringFuture);
+
+ @SuppressWarnings("unchecked")
+ final ArgumentCaptor> futureCallbackCaptor =
+ ArgumentCaptor.forClass(FutureCallback.class);
+
+ // Test successful behavior.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result successfulMockResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ cameraControlHostApiImpl.startFocusAndMetering(
+ cameraControlIdentifier, mockActionId, successfulMockResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(startFocusAndMeteringFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback successfulCallback = futureCallbackCaptor.getValue();
+
+ successfulCallback.onSuccess(mockResult);
+ verify(successfulMockResult).success(mockResultId);
+
+ // Test failed behavior.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result failedMockResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ final Throwable testThrowable = new Throwable();
+ cameraControlHostApiImpl.startFocusAndMetering(
+ cameraControlIdentifier, mockActionId, failedMockResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(startFocusAndMeteringFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback failedCallback = futureCallbackCaptor.getValue();
+
+ failedCallback.onFailure(testThrowable);
+ verify(failedMockResult).error(testThrowable);
+ }
+ }
+
+ @Test
+ public void cancelFocusAndMetering_cancelsFocusAndMeteringAsExpected() {
+ try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) {
+ final CameraControlHostApiImpl cameraControlHostApiImpl =
+ new CameraControlHostApiImpl(
+ mockBinaryMessenger, testInstanceManager, mock(Context.class));
+ final Long cameraControlIdentifier = 8L;
+
+ @SuppressWarnings("unchecked")
+ final ListenableFuture cancelFocusAndMeteringFuture = mock(ListenableFuture.class);
+
+ testInstanceManager.addDartCreatedInstance(cameraControl, cameraControlIdentifier);
+
+ when(cameraControl.cancelFocusAndMetering()).thenReturn(cancelFocusAndMeteringFuture);
+
+ @SuppressWarnings("unchecked")
+ final ArgumentCaptor> futureCallbackCaptor =
+ ArgumentCaptor.forClass(FutureCallback.class);
+
+ // Test successful behavior.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result successfulMockResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ cameraControlHostApiImpl.cancelFocusAndMetering(
+ cameraControlIdentifier, successfulMockResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(cancelFocusAndMeteringFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback successfulCallback = futureCallbackCaptor.getValue();
+
+ successfulCallback.onSuccess(mock(Void.class));
+ verify(successfulMockResult).success(null);
+
+ // Test failed behavior.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result failedMockResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ final Throwable testThrowable = new Throwable();
+ cameraControlHostApiImpl.cancelFocusAndMetering(cameraControlIdentifier, failedMockResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(cancelFocusAndMeteringFuture), futureCallbackCaptor.capture(), any()));
+
+ FutureCallback failedCallback = futureCallbackCaptor.getValue();
+
+ failedCallback.onFailure(testThrowable);
+ verify(failedMockResult).error(testThrowable);
+ }
+ }
+
+ @Test
+ public void setExposureCompensationIndex_setsExposureCompensationIndexAsExpected() {
+ try (MockedStatic mockedFutures = Mockito.mockStatic(Futures.class)) {
+ final CameraControlHostApiImpl cameraControlHostApiImpl =
+ new CameraControlHostApiImpl(
+ mockBinaryMessenger, testInstanceManager, mock(Context.class));
+ final Long cameraControlIdentifier = 53L;
+ final Long index = 2L;
+
+ @SuppressWarnings("unchecked")
+ final ListenableFuture setExposureCompensationIndexFuture =
+ mock(ListenableFuture.class);
+
+ testInstanceManager.addDartCreatedInstance(cameraControl, cameraControlIdentifier);
+
+ when(cameraControl.setExposureCompensationIndex(index.intValue()))
+ .thenReturn(setExposureCompensationIndexFuture);
+
+ @SuppressWarnings("unchecked")
+ final ArgumentCaptor> futureCallbackCaptor =
+ ArgumentCaptor.forClass(FutureCallback.class);
+
+ // Test successful behavior.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result successfulMockResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ cameraControlHostApiImpl.setExposureCompensationIndex(
+ cameraControlIdentifier, index, successfulMockResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(setExposureCompensationIndexFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback successfulCallback = futureCallbackCaptor.getValue();
+ final Integer fakeResult = 4;
+
+ successfulCallback.onSuccess(fakeResult);
+ verify(successfulMockResult).success(fakeResult.longValue());
+
+ // Test failed behavior.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result failedMockResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ final Throwable testThrowable = new Throwable();
+ cameraControlHostApiImpl.setExposureCompensationIndex(
+ cameraControlIdentifier, index, failedMockResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(setExposureCompensationIndexFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback failedCallback = futureCallbackCaptor.getValue();
+
+ failedCallback.onFailure(testThrowable);
+ verify(failedMockResult).error(testThrowable);
+ }
+ }
+
@Test
public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
final CameraControlFlutterApiImpl spyFlutterApi =
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java
new file mode 100644
index 00000000000..64db8054161
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java
@@ -0,0 +1,152 @@
+// 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.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.MeteringPoint;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.MeteringPointInfo;
+import java.util.Arrays;
+import java.util.List;
+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 FocusMeteringActionTest {
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock public BinaryMessenger mockBinaryMessenger;
+ @Mock public FocusMeteringAction focusMeteringAction;
+
+ InstanceManager testInstanceManager;
+
+ @Before
+ public void setUp() {
+ testInstanceManager = InstanceManager.create(identifier -> {});
+ }
+
+ @After
+ public void tearDown() {
+ testInstanceManager.stopFinalizationListener();
+ }
+
+ @Test
+ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatHasMode() {
+ FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy =
+ spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy());
+ FocusMeteringActionHostApiImpl hostApi =
+ new FocusMeteringActionHostApiImpl(testInstanceManager, proxySpy);
+ final Long focusMeteringActionIdentifier = 43L;
+
+ FocusMeteringAction.Builder mockFocusMeteringActionBuilder =
+ mock(FocusMeteringAction.Builder.class);
+ final MeteringPoint mockMeteringPoint1 = mock(MeteringPoint.class);
+ final MeteringPoint mockMeteringPoint2 = mock(MeteringPoint.class);
+ final MeteringPoint mockMeteringPoint3 = mock(MeteringPoint.class);
+ final Long mockMeteringPoint1Id = 47L;
+ final Long mockMeteringPoint2Id = 56L;
+ final Long mockMeteringPoint3Id = 99L;
+ final Integer mockMeteringPoint1Mode = FocusMeteringAction.FLAG_AE;
+ final Integer mockMeteringPoint2Mode = FocusMeteringAction.FLAG_AF;
+
+ MeteringPointInfo fakeMeteringPointInfo1 =
+ new MeteringPointInfo.Builder()
+ .setMeteringPointId(mockMeteringPoint1Id)
+ .setMeteringMode(mockMeteringPoint1Mode.longValue())
+ .build();
+ MeteringPointInfo fakeMeteringPointInfo2 =
+ new MeteringPointInfo.Builder()
+ .setMeteringPointId(mockMeteringPoint2Id)
+ .setMeteringMode(mockMeteringPoint2Mode.longValue())
+ .build();
+ MeteringPointInfo fakeMeteringPointInfo3 =
+ new MeteringPointInfo.Builder()
+ .setMeteringPointId(mockMeteringPoint3Id)
+ .setMeteringMode(null)
+ .build();
+
+ testInstanceManager.addDartCreatedInstance(mockMeteringPoint1, mockMeteringPoint1Id);
+ testInstanceManager.addDartCreatedInstance(mockMeteringPoint2, mockMeteringPoint2Id);
+ testInstanceManager.addDartCreatedInstance(mockMeteringPoint3, mockMeteringPoint3Id);
+
+ when(proxySpy.getFocusMeteringActionBuilder(
+ mockMeteringPoint1, mockMeteringPoint1Mode.intValue()))
+ .thenReturn(mockFocusMeteringActionBuilder);
+ when(mockFocusMeteringActionBuilder.build()).thenReturn(focusMeteringAction);
+
+ List mockMeteringPointInfos =
+ Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3);
+
+ hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos);
+
+ verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode);
+ verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3);
+ assertEquals(
+ testInstanceManager.getInstance(focusMeteringActionIdentifier), focusMeteringAction);
+ }
+
+ @Test
+ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() {
+ FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy =
+ spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy());
+ FocusMeteringActionHostApiImpl hostApi =
+ new FocusMeteringActionHostApiImpl(testInstanceManager, proxySpy);
+ final Long focusMeteringActionIdentifier = 43L;
+
+ FocusMeteringAction.Builder mockFocusMeteringActionBuilder =
+ mock(FocusMeteringAction.Builder.class);
+ final MeteringPoint mockMeteringPoint1 = mock(MeteringPoint.class);
+ final MeteringPoint mockMeteringPoint2 = mock(MeteringPoint.class);
+ final MeteringPoint mockMeteringPoint3 = mock(MeteringPoint.class);
+ final Long mockMeteringPoint1Id = 47L;
+ final Long mockMeteringPoint2Id = 56L;
+ final Long mockMeteringPoint3Id = 99L;
+ final Integer mockMeteringPoint2Mode = FocusMeteringAction.FLAG_AF;
+
+ MeteringPointInfo fakeMeteringPointInfo1 =
+ new MeteringPointInfo.Builder()
+ .setMeteringPointId(mockMeteringPoint1Id)
+ .setMeteringMode(null)
+ .build();
+ MeteringPointInfo fakeMeteringPointInfo2 =
+ new MeteringPointInfo.Builder()
+ .setMeteringPointId(mockMeteringPoint2Id)
+ .setMeteringMode(mockMeteringPoint2Mode.longValue())
+ .build();
+ MeteringPointInfo fakeMeteringPointInfo3 =
+ new MeteringPointInfo.Builder()
+ .setMeteringPointId(mockMeteringPoint3Id)
+ .setMeteringMode(null)
+ .build();
+
+ testInstanceManager.addDartCreatedInstance(mockMeteringPoint1, mockMeteringPoint1Id);
+ testInstanceManager.addDartCreatedInstance(mockMeteringPoint2, mockMeteringPoint2Id);
+ testInstanceManager.addDartCreatedInstance(mockMeteringPoint3, mockMeteringPoint3Id);
+
+ when(proxySpy.getFocusMeteringActionBuilder(mockMeteringPoint1))
+ .thenReturn(mockFocusMeteringActionBuilder);
+ when(mockFocusMeteringActionBuilder.build()).thenReturn(focusMeteringAction);
+
+ List mockMeteringPointInfos =
+ Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3);
+
+ hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos);
+
+ verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode);
+ verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3);
+ assertEquals(
+ testInstanceManager.getInstance(focusMeteringActionIdentifier), focusMeteringAction);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringResultTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringResultTest.java
new file mode 100644
index 00000000000..dae86557b45
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringResultTest.java
@@ -0,0 +1,73 @@
+// 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.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.camera.core.FocusMeteringResult;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.FocusMeteringResultFlutterApi;
+import java.util.Objects;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class FocusMeteringResultTest {
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock public BinaryMessenger mockBinaryMessenger;
+ @Mock public FocusMeteringResult focusMeteringResult;
+ @Mock public FocusMeteringResultFlutterApi mockFlutterApi;
+
+ InstanceManager testInstanceManager;
+
+ @Before
+ public void setUp() {
+ testInstanceManager = InstanceManager.create(identifier -> {});
+ }
+
+ @After
+ public void tearDown() {
+ testInstanceManager.stopFinalizationListener();
+ }
+
+ @Test
+ public void isFocusSuccessful_returnsExpectedResult() {
+ final FocusMeteringResultHostApiImpl focusMeteringResultHostApiImpl =
+ new FocusMeteringResultHostApiImpl(testInstanceManager);
+ final Long focusMeteringResultIdentifier = 98L;
+ final boolean result = true;
+
+ testInstanceManager.addDartCreatedInstance(focusMeteringResult, focusMeteringResultIdentifier);
+
+ when(focusMeteringResult.isFocusSuccessful()).thenReturn(result);
+
+ assertTrue(focusMeteringResultHostApiImpl.isFocusSuccessful(focusMeteringResultIdentifier));
+ verify(focusMeteringResult).isFocusSuccessful();
+ }
+
+ @Test
+ public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
+ final FocusMeteringResultFlutterApiImpl flutterApi =
+ new FocusMeteringResultFlutterApiImpl(mockBinaryMessenger, testInstanceManager);
+
+ flutterApi.setApi(mockFlutterApi);
+
+ flutterApi.create(focusMeteringResult, reply -> {});
+ final long focusMeteringResultIdentifier =
+ Objects.requireNonNull(
+ testInstanceManager.getIdentifierForStrongReference(focusMeteringResult));
+
+ verify(mockFlutterApi).create(eq(focusMeteringResultIdentifier), any());
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java
new file mode 100644
index 00000000000..f245eb53124
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java
@@ -0,0 +1,113 @@
+// 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.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
+import io.flutter.plugin.common.BinaryMessenger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+public class MeteringPointTest {
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock public BinaryMessenger mockBinaryMessenger;
+ @Mock public MeteringPoint meteringPoint;
+
+ InstanceManager testInstanceManager;
+
+ @Before
+ public void setUp() {
+ testInstanceManager = InstanceManager.create(identifier -> {});
+ }
+
+ @After
+ public void tearDown() {
+ testInstanceManager.stopFinalizationListener();
+ }
+
+ @Test
+ public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified() {
+ MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
+ spy(new MeteringPointHostApiImpl.MeteringPointProxy());
+ MeteringPointHostApiImpl hostApi = new MeteringPointHostApiImpl(testInstanceManager, proxySpy);
+ final Long meteringPointIdentifier = 78L;
+ final Float x = 0.3f;
+ final Float y = 0.2f;
+ final Float size = 6f;
+ final Float surfaceWidth = 1f;
+ final Float surfaceHeight = 1f;
+ SurfaceOrientedMeteringPointFactory mockSurfaceOrientedMeteringPointFactory =
+ mock(SurfaceOrientedMeteringPointFactory.class);
+
+ when(proxySpy.getSurfaceOrientedMeteringPointFactory(surfaceWidth, surfaceHeight))
+ .thenReturn(mockSurfaceOrientedMeteringPointFactory);
+ when(mockSurfaceOrientedMeteringPointFactory.createPoint(x, y, size)).thenReturn(meteringPoint);
+
+ hostApi.create(meteringPointIdentifier, x.doubleValue(), y.doubleValue(), size.doubleValue());
+
+ verify(mockSurfaceOrientedMeteringPointFactory).createPoint(x, y, size);
+ assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint);
+ }
+
+ @Test
+ public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified() {
+ MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
+ spy(new MeteringPointHostApiImpl.MeteringPointProxy());
+ MeteringPointHostApiImpl hostApi = new MeteringPointHostApiImpl(testInstanceManager, proxySpy);
+ final Long meteringPointIdentifier = 78L;
+ final Float x = 0.3f;
+ final Float y = 0.2f;
+ final Float surfaceWidth = 1f;
+ final Float surfaceHeight = 1f;
+ SurfaceOrientedMeteringPointFactory mockSurfaceOrientedMeteringPointFactory =
+ mock(SurfaceOrientedMeteringPointFactory.class);
+
+ when(proxySpy.getSurfaceOrientedMeteringPointFactory(surfaceWidth, surfaceHeight))
+ .thenReturn(mockSurfaceOrientedMeteringPointFactory);
+ when(mockSurfaceOrientedMeteringPointFactory.createPoint(x, y)).thenReturn(meteringPoint);
+
+ hostApi.create(meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null);
+
+ verify(mockSurfaceOrientedMeteringPointFactory).createPoint(x, y);
+ assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint);
+ }
+
+ @Test
+ public void getDefaultPointSize_returnsExpectedSize() {
+ try (MockedStatic mockedMeteringPointFactory =
+ Mockito.mockStatic(MeteringPointFactory.class)) {
+ final MeteringPointHostApiImpl meteringPointHostApiImpl =
+ new MeteringPointHostApiImpl(testInstanceManager);
+ final Long meteringPointIdentifier = 93L;
+ final Long index = 2L;
+ final Double defaultPointSize = 4D;
+
+ testInstanceManager.addDartCreatedInstance(meteringPoint, meteringPointIdentifier);
+
+ mockedMeteringPointFactory
+ .when(() -> MeteringPointFactory.getDefaultPointSize())
+ .thenAnswer((Answer) invocation -> defaultPointSize.floatValue());
+
+ assertEquals(meteringPointHostApiImpl.getDefaultPointSize(), defaultPointSize);
+ mockedMeteringPointFactory.verify(() -> MeteringPointFactory.getDefaultPointSize());
+ }
+ }
+}
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart
index 24f159a1eab..db7ec4b32d4 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart
@@ -12,6 +12,7 @@ import 'camera_state_error.dart';
import 'camerax_library.g.dart';
import 'device_orientation_manager.dart';
import 'exposure_state.dart';
+import 'focus_metering_result.dart';
import 'image_proxy.dart';
import 'java_object.dart';
import 'live_data.dart';
@@ -50,7 +51,8 @@ class AndroidCameraXCameraFlutterApis {
ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl,
PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl,
AnalyzerFlutterApiImpl? analyzerFlutterApiImpl,
- CameraControlFlutterApiImpl? cameraControlFlutterApiImpl}) {
+ CameraControlFlutterApiImpl? cameraControlFlutterApiImpl,
+ FocusMeteringResultFlutterApiImpl? focusMeteringResultFlutterApiImpl}) {
this.javaObjectFlutterApiImpl =
javaObjectFlutterApiImpl ?? JavaObjectFlutterApiImpl();
this.cameraInfoFlutterApiImpl =
@@ -94,6 +96,9 @@ class AndroidCameraXCameraFlutterApis {
planeProxyFlutterApiImpl ?? PlaneProxyFlutterApiImpl();
this.cameraControlFlutterApiImpl =
cameraControlFlutterApiImpl ?? CameraControlFlutterApiImpl();
+ this.focusMeteringResultFlutterApiImpl =
+ focusMeteringResultFlutterApiImpl ??
+ FocusMeteringResultFlutterApiImpl();
}
static bool _haveBeenSetUp = false;
@@ -169,6 +174,10 @@ class AndroidCameraXCameraFlutterApis {
/// Flutter Api implementation for [CameraControl].
late final CameraControlFlutterApiImpl cameraControlFlutterApiImpl;
+ /// Flutter Api implementation for [FocusMeteringResult].
+ late final FocusMeteringResultFlutterApiImpl
+ focusMeteringResultFlutterApiImpl;
+
/// Ensures all the Flutter APIs have been setup to receive calls from native code.
void ensureSetUp() {
if (!_haveBeenSetUp) {
@@ -195,6 +204,7 @@ class AndroidCameraXCameraFlutterApis {
LiveDataFlutterApi.setup(liveDataFlutterApiImpl);
ObserverFlutterApi.setup(observerFlutterApiImpl);
CameraControlFlutterApi.setup(cameraControlFlutterApiImpl);
+ FocusMeteringResultFlutterApi.setup(focusMeteringResultFlutterApiImpl);
_haveBeenSetUp = true;
}
}
diff --git a/packages/camera/camera_android_camerax/lib/src/camera_control.dart b/packages/camera/camera_android_camerax/lib/src/camera_control.dart
index 57b84772be3..9c50ffa161f 100644
--- a/packages/camera/camera_android_camerax/lib/src/camera_control.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camera_control.dart
@@ -7,6 +7,8 @@ import 'package:meta/meta.dart' show immutable;
import 'android_camera_camerax_flutter_api_impls.dart';
import 'camerax_library.g.dart';
+import 'focus_metering_action.dart';
+import 'focus_metering_result.dart';
import 'instance_manager.dart';
import 'java_object.dart';
import 'system_services.dart';
@@ -48,6 +50,34 @@ class CameraControl extends JavaObject {
Future setZoomRatio(double ratio) async {
return _api.setZoomRatioFromInstance(this, ratio);
}
+
+ /// Starts a focus and metering action configured by the [FocusMeteringAction].
+ ///
+ /// Will trigger an auto focus action and enable auto focus/auto exposure/
+ /// auto white balance metering regions.
+ ///
+ /// Returns null if focus and metering could not be started.
+ Future startFocusAndMetering(
+ FocusMeteringAction action) {
+ return _api.startFocusAndMeteringFromInstance(this, action);
+ }
+
+ /// Cancels current [FocusMeteringAction] and clears auto focus/auto exposure/
+ /// auto white balance regions.
+ Future cancelFocusAndMetering() =>
+ _api.cancelFocusAndMeteringFromInstance(this);
+
+ /// Sets the exposure compensation value for related [Camera] and returns the
+ /// new target exposure value.
+ ///
+ /// The exposure compensation value set on the camera must be within the range
+ /// of the current [ExposureState]'s `exposureCompensationRange` for the call
+ /// to succeed.
+ ///
+ /// Returns null if the exposure compensation index failed to be set.
+ Future setExposureCompensationIndex(int index) async {
+ return _api.setExposureCompensationIndexFromInstance(this, index);
+ }
}
/// Host API implementation of [CameraControl].
@@ -94,6 +124,47 @@ class _CameraControlHostApiImpl extends CameraControlHostApi {
'Zoom ratio was unable to be set. If ratio was not out of range, newer value may have been set; otherwise, the camera may be closed.');
}
}
+
+ /// Starts a focus and metering action configured by the [FocusMeteringAction]
+ /// for the specified [CameraControl] instance.
+ Future startFocusAndMeteringFromInstance(
+ CameraControl instance, FocusMeteringAction action) async {
+ final int cameraControlIdentifier =
+ instanceManager.getIdentifier(instance)!;
+ final int actionIdentifier = instanceManager.getIdentifier(action)!;
+ try {
+ final int focusMeteringResultId = await startFocusAndMetering(
+ cameraControlIdentifier, actionIdentifier);
+ return instanceManager.getInstanceWithWeakReference(
+ focusMeteringResultId);
+ } on PlatformException catch (e) {
+ SystemServices.cameraErrorStreamController
+ .add(e.message ?? 'Starting focus and metering failed.');
+ return Future.value();
+ }
+ }
+
+ /// Cancels current [FocusMeteringAction] and clears AF/AE/AWB regions for the
+ /// specified [CameraControl] instance.
+ Future cancelFocusAndMeteringFromInstance(
+ CameraControl instance) async {
+ final int identifier = instanceManager.getIdentifier(instance)!;
+ await cancelFocusAndMetering(identifier);
+ }
+
+ /// Sets exposure compensation index for specified [CameraControl] instance
+ /// and returns the new target exposure value.
+ Future setExposureCompensationIndexFromInstance(
+ CameraControl instance, int index) async {
+ final int identifier = instanceManager.getIdentifier(instance)!;
+ try {
+ return setExposureCompensationIndex(identifier, index);
+ } on PlatformException catch (e) {
+ SystemServices.cameraErrorStreamController.add(e.message ??
+ 'Setting the camera exposure compensation index failed.');
+ return Future.value();
+ }
+ }
}
/// Flutter API implementation of [CameraControl].
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 f1528bbeec8..e57dc8222ac 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
@@ -210,6 +210,37 @@ class VideoQualityData {
}
}
+/// Convenience class for building [FocusMeteringAction] with multiple metering
+/// points.
+class MeteringPointInfo {
+ MeteringPointInfo({
+ required this.meteringPointId,
+ this.meteringMode,
+ });
+
+ /// Instance manager ID corresponding to [MeteringPoint] that relates to this
+ /// info.
+ int meteringPointId;
+
+ /// Metering mode represented by one of the [FocusMeteringAction] constants.
+ int? meteringMode;
+
+ Object encode() {
+ return [
+ meteringPointId,
+ meteringMode,
+ ];
+ }
+
+ static MeteringPointInfo decode(Object result) {
+ result as List;
+ return MeteringPointInfo(
+ meteringPointId: result[0]! as int,
+ meteringMode: result[1] as int?,
+ );
+ }
+}
+
class InstanceManagerHostApi {
/// Constructor for [InstanceManagerHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
@@ -2847,6 +2878,86 @@ class CameraControlHostApi {
return;
}
}
+
+ Future startFocusAndMetering(
+ int arg_identifier, int arg_focusMeteringActionId) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.CameraControlHostApi.startFocusAndMetering', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_identifier, arg_focusMeteringActionId])
+ as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as int?)!;
+ }
+ }
+
+ Future cancelFocusAndMetering(int arg_identifier) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.CameraControlHostApi.cancelFocusAndMetering', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_identifier]) as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else {
+ return;
+ }
+ }
+
+ Future setExposureCompensationIndex(
+ int arg_identifier, int arg_index) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.CameraControlHostApi.setExposureCompensationIndex',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel
+ .send([arg_identifier, arg_index]) as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as int?)!;
+ }
+ }
}
abstract class CameraControlFlutterApi {
@@ -2877,3 +2988,281 @@ abstract class CameraControlFlutterApi {
}
}
}
+
+class _FocusMeteringActionHostApiCodec extends StandardMessageCodec {
+ const _FocusMeteringActionHostApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is MeteringPointInfo) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return MeteringPointInfo.decode(readValue(buffer)!);
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+class FocusMeteringActionHostApi {
+ /// Constructor for [FocusMeteringActionHostApi]. 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.
+ FocusMeteringActionHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec codec = _FocusMeteringActionHostApiCodec();
+
+ Future create(int arg_identifier,
+ List arg_meteringPointInfos) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FocusMeteringActionHostApi.create', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_identifier, arg_meteringPointInfos])
+ as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else {
+ return;
+ }
+ }
+}
+
+class FocusMeteringResultHostApi {
+ /// Constructor for [FocusMeteringResultHostApi]. 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.
+ FocusMeteringResultHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec codec = StandardMessageCodec();
+
+ Future isFocusSuccessful(int arg_identifier) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FocusMeteringResultHostApi.isFocusSuccessful',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_identifier]) as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as bool?)!;
+ }
+ }
+}
+
+abstract class FocusMeteringResultFlutterApi {
+ static const MessageCodec codec = StandardMessageCodec();
+
+ void create(int identifier);
+
+ static void setup(FocusMeteringResultFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FocusMeteringResultFlutterApi.create', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.FocusMeteringResultFlutterApi.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.FocusMeteringResultFlutterApi.create was null, expected non-null int.');
+ api.create(arg_identifier!);
+ return;
+ });
+ }
+ }
+ }
+}
+
+class MeteringPointHostApi {
+ /// Constructor for [MeteringPointHostApi]. 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.
+ MeteringPointHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec codec = StandardMessageCodec();
+
+ Future create(
+ int arg_identifier, double arg_x, double arg_y, double? arg_size) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.MeteringPointHostApi.create', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_identifier, arg_x, arg_y, arg_size])
+ as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else {
+ return;
+ }
+ }
+
+ Future getDefaultPointSize() async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.MeteringPointHostApi.getDefaultPointSize', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel.send(null) as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as double?)!;
+ }
+ }
+}
+
+class DisplayOrientedMeteringPointFactoryHostApi {
+ /// Constructor for [DisplayOrientedMeteringPointFactoryHostApi]. 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})
+ : _binaryMessenger = binaryMessenger;
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec codec = StandardMessageCodec();
+
+ Future create(int arg_identifier, int arg_cameraInfoId, int arg_width,
+ int arg_height) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.create',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel.send(
+ [arg_identifier, arg_cameraInfoId, arg_width, arg_height])
+ as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else {
+ return;
+ }
+ }
+
+ Future createPoint(int arg_x, int arg_y, int? arg_size) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.createPoint',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_x, arg_y, arg_size]) as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as int?)!;
+ }
+ }
+
+ Future getDefaultPointSize() async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.DisplayOrientedMeteringPointFactoryHostApi.getDefaultPointSize',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel.send(null) as List?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as int?)!;
+ }
+ }
+}
diff --git a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart
new file mode 100644
index 00000000000..6d9ebd97f01
--- /dev/null
+++ b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart
@@ -0,0 +1,116 @@
+// 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' show BinaryMessenger;
+import 'package:meta/meta.dart' show immutable;
+
+import 'camerax_library.g.dart';
+import 'instance_manager.dart';
+import 'java_object.dart';
+import 'metering_point.dart';
+
+/// A configuration used to trigger a focus and/or metering action.
+///
+/// See https://developer.android.com/reference/androidx/camera/core/FocusMeteringAction.
+@immutable
+class FocusMeteringAction extends JavaObject {
+ /// Creates a [FocusMeteringAction].
+ FocusMeteringAction({
+ BinaryMessenger? binaryMessenger,
+ InstanceManager? instanceManager,
+ required List<(MeteringPoint meteringPoint, int? meteringMode)>
+ meteringPointInfos,
+ }) : super.detached(
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ ) {
+ _api = _FocusMeteringActionHostApiImpl(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ _api.createFromInstance(this, meteringPointInfos);
+ }
+
+ /// Creates a [FocusMeteringAction] that is not automatically attached to a
+ /// native object.
+ FocusMeteringAction.detached({
+ BinaryMessenger? binaryMessenger,
+ InstanceManager? instanceManager,
+ }) : super.detached(
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ ) {
+ _api = _FocusMeteringActionHostApiImpl(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ }
+
+ late final _FocusMeteringActionHostApiImpl _api;
+
+ /// Flag for metering mode that indicates the auto focus region is enabled.
+ ///
+ /// An autofocus scan is also triggered when [flagAf] is assigned.
+ ///
+ /// See https://developer.android.com/reference/androidx/camera/core/FocusMeteringAction#FLAG_AF().
+ static const int flagAf = 1;
+
+ /// Flag for metering mode that indicates the auto exposure region is enabled.
+ ///
+ /// See https://developer.android.com/reference/androidx/camera/core/FocusMeteringAction#FLAG_AE().
+ static const int flagAe = 2;
+
+ /// Flag for metering mode that indicates the auto white balance region is
+ /// enabled.
+ ///
+ /// See https://developer.android.com/reference/androidx/camera/core/FocusMeteringAction#FLAG_AWB().
+ static const int flagAwb = 4;
+}
+
+/// Host API implementation of [FocusMeteringAction].
+class _FocusMeteringActionHostApiImpl extends FocusMeteringActionHostApi {
+ /// Constructs a [_FocusMeteringActionHostApiImpl].
+ ///
+ /// 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].
+ _FocusMeteringActionHostApiImpl(
+ {this.binaryMessenger, InstanceManager? instanceManager}) {
+ this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
+ }
+
+ /// 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.
+ late final InstanceManager instanceManager;
+
+ /// Creates a [FocusMeteringAction] instance with the specified list of
+ /// [MeteringPoint]s and their modes in order of descending priority.
+ void createFromInstance(
+ FocusMeteringAction instance,
+ List<(MeteringPoint meteringPoint, int? meteringMode)>
+ meteringPointInfos) {
+ final int identifier = instanceManager.addDartCreatedInstance(instance,
+ onCopy: (FocusMeteringAction original) {
+ return FocusMeteringAction.detached(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ });
+
+ final List meteringPointInfosWithIds =
+ [];
+ for (final (
+ MeteringPoint meteringPoint,
+ int? meteringMode
+ ) meteringPointInfo in meteringPointInfos) {
+ meteringPointInfosWithIds.add(MeteringPointInfo(
+ meteringPointId: instanceManager.getIdentifier(meteringPointInfo.$1)!,
+ meteringMode: meteringPointInfo.$2));
+ }
+
+ create(identifier, meteringPointInfosWithIds);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/lib/src/focus_metering_result.dart b/packages/camera/camera_android_camerax/lib/src/focus_metering_result.dart
new file mode 100644
index 00000000000..3a1afbd5fa5
--- /dev/null
+++ b/packages/camera/camera_android_camerax/lib/src/focus_metering_result.dart
@@ -0,0 +1,108 @@
+// 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' show BinaryMessenger;
+import 'package:meta/meta.dart' show immutable;
+
+import 'android_camera_camerax_flutter_api_impls.dart';
+import 'camerax_library.g.dart';
+import 'instance_manager.dart';
+import 'java_object.dart';
+
+/// The result of [CameraControl.startFocusAndMetering].
+///
+/// See https://developer.android.com/reference/kotlin/androidx/camera/core/FocusMeteringResult.
+@immutable
+class FocusMeteringResult extends JavaObject {
+ /// Creates a [FocusMeteringResult] that is not automatically attached to a
+ /// native object.
+ FocusMeteringResult.detached({
+ BinaryMessenger? binaryMessenger,
+ InstanceManager? instanceManager,
+ }) : super.detached(
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ ) {
+ _api = _FocusMeteringResultHostApiImpl(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
+ }
+
+ late final _FocusMeteringResultHostApiImpl _api;
+
+ /// Returns whether or not auto focus is successful.
+ ///
+ /// If the current camera does not support auto focus, it will return true. If
+ /// auto focus is not requested, it will return false.
+ Future isFocusSuccessful() => _api.isFocusSuccessfulFromInstance(this);
+}
+
+/// Host API implementation of [FocusMeteringResult].
+class _FocusMeteringResultHostApiImpl extends FocusMeteringResultHostApi {
+ /// Constructs a [FocusMeteringActionHostApiImpl].
+ ///
+ /// 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].
+ _FocusMeteringResultHostApiImpl(
+ {this.binaryMessenger, InstanceManager? instanceManager}) {
+ this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
+ }
+
+ /// 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.
+ late final InstanceManager instanceManager;
+
+ /// Returns whether or not the [instance] indicates that auto focus was
+ /// successful.
+ Future isFocusSuccessfulFromInstance(FocusMeteringResult instance) {
+ final int identifier = instanceManager.getIdentifier(instance)!;
+ return isFocusSuccessful(identifier);
+ }
+}
+
+/// Flutter API implementation of [FocusMeteringResult].
+class FocusMeteringResultFlutterApiImpl extends FocusMeteringResultFlutterApi {
+ /// Constructs a [FocusMeteringResultFlutterApiImpl].
+ ///
+ /// 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].
+ FocusMeteringResultFlutterApiImpl({
+ BinaryMessenger? binaryMessenger,
+ InstanceManager? instanceManager,
+ }) : _binaryMessenger = binaryMessenger,
+ _instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
+
+ /// Receives binary data across the Flutter platform barrier.
+ final BinaryMessenger? _binaryMessenger;
+
+ /// Maintains instances stored to communicate with native language objects.
+ final InstanceManager _instanceManager;
+
+ @override
+ void create(int identifier) {
+ _instanceManager.addHostCreatedInstance(
+ FocusMeteringResult.detached(
+ binaryMessenger: _binaryMessenger, instanceManager: _instanceManager),
+ identifier,
+ onCopy: (FocusMeteringResult original) {
+ return FocusMeteringResult.detached(
+ binaryMessenger: _binaryMessenger,
+ instanceManager: _instanceManager);
+ },
+ );
+ }
+}
diff --git a/packages/camera/camera_android_camerax/lib/src/metering_point.dart b/packages/camera/camera_android_camerax/lib/src/metering_point.dart
new file mode 100644
index 00000000000..d699993a893
--- /dev/null
+++ b/packages/camera/camera_android_camerax/lib/src/metering_point.dart
@@ -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.
+
+import 'package:flutter/services.dart' show BinaryMessenger;
+import 'package:meta/meta.dart' show immutable;
+
+import 'android_camera_camerax_flutter_api_impls.dart';
+import 'camerax_library.g.dart';
+import 'instance_manager.dart';
+import 'java_object.dart';
+
+/// Representation for a region which can be converted to sensor coordinate
+/// system for focus and metering purpose.
+///
+/// See https://developer.android.com/reference/androidx/camera/core/MeteringPoint.
+@immutable
+class MeteringPoint extends JavaObject {
+ /// Creates a [MeteringPoint].
+ MeteringPoint({
+ BinaryMessenger? binaryMessenger,
+ InstanceManager? instanceManager,
+ required this.x,
+ required this.y,
+ this.size,
+ }) : super.detached(
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ ) {
+ _api = _MeteringPointHostApiImpl(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ _api.createFromInstance(this, x, y, size);
+ AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
+ }
+
+ /// Creates a [MeteringPoint] that is not automatically attached to a
+ /// native object.
+ MeteringPoint.detached({
+ BinaryMessenger? binaryMessenger,
+ InstanceManager? instanceManager,
+ required this.x,
+ required this.y,
+ this.size,
+ }) : super.detached(
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ ) {
+ _api = _MeteringPointHostApiImpl(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
+ }
+
+ late final _MeteringPointHostApiImpl _api;
+
+ /// X coordinate.
+ final double x;
+
+ /// Y coordinate.
+ final double y;
+
+ /// The size of the [MeteringPoint] width and height (ranging from 0 to 1),
+ /// which is a normalized percentage of the sensor width/height (or crop
+ /// region width/height if crop region is set).
+ final double? size;
+
+ /// The default size of the [MeteringPoint] width and height (ranging from 0
+ /// to 1) which is a (normalized) percentage of the sensor width/height (or
+ /// crop region width/height if crop region is set).
+ static Future getDefaultPointSize(
+ {BinaryMessenger? binaryMessenger}) {
+ final MeteringPointHostApi hostApi =
+ MeteringPointHostApi(binaryMessenger: binaryMessenger);
+ return hostApi.getDefaultPointSize();
+ }
+}
+
+/// Host API implementation of [MeteringPoint].
+class _MeteringPointHostApiImpl extends MeteringPointHostApi {
+ /// Constructs a [_MeteringPointHostApiImpl].
+ ///
+ /// 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].
+ _MeteringPointHostApiImpl(
+ {this.binaryMessenger, InstanceManager? instanceManager}) {
+ this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
+ }
+
+ /// 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.
+ late final InstanceManager instanceManager;
+
+ /// Creates a [MeteringPoint] instance with the specified [x] and [y]
+ /// coordinates as well as [size] if non-null.
+ Future createFromInstance(
+ MeteringPoint instance, double x, double y, double? size) {
+ int? identifier = instanceManager.getIdentifier(instance);
+ identifier ??= instanceManager.addDartCreatedInstance(instance,
+ onCopy: (MeteringPoint original) {
+ return MeteringPoint.detached(
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ x: original.x,
+ y: original.y,
+ size: original.size);
+ });
+
+ return create(identifier, x, y, size);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
index 94285d148df..7fbc7e1abaf 100644
--- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
+++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
@@ -126,6 +126,23 @@ enum VideoResolutionFallbackRule {
lowerQualityThan,
}
+/// Convenience class for building [FocusMeteringAction]s with multiple metering
+/// points.
+class MeteringPointInfo {
+ MeteringPointInfo({
+ required this.meteringPointId,
+ required this.meteringMode,
+ });
+
+ /// InstanceManager ID for a [MeteringPoint].
+ int meteringPointId;
+
+ /// The metering mode of the [MeteringPoint] whose ID is [meteringPointId].
+ ///
+ /// Metering mode should be one of the [FocusMeteringAction] constants.
+ int? meteringMode;
+}
+
@HostApi(dartHostTestHandler: 'TestInstanceManagerHostApi')
abstract class InstanceManagerHostApi {
/// Clear the native `InstanceManager`.
@@ -444,9 +461,40 @@ abstract class CameraControlHostApi {
@async
void setZoomRatio(int identifier, double ratio);
+
+ @async
+ int startFocusAndMetering(int identifier, int focusMeteringActionId);
+
+ @async
+ void cancelFocusAndMetering(int identifier);
+
+ @async
+ int setExposureCompensationIndex(int identifier, int index);
}
@FlutterApi()
abstract class CameraControlFlutterApi {
void create(int identifier);
}
+
+@HostApi(dartHostTestHandler: 'TestFocusMeteringActionHostApi')
+abstract class FocusMeteringActionHostApi {
+ void create(int identifier, List meteringPointInfos);
+}
+
+@HostApi(dartHostTestHandler: 'TestFocusMeteringResultHostApi')
+abstract class FocusMeteringResultHostApi {
+ bool isFocusSuccessful(int identifier);
+}
+
+@FlutterApi()
+abstract class FocusMeteringResultFlutterApi {
+ void create(int identifier);
+}
+
+@HostApi(dartHostTestHandler: 'TestMeteringPointHostApi')
+abstract class MeteringPointHostApi {
+ void create(int identifier, double x, double y, double? size);
+
+ double getDefaultPointSize();
+}
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index 8b8825eed1f..5ae0942e7d9 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+27
+version: 0.5.0+28
environment:
sdk: ">=3.0.0 <4.0.0"
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart
index 174fde92675..4c90e940e71 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart
@@ -4,34 +4,36 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i16;
-import 'dart:typed_data' as _i27;
+import 'dart:typed_data' as _i29;
import 'package:camera_android_camerax/src/analyzer.dart' as _i15;
import 'package:camera_android_camerax/src/camera.dart' as _i9;
import 'package:camera_android_camerax/src/camera_control.dart' as _i3;
import 'package:camera_android_camerax/src/camera_info.dart' as _i2;
-import 'package:camera_android_camerax/src/camera_selector.dart' as _i20;
+import 'package:camera_android_camerax/src/camera_selector.dart' as _i22;
import 'package:camera_android_camerax/src/camera_state.dart' as _i18;
import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7;
import 'package:camera_android_camerax/src/exposure_state.dart' as _i5;
-import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i21;
-import 'package:camera_android_camerax/src/image_analysis.dart' as _i22;
-import 'package:camera_android_camerax/src/image_capture.dart' as _i23;
+import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i23;
+import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i21;
+import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i20;
+import 'package:camera_android_camerax/src/image_analysis.dart' as _i24;
+import 'package:camera_android_camerax/src/image_capture.dart' as _i25;
import 'package:camera_android_camerax/src/image_proxy.dart' as _i17;
import 'package:camera_android_camerax/src/live_data.dart' as _i4;
-import 'package:camera_android_camerax/src/observer.dart' as _i26;
+import 'package:camera_android_camerax/src/observer.dart' as _i28;
import 'package:camera_android_camerax/src/pending_recording.dart' as _i10;
-import 'package:camera_android_camerax/src/plane_proxy.dart' as _i25;
-import 'package:camera_android_camerax/src/preview.dart' as _i28;
+import 'package:camera_android_camerax/src/plane_proxy.dart' as _i27;
+import 'package:camera_android_camerax/src/preview.dart' as _i30;
import 'package:camera_android_camerax/src/process_camera_provider.dart'
- as _i29;
-import 'package:camera_android_camerax/src/quality_selector.dart' as _i31;
+ as _i31;
+import 'package:camera_android_camerax/src/quality_selector.dart' as _i33;
import 'package:camera_android_camerax/src/recorder.dart' as _i11;
import 'package:camera_android_camerax/src/recording.dart' as _i8;
-import 'package:camera_android_camerax/src/resolution_selector.dart' as _i32;
-import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i33;
-import 'package:camera_android_camerax/src/use_case.dart' as _i30;
-import 'package:camera_android_camerax/src/video_capture.dart' as _i34;
+import 'package:camera_android_camerax/src/resolution_selector.dart' as _i34;
+import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i35;
+import 'package:camera_android_camerax/src/use_case.dart' as _i32;
+import 'package:camera_android_camerax/src/video_capture.dart' as _i36;
import 'package:camera_android_camerax/src/zoom_state.dart' as _i19;
import 'package:camera_platform_interface/camera_platform_interface.dart'
as _i6;
@@ -39,9 +41,9 @@ import 'package:flutter/foundation.dart' as _i14;
import 'package:flutter/services.dart' as _i13;
import 'package:flutter/widgets.dart' as _i12;
import 'package:mockito/mockito.dart' as _i1;
-import 'package:mockito/src/dummies.dart' as _i24;
+import 'package:mockito/src/dummies.dart' as _i26;
-import 'test_camerax_library.g.dart' as _i35;
+import 'test_camerax_library.g.dart' as _i37;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -402,6 +404,40 @@ class MockCameraControl extends _i1.Mock implements _i3.CameraControl {
returnValue: _i16.Future.value(),
returnValueForMissingStub: _i16.Future.value(),
) as _i16.Future);
+
+ @override
+ _i16.Future<_i20.FocusMeteringResult?> startFocusAndMetering(
+ _i21.FocusMeteringAction? action) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #startFocusAndMetering,
+ [action],
+ ),
+ returnValue: _i16.Future<_i20.FocusMeteringResult?>.value(),
+ returnValueForMissingStub:
+ _i16.Future<_i20.FocusMeteringResult?>.value(),
+ ) as _i16.Future<_i20.FocusMeteringResult?>);
+
+ @override
+ _i16.Future cancelFocusAndMetering() => (super.noSuchMethod(
+ Invocation.method(
+ #cancelFocusAndMetering,
+ [],
+ ),
+ returnValue: _i16.Future.value(),
+ returnValueForMissingStub: _i16.Future.value(),
+ ) as _i16.Future);
+
+ @override
+ _i16.Future setExposureCompensationIndex(int? index) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setExposureCompensationIndex,
+ [index],
+ ),
+ returnValue: _i16.Future.value(),
+ returnValueForMissingStub: _i16.Future.value(),
+ ) as _i16.Future);
}
/// A class which mocks [CameraImageData].
@@ -448,7 +484,7 @@ class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData {
///
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
-class MockCameraSelector extends _i1.Mock implements _i20.CameraSelector {
+class MockCameraSelector extends _i1.Mock implements _i22.CameraSelector {
@override
_i16.Future> filter(List<_i2.CameraInfo>? cameraInfos) =>
(super.noSuchMethod(
@@ -494,7 +530,7 @@ class MockExposureState extends _i1.Mock implements _i5.ExposureState {
///
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
-class MockFallbackStrategy extends _i1.Mock implements _i21.FallbackStrategy {
+class MockFallbackStrategy extends _i1.Mock implements _i23.FallbackStrategy {
@override
_i7.VideoQuality get quality => (super.noSuchMethod(
Invocation.getter(#quality),
@@ -515,7 +551,7 @@ class MockFallbackStrategy extends _i1.Mock implements _i21.FallbackStrategy {
///
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
-class MockImageAnalysis extends _i1.Mock implements _i22.ImageAnalysis {
+class MockImageAnalysis extends _i1.Mock implements _i24.ImageAnalysis {
@override
_i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod(
Invocation.method(
@@ -551,7 +587,7 @@ class MockImageAnalysis extends _i1.Mock implements _i22.ImageAnalysis {
///
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
-class MockImageCapture extends _i1.Mock implements _i23.ImageCapture {
+class MockImageCapture extends _i1.Mock implements _i25.ImageCapture {
@override
_i16.Future setTargetRotation(int? rotation) => (super.noSuchMethod(
Invocation.method(
@@ -578,7 +614,7 @@ class MockImageCapture extends _i1.Mock implements _i23.ImageCapture {
#takePicture,
[],
),
- returnValue: _i16.Future.value(_i24.dummyValue(
+ returnValue: _i16.Future.value(_i26.dummyValue(
this,
Invocation.method(
#takePicture,
@@ -586,7 +622,7 @@ class MockImageCapture extends _i1.Mock implements _i23.ImageCapture {
),
)),
returnValueForMissingStub:
- _i16.Future.value(_i24.dummyValue(
+ _i16.Future