diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index e97f2a55030..4f63c42d1f9 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.0+34
+
+* Implements `setFocusPoint`, `setExposurePoint`, and `setExposureOffset`.
+
## 0.5.0+33
* Fixes typo in `README.md`.
diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md
index 13967faa200..48674de44cf 100644
--- a/packages/camera/camera_android_camerax/README.md
+++ b/packages/camera/camera_android_camerax/README.md
@@ -24,20 +24,19 @@ dependencies:
## Missing features and limitations
-
### 240p resolution configuration for video recording
240p resolution configuration for video recording is unsupported by CameraX,
and thus, the plugin will fall back to 480p if configured with a
`ResolutionPreset`.
-### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\]
+### Exposure mode configuration \[[Issue #120468][120468]\]
-`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.
+`setExposureMode`is unimplemented.
-### Focus mode & point configuration \[[Issue #120467][120467]\]
+### Focus mode configuration \[[Issue #120467][120467]\]
-`setFocusMode` & `setFocusPoint` are unimplemented.
+`setFocusMode` is unimplemented.
### Setting maximum duration and stream options for video capture
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 b0e0493cbe2..ea9f3ed4d06 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
@@ -27,6 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
@VisibleForTesting @Nullable public ImageCaptureHostApiImpl imageCaptureHostApiImpl;
@VisibleForTesting @Nullable public CameraControlHostApiImpl cameraControlHostApiImpl;
@VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl;
+ @VisibleForTesting @Nullable public MeteringPointHostApiImpl meteringPointHostApiImpl;
@VisibleForTesting
public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl;
@@ -119,6 +120,12 @@ public void setUp(
cameraControlHostApiImpl =
new CameraControlHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
+ GeneratedCameraXLibrary.FocusMeteringActionHostApi.setup(
+ binaryMessenger, new FocusMeteringActionHostApiImpl(instanceManager));
+ GeneratedCameraXLibrary.FocusMeteringResultHostApi.setup(
+ binaryMessenger, new FocusMeteringResultHostApiImpl(instanceManager));
+ meteringPointHostApiImpl = new MeteringPointHostApiImpl(instanceManager);
+ GeneratedCameraXLibrary.MeteringPointHostApi.setup(binaryMessenger, meteringPointHostApiImpl);
}
@Override
@@ -238,5 +245,8 @@ public void updateActivity(@Nullable Activity activity) {
if (deviceOrientationManagerHostApiImpl != null) {
deviceOrientationManagerHostApiImpl.setActivity(activity);
}
+ if (meteringPointHostApiImpl != null) {
+ meteringPointHostApiImpl.setActivity(activity);
+ }
}
}
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 ba26e91c962..ba4318a8927 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
@@ -83,6 +83,12 @@ public void onSuccess(Void voidResult) {
}
public void onFailure(Throwable t) {
+ if (t instanceof CameraControl.OperationCanceledException) {
+ // Operation was canceled due to camera being closed or a new request was submitted, which
+ // is not actionable and should not block a new value from potentially being submitted.
+ result.success(null);
+ return;
+ }
result.error(t);
}
},
@@ -94,6 +100,9 @@ public void onFailure(Throwable t) {
*
*
Will trigger an auto focus action and enable auto focus/auto exposure/auto white balance
* metering regions.
+ *
+ *
Will send a {@link GeneratedCameraXLibrary.Result} with a null result if operation was
+ * canceled.
*/
public void startFocusAndMetering(
@NonNull CameraControl cameraControl,
@@ -117,6 +126,12 @@ public void onSuccess(FocusMeteringResult focusMeteringResult) {
}
public void onFailure(Throwable t) {
+ if (t instanceof CameraControl.OperationCanceledException) {
+ // Operation was canceled due to camera being closed or a new request was submitted, which
+ // is not actionable and should not block a new value from potentially being submitted.
+ result.success(null);
+ return;
+ }
result.error(t);
}
},
@@ -152,6 +167,9 @@ public void onFailure(Throwable t) {
*
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.
+ *
+ *
Will send a {@link GeneratedCameraXLibrary.Result} with a null result if operation was
+ * canceled.
*/
public void setExposureCompensationIndex(
@NonNull CameraControl cameraControl, @NonNull Long index, @NonNull Result result) {
@@ -166,6 +184,12 @@ public void onSuccess(Integer integerResult) {
}
public void onFailure(Throwable t) {
+ if (t instanceof CameraControl.OperationCanceledException) {
+ // Operation was canceled due to camera being closed or a new request was submitted, which
+ // is not actionable and should not block a new value from potentially being submitted.
+ result.success(null);
+ return;
+ }
result.error(t);
}
},
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 08f8ea2da9f..ac92427314f 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
@@ -83,14 +83,14 @@ private CameraStateType(final int index) {
* If you need to add another type to support a type S to use a LiveData in this plugin,
* ensure the following is done on the Dart side:
*
- *
* In `../lib/src/live_data.dart`, add new cases for S in
+ *
* In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in
* `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of type S from a
* LiveData instance and in `LiveDataFlutterApiImpl#create` to create the expected type of
* LiveData when requested.
*
*
On the native side, ensure the following is done:
*
- *
* Update `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for
+ *
* Make sure `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for
* instances of type S. * Update `ObserverFlutterApiWrapper#onChanged` to properly handle
* receiving calls with instances of type S if a LiveData instance is observed.
*/
@@ -146,6 +146,24 @@ private VideoResolutionFallbackRule(final int index) {
}
}
+ /**
+ * The types of capture request options this plugin currently supports.
+ *
+ *
If you need to add another option to support, ensure the following is done on the Dart side:
+ *
+ *
* In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this
+ * option in `_CaptureRequestOptionsHostApiImpl#createFromInstances` to create the expected Map
+ * entry of option key index and value to send to the native side.
+ *
+ *
On the native side, ensure the following is done:
+ *
+ *
* Update `CaptureRequestOptionsHostApiImpl#create` to set the correct `CaptureRequest` key
+ * with a valid value type for this option.
+ *
+ *
See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest for the
+ * sorts of capture request options that can be supported via CameraX's interoperability with
+ * Camera2.
+ */
public enum CaptureRequestKeySupportedType {
CONTROL_AE_LOCK(0);
@@ -3899,7 +3917,11 @@ public void create(@NonNull Long identifierArg, @NonNull Reply callback) {
public interface MeteringPointHostApi {
void create(
- @NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size);
+ @NonNull Long identifier,
+ @NonNull Double x,
+ @NonNull Double y,
+ @Nullable Double size,
+ @NonNull Long cameraInfoId);
@NonNull
Double getDefaultPointSize();
@@ -3927,12 +3949,14 @@ static void setup(
Double xArg = (Double) args.get(1);
Double yArg = (Double) args.get(2);
Double sizeArg = (Double) args.get(3);
+ Number cameraInfoIdArg = (Number) args.get(4);
try {
api.create(
(identifierArg == null) ? null : identifierArg.longValue(),
xArg,
yArg,
- sizeArg);
+ sizeArg,
+ (cameraInfoIdArg == null) ? null : cameraInfoIdArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList wrappedError = wrapError(exception);
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java
index 56ffcfb2e39..36306253ed1 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointHostApiImpl.java
@@ -4,13 +4,20 @@
package io.flutter.plugins.camerax;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Build;
+import android.view.Display;
+import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.DisplayOrientedMeteringPointFactory;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
-import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.MeteringPointHostApi;
+import java.util.Objects;
/**
* Host API implementation for {@link MeteringPoint}.
@@ -25,17 +32,32 @@ public class MeteringPointHostApiImpl implements MeteringPointHostApi {
/** Proxy for constructor and static methods of {@link MeteringPoint}. */
@VisibleForTesting
public static class MeteringPointProxy {
+ Activity activity;
/**
* 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.
+ *
A {@link DisplayOrientedMeteringPointFactory} is used to construct the {@link
+ * MeteringPoint} because this factory handles the transformation of specified coordinates based
+ * on camera information and the device orientation automatically.
*/
@NonNull
- public MeteringPoint create(@NonNull Double x, @NonNull Double y, @Nullable Double size) {
- SurfaceOrientedMeteringPointFactory factory = getSurfaceOrientedMeteringPointFactory(1f, 1f);
+ public MeteringPoint create(
+ @NonNull Double x,
+ @NonNull Double y,
+ @Nullable Double size,
+ @NonNull CameraInfo cameraInfo) {
+ Display display = null;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ display = activity.getDisplay();
+ } else {
+ display = getDefaultDisplayForAndroidVersionBelowR(activity);
+ }
+
+ DisplayOrientedMeteringPointFactory factory =
+ getDisplayOrientedMeteringPointFactory(display, cameraInfo, 1f, 1f);
+
if (size == null) {
return factory.createPoint(x.floatValue(), y.floatValue());
} else {
@@ -43,11 +65,18 @@ public MeteringPoint create(@NonNull Double x, @NonNull Double y, @Nullable Doub
}
}
+ @NonNull
+ @SuppressWarnings("deprecation")
+ private Display getDefaultDisplayForAndroidVersionBelowR(@NonNull Activity activity) {
+ return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay();
+ }
+
@VisibleForTesting
@NonNull
- public SurfaceOrientedMeteringPointFactory getSurfaceOrientedMeteringPointFactory(
- float width, float height) {
- return new SurfaceOrientedMeteringPointFactory(width, height);
+ public DisplayOrientedMeteringPointFactory getDisplayOrientedMeteringPointFactory(
+ @NonNull Display display, @NonNull CameraInfo cameraInfo, float width, float height) {
+ return new DisplayOrientedMeteringPointFactory(display, cameraInfo, width, height);
}
/**
@@ -81,10 +110,23 @@ public MeteringPointHostApiImpl(@NonNull InstanceManager instanceManager) {
this.proxy = proxy;
}
+ public void setActivity(@NonNull Activity activity) {
+ this.proxy.activity = activity;
+ }
+
@Override
public void create(
- @NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size) {
- MeteringPoint meteringPoint = proxy.create(x, y, size);
+ @NonNull Long identifier,
+ @NonNull Double x,
+ @NonNull Double y,
+ @Nullable Double size,
+ @NonNull Long cameraInfoId) {
+ MeteringPoint meteringPoint =
+ proxy.create(
+ x,
+ y,
+ size,
+ (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId)));
instanceManager.addDartCreatedInstance(meteringPoint, identifier);
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java
index 8c9daf8e014..f9f4be6db6e 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java
@@ -92,6 +92,8 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
mock(SystemServicesHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
+ final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
+ mock(MeteringPointHostApiImpl.class);
final ArgumentCaptor permissionsRegistryCaptor =
ArgumentCaptor.forClass(PermissionsRegistry.class);
@@ -103,6 +105,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
+ plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;
plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onAttachedToActivity(activityPluginBinding);
@@ -110,6 +113,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
// Check Activity references are set.
verify(mockSystemServicesHostApiImpl).setActivity(mockActivity);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(mockActivity);
+ verify(mockMeteringPointHostApiImpl).setActivity(mockActivity);
// Check permissions registry reference is set.
verify(mockSystemServicesHostApiImpl)
@@ -129,11 +133,14 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
mock(SystemServicesHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
+ final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
+ mock(MeteringPointHostApiImpl.class);
plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
+ plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;
plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onDetachedFromActivityForConfigChanges();
@@ -142,6 +149,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
verify(mockLiveDataHostApiImpl).setLifecycleOwner(null);
verify(mockSystemServicesHostApiImpl).setActivity(null);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(null);
+ verify(mockMeteringPointHostApiImpl).setActivity(null);
}
@Test
@@ -251,6 +259,8 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg
mock(CameraControlHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
+ final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
+ mock(MeteringPointHostApiImpl.class);
final ArgumentCaptor permissionsRegistryCaptor =
ArgumentCaptor.forClass(PermissionsRegistry.class);
@@ -265,6 +275,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg
plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl;
plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
+ plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
plugin.onAttachedToEngine(flutterPluginBinding);
@@ -273,6 +284,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg
// Check Activity references are set.
verify(mockSystemServicesHostApiImpl).setActivity(mockActivity);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(mockActivity);
+ verify(mockMeteringPointHostApiImpl).setActivity(mockActivity);
// Check Activity as Context references are set.
verify(mockProcessCameraProviderHostApiImpl).setContext(mockActivity);
@@ -300,11 +312,14 @@ public void onDetachedFromActivity_removesReferencesToActivityPluginBindingAndAc
final LiveDataHostApiImpl mockLiveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
+ final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
+ mock(MeteringPointHostApiImpl.class);
plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
+ plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;
plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onDetachedFromActivityForConfigChanges();
@@ -313,6 +328,7 @@ public void onDetachedFromActivity_removesReferencesToActivityPluginBindingAndAc
verify(mockLiveDataHostApiImpl).setLifecycleOwner(null);
verify(mockSystemServicesHostApiImpl).setActivity(null);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(null);
+ verify(mockMeteringPointHostApiImpl).setActivity(null);
}
@Test
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 42ace7a51fb..a0fe8f977ee 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
@@ -149,6 +149,22 @@ public void setZoomRatio_setsZoomAsExpected() {
failedSetZoomRatioCallback.onFailure(testThrowable);
verify(failedMockResult).error(testThrowable);
+
+ // Test response to canceled operation.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result canceledOpResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ final CameraControl.OperationCanceledException canceledOpThrowable =
+ mock(CameraControl.OperationCanceledException.class);
+ cameraControlHostApiImpl.setZoomRatio(cameraControlIdentifier, zoomRatio, canceledOpResult);
+ mockedFutures.verify(
+ () -> Futures.addCallback(eq(setZoomRatioFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback canceledOpCallback = futureCallbackCaptor.getValue();
+
+ canceledOpCallback.onFailure(canceledOpThrowable);
+ verify(canceledOpResult).success(null);
}
}
@@ -212,6 +228,25 @@ public void startFocusAndMetering_startsFocusAndMeteringAsExpected() {
failedCallback.onFailure(testThrowable);
verify(failedMockResult).error(testThrowable);
+
+ // Test response to canceled operation.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result canceledOpResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ final CameraControl.OperationCanceledException canceledOpThrowable =
+ mock(CameraControl.OperationCanceledException.class);
+ cameraControlHostApiImpl.startFocusAndMetering(
+ cameraControlIdentifier, mockActionId, canceledOpResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(startFocusAndMeteringFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback canceledOpCallback = futureCallbackCaptor.getValue();
+
+ canceledOpCallback.onFailure(canceledOpThrowable);
+ verify(canceledOpResult).success(null);
}
}
@@ -326,6 +361,25 @@ public void setExposureCompensationIndex_setsExposureCompensationIndexAsExpected
failedCallback.onFailure(testThrowable);
verify(failedMockResult).error(testThrowable);
+
+ // Test response to canceled operation.
+ @SuppressWarnings("unchecked")
+ final GeneratedCameraXLibrary.Result canceledOpResult =
+ mock(GeneratedCameraXLibrary.Result.class);
+ final CameraControl.OperationCanceledException canceledOpThrowable =
+ mock(CameraControl.OperationCanceledException.class);
+ cameraControlHostApiImpl.setExposureCompensationIndex(
+ cameraControlIdentifier, index, canceledOpResult);
+ mockedFutures.verify(
+ () ->
+ Futures.addCallback(
+ eq(setExposureCompensationIndexFuture), futureCallbackCaptor.capture(), any()));
+ mockedFutures.clearInvocations();
+
+ FutureCallback canceledOpCallback = futureCallbackCaptor.getValue();
+
+ canceledOpCallback.onFailure(canceledOpThrowable);
+ verify(canceledOpResult).success(null);
}
}
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
index f245eb53124..0734f6ba6a9 100644
--- 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
@@ -10,21 +10,30 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
+import android.content.Context;
+import android.view.Display;
+import android.view.WindowManager;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.DisplayOrientedMeteringPointFactory;
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.junit.runner.RunWith;
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;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+@RunWith(RobolectricTestRunner.class)
public class MeteringPointTest {
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@@ -44,49 +53,158 @@ public void tearDown() {
}
@Test
- public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified() {
- MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
+ @Config(sdk = 30)
+ public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified_AboveAndroid30() {
+ final MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
spy(new MeteringPointHostApiImpl.MeteringPointProxy());
- MeteringPointHostApiImpl hostApi = new MeteringPointHostApiImpl(testInstanceManager, proxySpy);
+ final 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 x = 0.25f;
+ final Float y = 0.18f;
+ final Float size = 0.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);
+ final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory =
+ mock(DisplayOrientedMeteringPointFactory.class);
+ final Activity mockActivity = mock(Activity.class);
+ final Display mockDisplay = mock(Display.class);
+ final CameraInfo mockCameraInfo = mock(CameraInfo.class);
+ final long mockCameraInfoId = 55L;
+
+ hostApi.setActivity(mockActivity);
+ testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId);
+
+ when(mockActivity.getDisplay()).thenReturn(mockDisplay);
+ when(proxySpy.getDisplayOrientedMeteringPointFactory(
+ mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight))
+ .thenReturn(mockDisplayOrientedMeteringPointFactory);
+ when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y, size)).thenReturn(meteringPoint);
+
+ hostApi.create(
+ meteringPointIdentifier,
+ x.doubleValue(),
+ y.doubleValue(),
+ size.doubleValue(),
+ mockCameraInfoId);
+
+ verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y, size);
assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint);
}
@Test
- public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified() {
- MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
+ @Config(sdk = 29)
+ @SuppressWarnings("deprecation")
+ public void hostApiCreate_createsExpectedMeteringPointWithSizeSpecified_BelowAndroid30() {
+ final MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
spy(new MeteringPointHostApiImpl.MeteringPointProxy());
- MeteringPointHostApiImpl hostApi = new MeteringPointHostApiImpl(testInstanceManager, proxySpy);
+ final 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)).thenReturn(meteringPoint);
+ final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory =
+ mock(DisplayOrientedMeteringPointFactory.class);
+ final Activity mockActivity = mock(Activity.class);
+ final WindowManager mockWindowManager = mock(WindowManager.class);
+ final Display mockDisplay = mock(Display.class);
+ final CameraInfo mockCameraInfo = mock(CameraInfo.class);
+ final long mockCameraInfoId = 5L;
+
+ hostApi.setActivity(mockActivity);
+ testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId);
+
+ when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager);
+ when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay);
+ when(proxySpy.getDisplayOrientedMeteringPointFactory(
+ mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight))
+ .thenReturn(mockDisplayOrientedMeteringPointFactory);
+ when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y, size)).thenReturn(meteringPoint);
+
+ hostApi.create(
+ meteringPointIdentifier,
+ x.doubleValue(),
+ y.doubleValue(),
+ size.doubleValue(),
+ mockCameraInfoId);
+
+ verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y, size);
+ assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint);
+ }
- hostApi.create(meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null);
+ @Test
+ @Config(sdk = 30)
+ public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified_AboveAndroid30() {
+ final MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
+ spy(new MeteringPointHostApiImpl.MeteringPointProxy());
+ final MeteringPointHostApiImpl hostApi =
+ new MeteringPointHostApiImpl(testInstanceManager, proxySpy);
+ final Long meteringPointIdentifier = 78L;
+ final Float x = 0.23f;
+ final Float y = 0.32f;
+ final Float surfaceWidth = 1f;
+ final Float surfaceHeight = 1f;
+ final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory =
+ mock(DisplayOrientedMeteringPointFactory.class);
+ final Activity mockActivity = mock(Activity.class);
+ final Display mockDisplay = mock(Display.class);
+ final CameraInfo mockCameraInfo = mock(CameraInfo.class);
+ final long mockCameraInfoId = 6L;
+
+ hostApi.setActivity(mockActivity);
+ testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId);
+
+ when(mockActivity.getDisplay()).thenReturn(mockDisplay);
+ when(proxySpy.getDisplayOrientedMeteringPointFactory(
+ mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight))
+ .thenReturn(mockDisplayOrientedMeteringPointFactory);
+ when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y)).thenReturn(meteringPoint);
+
+ hostApi.create(
+ meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null, mockCameraInfoId);
+
+ verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y);
+ assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint);
+ }
- verify(mockSurfaceOrientedMeteringPointFactory).createPoint(x, y);
+ @Test
+ @Config(sdk = 29)
+ @SuppressWarnings("deprecation")
+ public void hostApiCreate_createsExpectedMeteringPointWithoutSizeSpecified_BelowAndroid30() {
+ final MeteringPointHostApiImpl.MeteringPointProxy proxySpy =
+ spy(new MeteringPointHostApiImpl.MeteringPointProxy());
+ final MeteringPointHostApiImpl hostApi =
+ new MeteringPointHostApiImpl(testInstanceManager, proxySpy);
+ final Long meteringPointIdentifier = 78L;
+ final Float x = 0.1f;
+ final Float y = 0.8f;
+ final Float surfaceWidth = 1f;
+ final Float surfaceHeight = 1f;
+ final DisplayOrientedMeteringPointFactory mockDisplayOrientedMeteringPointFactory =
+ mock(DisplayOrientedMeteringPointFactory.class);
+ final Activity mockActivity = mock(Activity.class);
+ final WindowManager mockWindowManager = mock(WindowManager.class);
+ final Display mockDisplay = mock(Display.class);
+ final CameraInfo mockCameraInfo = mock(CameraInfo.class);
+ final long mockCameraInfoId = 7L;
+
+ hostApi.setActivity(mockActivity);
+ testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoId);
+
+ when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager);
+ when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay);
+ when(proxySpy.getDisplayOrientedMeteringPointFactory(
+ mockDisplay, mockCameraInfo, surfaceWidth, surfaceHeight))
+ .thenReturn(mockDisplayOrientedMeteringPointFactory);
+ when(mockDisplayOrientedMeteringPointFactory.createPoint(x, y)).thenReturn(meteringPoint);
+
+ hostApi.create(
+ meteringPointIdentifier, x.doubleValue(), y.doubleValue(), null, mockCameraInfoId);
+
+ verify(mockDisplayOrientedMeteringPointFactory).createPoint(x, y);
assertEquals(testInstanceManager.getInstance(meteringPointIdentifier), meteringPoint);
}
diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart
index 4484c5dac98..adb86837371 100644
--- a/packages/camera/camera_android_camerax/example/lib/main.dart
+++ b/packages/camera/camera_android_camerax/example/lib/main.dart
@@ -282,14 +282,15 @@ class _CameraExampleHomeState extends State
IconButton(
icon: const Icon(Icons.exposure),
color: Colors.blue,
- onPressed:
- () {}, // TODO(camsim99): Add functionality back here.
+ onPressed: controller != null
+ ? onExposureModeButtonPressed
+ : null,
),
IconButton(
icon: const Icon(Icons.filter_center_focus),
color: Colors.blue,
onPressed:
- () {}, // TODO(camsim99): Add functionality back here.
+ controller != null ? onFocusModeButtonPressed : null,
)
]
: [],
@@ -394,8 +395,13 @@ class _CameraExampleHomeState extends State
style: styleAuto,
onPressed:
() {}, // TODO(camsim99): Add functionality back here.
- onLongPress:
- () {}, // TODO(camsim99): Add functionality back here.,
+ onLongPress: () {
+ if (controller != null) {
+ CameraPlatform.instance
+ .setExposurePoint(controller!.cameraId, null);
+ showInSnackBar('Resetting exposure point');
+ }
+ },
child: const Text('AUTO'),
),
TextButton(
@@ -406,8 +412,9 @@ class _CameraExampleHomeState extends State
),
TextButton(
style: styleLocked,
- onPressed:
- () {}, // TODO(camsim99): Add functionality back here.
+ onPressed: controller != null
+ ? () => controller!.setExposureOffset(0.0)
+ : null,
child: const Text('RESET OFFSET'),
),
],
@@ -468,8 +475,13 @@ class _CameraExampleHomeState extends State
style: styleAuto,
onPressed:
() {}, // TODO(camsim99): Add functionality back here.
- onLongPress:
- () {}, // TODO(camsim99): Add functionality back here.
+ onLongPress: () {
+ if (controller != null) {
+ CameraPlatform.instance
+ .setFocusPoint(controller!.cameraId, null);
+ }
+ showInSnackBar('Resetting focus point');
+ },
child: const Text('AUTO'),
),
TextButton(
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
index b05788d68a4..deddf4709c9 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
@@ -3,10 +3,12 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:math' show Point;
import 'package:async/async.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
-import 'package:flutter/services.dart' show DeviceOrientation;
+import 'package:flutter/services.dart'
+ show DeviceOrientation, PlatformException;
import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';
@@ -21,10 +23,12 @@ import 'camerax_proxy.dart';
import 'device_orientation_manager.dart';
import 'exposure_state.dart';
import 'fallback_strategy.dart';
+import 'focus_metering_action.dart';
import 'image_analysis.dart';
import 'image_capture.dart';
import 'image_proxy.dart';
import 'live_data.dart';
+import 'metering_point.dart';
import 'observer.dart';
import 'pending_recording.dart';
import 'plane_proxy.dart';
@@ -69,6 +73,9 @@ class AndroidCameraCameraX extends CameraPlatform {
@visibleForTesting
CameraInfo? cameraInfo;
+ /// The [CameraControl] instance that corresponds to the [camera] instance.
+ late CameraControl cameraControl;
+
/// The [LiveData] of the [CameraState] that represents the state of the
/// [camera] instance.
LiveData? liveCameraState;
@@ -180,6 +187,16 @@ class AndroidCameraCameraX extends CameraPlatform {
/// for an example on how setting target rotations for [UseCase]s works.
bool shouldSetDefaultRotation = false;
+ /// The currently set [FocusMeteringAction] used to enable auto-focus and
+ /// auto-exposure.
+ @visibleForTesting
+ FocusMeteringAction? currentFocusMeteringAction;
+
+ /// Error code indicating that exposure compensation is not supported by
+ /// CameraX for the device.
+ static const String exposureCompensationNotSupported =
+ 'exposureCompensationNotSupported';
+
/// Returns list of all available cameras and their descriptions.
@override
Future> availableCameras() async {
@@ -430,6 +447,18 @@ class AndroidCameraCameraX extends CameraPlatform {
captureOrientationLocked = false;
}
+ /// Sets the exposure point for automatically determining the exposure values.
+ ///
+ /// Supplying `null` for the [point] argument will result in resetting to the
+ /// original exposure point value.
+ ///
+ /// [cameraId] is not used.
+ @override
+ Future setExposurePoint(int cameraId, Point? point) async {
+ await _startFocusAndMeteringFor(
+ point: point, meteringMode: FocusMeteringAction.flagAe);
+ }
+
/// Gets the minimum supported exposure offset for the selected camera in EV units.
///
/// [cameraId] not used.
@@ -452,13 +481,71 @@ class AndroidCameraCameraX extends CameraPlatform {
/// Gets the supported step size for exposure offset for the selected camera in EV units.
///
- /// Returns 0 when exposure compensation is not supported.
+ /// Returns -1 if exposure compensation is not supported for the device.
///
/// [cameraId] not used.
@override
Future getExposureOffsetStepSize(int cameraId) async {
final ExposureState exposureState = await cameraInfo!.getExposureState();
- return exposureState.exposureCompensationStep;
+ final double exposureOffsetStepSize =
+ exposureState.exposureCompensationStep;
+ if (exposureOffsetStepSize == 0) {
+ // CameraX returns a step size of 0 if exposure compensation is not
+ // supported for the device.
+ return -1;
+ }
+ return exposureOffsetStepSize;
+ }
+
+ /// Sets the exposure offset for the selected camera.
+ ///
+ /// The supplied [offset] value should be in EV units. 1 EV unit represents a
+ /// doubling in brightness. It should be between the minimum and maximum offsets
+ /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively.
+ /// Throws a `CameraException` when trying to set exposure offset on a device
+ /// that doesn't support exposure compensationan or if setting the offset fails,
+ /// like in the case that an illegal offset is supplied.
+ ///
+ /// When the supplied [offset] value does not align with the step size obtained
+ /// through `getExposureStepSize`, it will automatically be rounded to the nearest step.
+ ///
+ /// Returns the (rounded) offset value that was set.
+ @override
+ Future setExposureOffset(int cameraId, double offset) async {
+ final double exposureOffsetStepSize =
+ (await cameraInfo!.getExposureState()).exposureCompensationStep;
+ if (exposureOffsetStepSize == 0) {
+ throw CameraException(exposureCompensationNotSupported,
+ 'Exposure compensation not supported');
+ }
+
+ // (Exposure compensation index) * (exposure offset step size) =
+ // (exposure offset).
+ final int roundedExposureCompensationIndex =
+ (offset / exposureOffsetStepSize).round();
+
+ try {
+ await cameraControl
+ .setExposureCompensationIndex(roundedExposureCompensationIndex);
+ } on PlatformException catch (e) {
+ throw CameraException(
+ 'setExposureOffsetFailed',
+ e.message ??
+ 'Setting the camera exposure compensation index failed.');
+ }
+ return roundedExposureCompensationIndex * exposureOffsetStepSize;
+ }
+
+ /// Sets the focus point for automatically determining the focus values.
+ ///
+ /// Supplying `null` for the [point] argument will result in resetting to the
+ /// original focus point value.
+ ///
+ /// [cameraId] is not used.
+ @override
+ Future setFocusPoint(int cameraId, Point? point) async {
+ await _startFocusAndMeteringFor(
+ point: point, meteringMode: FocusMeteringAction.flagAf);
}
/// Gets the maximum supported zoom level for the selected camera.
@@ -502,7 +589,6 @@ class AndroidCameraCameraX extends CameraPlatform {
/// Throws a `CameraException` when an illegal zoom level is supplied.
@override
Future setZoomLevel(int cameraId, double zoom) async {
- final CameraControl cameraControl = await camera!.getCameraControl();
await cameraControl.setZoomRatio(zoom);
}
@@ -587,10 +673,8 @@ class AndroidCameraCameraX extends CameraPlatform {
/// respectively.
@override
Future setFlashMode(int cameraId, FlashMode mode) async {
- CameraControl? cameraControl;
// Turn off torch mode if it is enabled and not being redundantly set.
if (mode != FlashMode.torch && torchEnabled) {
- cameraControl = await camera!.getCameraControl();
await cameraControl.enableTorch(false);
torchEnabled = false;
}
@@ -608,7 +692,6 @@ class AndroidCameraCameraX extends CameraPlatform {
// Torch mode enabled already.
return;
}
- cameraControl = await camera!.getCameraControl();
await cameraControl.enableTorch(true);
torchEnabled = true;
}
@@ -833,14 +916,15 @@ class AndroidCameraCameraX extends CameraPlatform {
// Methods concerning camera state:
- /// Updates [cameraInfo] to the information corresponding to [camera] and
- /// adds observers to the [LiveData] of the [CameraState] of the current
- /// [camera], saved as [liveCameraState].
+ /// Updates [cameraInfo] and [cameraControl] to the information corresponding
+ /// to [camera] and adds observers to the [LiveData] of the [CameraState] of
+ /// the current [camera], saved as [liveCameraState].
///
/// If a previous [liveCameraState] was stored, existing observers are
/// removed, as well.
Future _updateCameraInfoAndLiveCameraState(int cameraId) async {
cameraInfo = await camera!.getCameraInfo();
+ cameraControl = await camera!.getCameraControl();
await liveCameraState?.removeObservers();
liveCameraState = await cameraInfo!.getCameraState();
await liveCameraState!.observe(_createCameraClosingObserver(cameraId));
@@ -981,4 +1065,87 @@ class AndroidCameraCameraX extends CameraPlatform {
return proxy.createQualitySelector(
videoQuality: videoQuality, fallbackStrategy: fallbackStrategy);
}
+
+ // Methods for configuring auto-focus and auto-exposure:
+
+ /// Starts a focus and metering action.
+ ///
+ /// This method will modify and start the current action's metering points
+ /// overriden with the [point] provided for the specified [meteringMode] type
+ /// only, with all other points of other modes left untouched. Thus, the
+ /// focus and metering action started will contain only the one most recently
+ /// set point for each metering mode: AF, AE, AWB.
+ ///
+ /// Thus, if [point] is non-null, this action includes:
+ /// * metering points and their modes previously added to
+ /// [currentFocusMeteringAction] that do not share a metering mode with
+ /// [point] and
+ /// * [point] with the specified [meteringMode].
+ /// If [point] is null, this action includes only metering points and
+ /// their modes previously added to [currentFocusMeteringAction] that do not
+ /// share a metering mode with [point]. If there are no such metering
+ /// points, then the previously enabled focus and metering actions will be
+ /// canceled.
+ Future _startFocusAndMeteringFor(
+ {required Point? point, required int meteringMode}) async {
+ if (point == null) {
+ // Try to clear any metering point from previous action with the specified
+ // meteringMode.
+ if (currentFocusMeteringAction == null) {
+ // Attempting to clear a metering point from a previous action, but no
+ // such action exists.
+ return;
+ }
+
+ // Remove metering point with specified meteringMode from current focus
+ // and metering action, as only one focus or exposure point may be set
+ // at once in this plugin.
+ final List<(MeteringPoint, int?)> newMeteringPointInfos =
+ currentFocusMeteringAction!.meteringPointInfos
+ .where(((MeteringPoint, int?) meteringPointInfo) =>
+ // meteringPointInfo may technically include points without a
+ // mode specified, but this logic is safe because this plugin
+ // only uses points that explicitly have mode
+ // FocusMeteringAction.flagAe or FocusMeteringAction.flagAf.
+ meteringPointInfo.$2 != meteringMode)
+ .toList();
+
+ if (newMeteringPointInfos.isEmpty) {
+ // If no other metering points were specified, cancel any previously
+ // started focus and metering actions.
+ await cameraControl.cancelFocusAndMetering();
+ currentFocusMeteringAction = null;
+ return;
+ }
+ currentFocusMeteringAction =
+ proxy.createFocusMeteringAction(newMeteringPointInfos);
+ } else if (point.x < 0 || point.x > 1 || point.y < 0 || point.y > 1) {
+ throw CameraException('pointInvalid',
+ 'The coordinates of a metering point for an auto-focus or auto-exposure action must be within (0,0) and (1,1), but point $point was provided for metering mode $meteringMode.');
+ } else {
+ // Add new metering point with specified meteringMode, which may involve
+ // replacing a metering point with the same specified meteringMode from
+ // the current focus and metering action.
+ List<(MeteringPoint, int?)> newMeteringPointInfos =
+ <(MeteringPoint, int?)>[];
+
+ if (currentFocusMeteringAction != null) {
+ newMeteringPointInfos = currentFocusMeteringAction!.meteringPointInfos
+ .where(((MeteringPoint, int?) meteringPointInfo) =>
+ // meteringPointInfo may technically include points without a
+ // mode specified, but this logic is safe because this plugin
+ // only uses points that explicitly have mode
+ // FocusMeteringAction.flagAe or FocusMeteringAction.flagAf.
+ meteringPointInfo.$2 != meteringMode)
+ .toList();
+ }
+ final MeteringPoint newMeteringPoint =
+ proxy.createMeteringPoint(point.x, point.y, cameraInfo!);
+ newMeteringPointInfos.add((newMeteringPoint, meteringMode));
+ currentFocusMeteringAction =
+ proxy.createFocusMeteringAction(newMeteringPointInfos);
+ }
+
+ await cameraControl.startFocusAndMetering(currentFocusMeteringAction!);
+ }
}
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 9c50ffa161f..a233011975f 100644
--- a/packages/camera/camera_android_camerax/lib/src/camera_control.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camera_control.dart
@@ -56,6 +56,10 @@ class CameraControl extends JavaObject {
/// Will trigger an auto focus action and enable auto focus/auto exposure/
/// auto white balance metering regions.
///
+ /// Only one [FocusMeteringAction] is allowed to run at a time; if multiple
+ /// are executed in a row, only the latest one will work and other actions
+ /// will be canceled.
+ ///
/// Returns null if focus and metering could not be started.
Future startFocusAndMetering(
FocusMeteringAction action) {
@@ -74,6 +78,10 @@ class CameraControl extends JavaObject {
/// of the current [ExposureState]'s `exposureCompensationRange` for the call
/// to succeed.
///
+ /// Only one [setExposureCompensationIndex] is allowed to run at a time; if
+ /// multiple are executed in a row, only the latest setting will be kept in
+ /// the camera.
+ ///
/// Returns null if the exposure compensation index failed to be set.
Future setExposureCompensationIndex(int index) async {
return _api.setExposureCompensationIndexFromInstance(this, index);
@@ -133,8 +141,13 @@ class _CameraControlHostApiImpl extends CameraControlHostApi {
instanceManager.getIdentifier(instance)!;
final int actionIdentifier = instanceManager.getIdentifier(action)!;
try {
- final int focusMeteringResultId = await startFocusAndMetering(
+ final int? focusMeteringResultId = await startFocusAndMetering(
cameraControlIdentifier, actionIdentifier);
+ if (focusMeteringResultId == null) {
+ SystemServices.cameraErrorStreamController.add(
+ 'Starting focus and metering was canceled due to the camera being closed or a new request being submitted.');
+ return Future.value();
+ }
return instanceManager.getInstanceWithWeakReference(
focusMeteringResultId);
} on PlatformException catch (e) {
@@ -158,11 +171,20 @@ class _CameraControlHostApiImpl extends CameraControlHostApi {
CameraControl instance, int index) async {
final int identifier = instanceManager.getIdentifier(instance)!;
try {
- return setExposureCompensationIndex(identifier, index);
+ final int? exposureCompensationIndex =
+ await setExposureCompensationIndex(identifier, index);
+ if (exposureCompensationIndex == null) {
+ SystemServices.cameraErrorStreamController.add(
+ 'Setting exposure compensation index was canceled due to the camera being closed or a new request being submitted.');
+ return Future.value();
+ }
+ return exposureCompensationIndex;
} on PlatformException catch (e) {
SystemServices.cameraErrorStreamController.add(e.message ??
'Setting the camera exposure compensation index failed.');
- return Future.value();
+ // Surfacing error to plugin layer to maintain consistency of
+ // setExposureOffset implementation across platform implementations.
+ rethrow;
}
}
}
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
index 07515c0c14f..14239dd01b6 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
@@ -27,14 +27,14 @@ enum CameraStateType {
/// If you need to add another type to support a type S to use a LiveData in
/// this plugin, ensure the following is done on the Dart side:
///
-/// * In `../lib/src/live_data.dart`, add new cases for S in
+/// * In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in
/// `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of
/// type S from a LiveData instance and in `LiveDataFlutterApiImpl#create`
/// to create the expected type of LiveData when requested.
///
/// On the native side, ensure the following is done:
///
-/// * Update `LiveDataHostApiImpl#getValue` is updated to properly return
+/// * Make sure `LiveDataHostApiImpl#getValue` is updated to properly return
/// identifiers for instances of type S.
/// * Update `ObserverFlutterApiWrapper#onChanged` to properly handle receiving
/// calls with instances of type S if a LiveData instance is observed.
@@ -68,6 +68,24 @@ enum VideoResolutionFallbackRule {
lowerQualityThan,
}
+/// The types of capture request options this plugin currently supports.
+///
+/// If you need to add another option to support, ensure the following is done
+/// on the Dart side:
+///
+/// * In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this
+/// option in `_CaptureRequestOptionsHostApiImpl#createFromInstances`
+/// to create the expected Map entry of option key index and value to send to
+/// the native side.
+///
+/// On the native side, ensure the following is done:
+///
+/// * Update `CaptureRequestOptionsHostApiImpl#create` to set the correct
+/// `CaptureRequest` key with a valid value type for this option.
+///
+/// See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest
+/// for the sorts of capture request options that can be supported via CameraX's
+/// interoperability with Camera2.
enum CaptureRequestKeySupportedType {
controlAeLock,
}
@@ -2884,7 +2902,7 @@ class CameraControlHostApi {
}
}
- Future startFocusAndMetering(
+ Future startFocusAndMetering(
int arg_identifier, int arg_focusMeteringActionId) async {
final BasicMessageChannel channel = BasicMessageChannel(
'dev.flutter.pigeon.CameraControlHostApi.startFocusAndMetering', codec,
@@ -2903,13 +2921,8 @@ class CameraControlHostApi {
message: replyList[1] as String?,
details: replyList[2],
);
- } else if (replyList[0] == null) {
- throw PlatformException(
- code: 'null-error',
- message: 'Host platform returned null value for non-null return value.',
- );
} else {
- return (replyList[0] as int?)!;
+ return (replyList[0] as int?);
}
}
@@ -2935,7 +2948,7 @@ class CameraControlHostApi {
}
}
- Future setExposureCompensationIndex(
+ Future setExposureCompensationIndex(
int arg_identifier, int arg_index) async {
final BasicMessageChannel channel = BasicMessageChannel(
'dev.flutter.pigeon.CameraControlHostApi.setExposureCompensationIndex',
@@ -2954,13 +2967,8 @@ class CameraControlHostApi {
message: replyList[1] as String?,
details: replyList[2],
);
- } else if (replyList[0] == null) {
- throw PlatformException(
- code: 'null-error',
- message: 'Host platform returned null value for non-null return value.',
- );
} else {
- return (replyList[0] as int?)!;
+ return (replyList[0] as int?);
}
}
}
@@ -3130,14 +3138,14 @@ class MeteringPointHostApi {
static const MessageCodec codec = StandardMessageCodec();
- Future create(
- int arg_identifier, double arg_x, double arg_y, double? arg_size) async {
+ Future create(int arg_identifier, double arg_x, double arg_y,
+ double? arg_size, int arg_cameraInfoId) 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?;
+ final List? replyList = await channel.send(
+ [arg_identifier, arg_x, arg_y, arg_size, arg_cameraInfoId])
+ as List?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart
index 87d3680ff2c..072a753d8b4 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart
@@ -5,14 +5,17 @@
import 'dart:ui' show Size;
import 'analyzer.dart';
+import 'camera_info.dart';
import 'camera_selector.dart';
import 'camera_state.dart';
import 'camerax_library.g.dart';
import 'device_orientation_manager.dart';
import 'fallback_strategy.dart';
+import 'focus_metering_action.dart';
import 'image_analysis.dart';
import 'image_capture.dart';
import 'image_proxy.dart';
+import 'metering_point.dart';
import 'observer.dart';
import 'preview.dart';
import 'process_camera_provider.dart';
@@ -49,6 +52,8 @@ class CameraXProxy {
_startListeningForDeviceOrientationChange,
this.setPreviewSurfaceProvider = _setPreviewSurfaceProvider,
this.getDefaultDisplayRotation = _getDefaultDisplayRotation,
+ this.createMeteringPoint = _createMeteringPoint,
+ this.createFocusMeteringAction = _createFocusMeteringAction,
});
/// Returns a [ProcessCameraProvider] instance.
@@ -137,6 +142,16 @@ class CameraXProxy {
/// rotation constants.
Future Function() getDefaultDisplayRotation;
+ /// Returns a [MeteringPoint] with the specified coordinates based on
+ /// [cameraInfo].
+ MeteringPoint Function(double x, double y, CameraInfo cameraInfo)
+ createMeteringPoint;
+
+ /// Returns a [FocusMeteringAction] based on the specified metering points
+ /// and their modes.
+ FocusMeteringAction Function(List<(MeteringPoint, int?)> meteringPointInfos)
+ createFocusMeteringAction;
+
static Future _getProcessCameraProvider() {
return ProcessCameraProvider.getInstance();
}
@@ -239,4 +254,14 @@ class CameraXProxy {
static Future _getDefaultDisplayRotation() async {
return DeviceOrientationManager.getDefaultDisplayRotation();
}
+
+ static MeteringPoint _createMeteringPoint(
+ double x, double y, CameraInfo cameraInfo) {
+ return MeteringPoint(x: x, y: y, cameraInfo: cameraInfo);
+ }
+
+ static FocusMeteringAction _createFocusMeteringAction(
+ List<(MeteringPoint, int?)> meteringPointInfos) {
+ return FocusMeteringAction(meteringPointInfos: meteringPointInfos);
+ }
}
diff --git a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart
index 6d9ebd97f01..fe09ebc0443 100644
--- a/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart
+++ b/packages/camera/camera_android_camerax/lib/src/focus_metering_action.dart
@@ -19,8 +19,7 @@ class FocusMeteringAction extends JavaObject {
FocusMeteringAction({
BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
- required List<(MeteringPoint meteringPoint, int? meteringMode)>
- meteringPointInfos,
+ required this.meteringPointInfos,
}) : super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
@@ -35,6 +34,7 @@ class FocusMeteringAction extends JavaObject {
FocusMeteringAction.detached({
BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
+ required this.meteringPointInfos,
}) : super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
@@ -45,6 +45,11 @@ class FocusMeteringAction extends JavaObject {
late final _FocusMeteringActionHostApiImpl _api;
+ /// The requested [MeteringPoint]s and modes that are relevant to each of those
+ /// points.
+ final List<(MeteringPoint meteringPoint, int? meteringMode)>
+ meteringPointInfos;
+
/// Flag for metering mode that indicates the auto focus region is enabled.
///
/// An autofocus scan is also triggered when [flagAf] is assigned.
@@ -97,7 +102,9 @@ class _FocusMeteringActionHostApiImpl extends FocusMeteringActionHostApi {
final int identifier = instanceManager.addDartCreatedInstance(instance,
onCopy: (FocusMeteringAction original) {
return FocusMeteringAction.detached(
- binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ binaryMessenger: binaryMessenger,
+ instanceManager: instanceManager,
+ meteringPointInfos: original.meteringPointInfos);
});
final List meteringPointInfosWithIds =
diff --git a/packages/camera/camera_android_camerax/lib/src/metering_point.dart b/packages/camera/camera_android_camerax/lib/src/metering_point.dart
index d699993a893..c5ee6061de9 100644
--- a/packages/camera/camera_android_camerax/lib/src/metering_point.dart
+++ b/packages/camera/camera_android_camerax/lib/src/metering_point.dart
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart' show BinaryMessenger;
import 'package:meta/meta.dart' show immutable;
import 'android_camera_camerax_flutter_api_impls.dart';
+import 'camera_info.dart';
import 'camerax_library.g.dart';
import 'instance_manager.dart';
import 'java_object.dart';
@@ -23,13 +24,14 @@ class MeteringPoint extends JavaObject {
required this.x,
required this.y,
this.size,
+ required this.cameraInfo,
}) : super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
) {
_api = _MeteringPointHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
- _api.createFromInstance(this, x, y, size);
+ _api.createFromInstance(this, x, y, size, cameraInfo);
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
}
@@ -41,6 +43,7 @@ class MeteringPoint extends JavaObject {
required this.x,
required this.y,
this.size,
+ required this.cameraInfo,
}) : super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
@@ -63,6 +66,10 @@ class MeteringPoint extends JavaObject {
/// region width/height if crop region is set).
final double? size;
+ /// The [CameraInfo] used to construct the metering point with a display-
+ /// oriented metering point factory.
+ final CameraInfo cameraInfo;
+
/// 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).
@@ -100,8 +107,8 @@ class _MeteringPointHostApiImpl extends MeteringPointHostApi {
/// 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) {
+ Future createFromInstance(MeteringPoint instance, double x, double y,
+ double? size, CameraInfo cameraInfo) {
int? identifier = instanceManager.getIdentifier(instance);
identifier ??= instanceManager.addDartCreatedInstance(instance,
onCopy: (MeteringPoint original) {
@@ -110,9 +117,11 @@ class _MeteringPointHostApiImpl extends MeteringPointHostApi {
instanceManager: instanceManager,
x: original.x,
y: original.y,
+ cameraInfo: original.cameraInfo,
size: original.size);
});
+ final int? camInfoId = instanceManager.getIdentifier(cameraInfo);
- return create(identifier, x, y, size);
+ return create(identifier, x, y, size, camInfoId!);
}
}
diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
index db6164044d3..fc78cae75d2 100644
--- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
+++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
@@ -66,7 +66,7 @@ class CameraStateTypeData {
/// If you need to add another type to support a type S to use a LiveData in
/// this plugin, ensure the following is done on the Dart side:
///
-/// * In `../lib/src/live_data.dart`, add new cases for S in
+/// * In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in
/// `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of
/// type S from a LiveData instance and in `LiveDataFlutterApiImpl#create`
/// to create the expected type of LiveData when requested.
@@ -148,7 +148,7 @@ class MeteringPointInfo {
/// If you need to add another option to support, ensure the following is done
/// on the Dart side:
///
-/// * In `../lib/src/capture_request_options.dart`, add new cases for this
+/// * In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this
/// option in `_CaptureRequestOptionsHostApiImpl#createFromInstances`
/// to create the expected Map entry of option key index and value to send to
/// the native side.
@@ -485,13 +485,13 @@ abstract class CameraControlHostApi {
void setZoomRatio(int identifier, double ratio);
@async
- int startFocusAndMetering(int identifier, int focusMeteringActionId);
+ int? startFocusAndMetering(int identifier, int focusMeteringActionId);
@async
void cancelFocusAndMetering(int identifier);
@async
- int setExposureCompensationIndex(int identifier, int index);
+ int? setExposureCompensationIndex(int identifier, int index);
}
@FlutterApi()
@@ -516,7 +516,8 @@ abstract class FocusMeteringResultFlutterApi {
@HostApi(dartHostTestHandler: 'TestMeteringPointHostApi')
abstract class MeteringPointHostApi {
- void create(int identifier, double x, double y, double? size);
+ void create(
+ int identifier, double x, double y, double? size, int cameraInfoId);
double getDefaultPointSize();
}
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index fee04bae156..3df56f5e1db 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+33
+version: 0.5.0+34
environment:
sdk: ^3.1.0
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
index bc6a0d1c6a3..0868916f3bf 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:math' show Point;
import 'package:async/async.dart';
import 'package:camera_android_camerax/camera_android_camerax.dart';
@@ -18,10 +19,12 @@ import 'package:camera_android_camerax/src/camerax_proxy.dart';
import 'package:camera_android_camerax/src/device_orientation_manager.dart';
import 'package:camera_android_camerax/src/exposure_state.dart';
import 'package:camera_android_camerax/src/fallback_strategy.dart';
+import 'package:camera_android_camerax/src/focus_metering_action.dart';
import 'package:camera_android_camerax/src/image_analysis.dart';
import 'package:camera_android_camerax/src/image_capture.dart';
import 'package:camera_android_camerax/src/image_proxy.dart';
import 'package:camera_android_camerax/src/live_data.dart';
+import 'package:camera_android_camerax/src/metering_point.dart';
import 'package:camera_android_camerax/src/observer.dart';
import 'package:camera_android_camerax/src/pending_recording.dart';
import 'package:camera_android_camerax/src/plane_proxy.dart';
@@ -38,7 +41,8 @@ import 'package:camera_android_camerax/src/use_case.dart';
import 'package:camera_android_camerax/src/video_capture.dart';
import 'package:camera_android_camerax/src/zoom_state.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
-import 'package:flutter/services.dart' show DeviceOrientation, Uint8List;
+import 'package:flutter/services.dart'
+ show DeviceOrientation, PlatformException, Uint8List;
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
@@ -113,6 +117,17 @@ void main() {
return cameraClosingEventSent && cameraErrorSent;
}
+ /// CameraXProxy for testing exposure and focus related controls.
+ ///
+ /// Modifies the creation of MeteringPoints and FocusMeteringActions to return
+ /// objects detached from a native object.
+ CameraXProxy getProxyForExposureAndFocus() => CameraXProxy(
+ createMeteringPoint: (double x, double y, CameraInfo cameraInfo) =>
+ MeteringPoint.detached(x: x, y: y, cameraInfo: cameraInfo),
+ createFocusMeteringAction: (List<(MeteringPoint, int?)>
+ meteringPointInfos) =>
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos));
+
test('Should fetch CameraDescription instances for available cameras',
() async {
// Arrange
@@ -331,6 +346,7 @@ void main() {
final MockVideoCapture mockVideoCapture = MockVideoCapture();
final MockCamera mockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final MockCameraControl mockCameraControl = MockCameraControl();
// Tell plugin to create mock/detached objects and stub method calls for the
// testing of createCamera.
@@ -377,6 +393,8 @@ void main() {
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => MockLiveCameraState());
+ when(mockCamera.getCameraControl())
+ .thenAnswer((_) async => mockCameraControl);
camera.processCameraProvider = mockProcessCameraProvider;
await camera.createCamera(testCameraDescription, testResolutionPreset,
@@ -389,6 +407,9 @@ void main() {
// Verify the camera's CameraInfo instance got updated.
expect(camera.cameraInfo, equals(mockCameraInfo));
+ // Verify camera's CameraControl instance got updated.
+ expect(camera.cameraControl, equals(mockCameraControl));
+
// Verify preview has been marked as bound to the camera lifecycle by
// createCamera.
expect(camera.previewInitiallyBound, isTrue);
@@ -940,6 +961,7 @@ void main() {
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final MockCameraControl mockCameraControl = MockCameraControl();
final MockLiveCameraState mockLiveCameraState = MockLiveCameraState();
// Set directly for test versus calling createCamera.
@@ -961,6 +983,8 @@ void main() {
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => mockLiveCameraState);
+ when(mockCamera.getCameraControl())
+ .thenAnswer((_) async => mockCameraControl);
await camera.resumePreview(78);
@@ -974,6 +998,7 @@ void main() {
as Observer),
isTrue);
expect(camera.cameraInfo, equals(mockCameraInfo));
+ expect(camera.cameraControl, equals(mockCameraControl));
});
test(
@@ -1017,6 +1042,7 @@ void main() {
final MockCamera mockCamera = MockCamera();
final MockCamera newMockCamera = MockCamera();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final MockCameraControl mockCameraControl = MockCameraControl();
final MockLiveCameraState mockLiveCameraState = MockLiveCameraState();
final MockLiveCameraState newMockLiveCameraState = MockLiveCameraState();
final TestSystemServicesHostApi mockSystemServicesApi =
@@ -1055,6 +1081,8 @@ void main() {
.thenAnswer((_) async => newMockCamera);
when(newMockCamera.getCameraInfo())
.thenAnswer((_) async => mockCameraInfo);
+ when(newMockCamera.getCameraControl())
+ .thenAnswer((_) async => mockCameraControl);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => newMockLiveCameraState);
@@ -1066,6 +1094,7 @@ void main() {
camera.cameraSelector!, [camera.videoCapture!]));
expect(camera.camera, equals(newMockCamera));
expect(camera.cameraInfo, equals(mockCameraInfo));
+ expect(camera.cameraControl, equals(mockCameraControl));
verify(mockLiveCameraState.removeObservers());
expect(
await testCameraClosingObserver(
@@ -1453,18 +1482,14 @@ void main() {
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 77;
- final MockCameraControl mockCameraControl = MockCameraControl();
// Set directly for test versus calling createCamera.
camera.imageCapture = MockImageCapture();
- camera.camera = MockCamera();
+ camera.cameraControl = MockCameraControl();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
- when(camera.camera!.getCameraControl())
- .thenAnswer((_) async => mockCameraControl);
-
await camera.setFlashMode(cameraId, FlashMode.torch);
await camera.takePicture(cameraId);
verify(camera.imageCapture!.setFlashMode(ImageCapture.flashModeOff));
@@ -1479,14 +1504,11 @@ void main() {
// Set directly for test versus calling createCamera.
camera.imageCapture = MockImageCapture();
- camera.camera = MockCamera();
+ camera.cameraControl = mockCameraControl;
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
- when(camera.camera!.getCameraControl())
- .thenAnswer((_) async => mockCameraControl);
-
for (final FlashMode flashMode in FlashMode.values) {
await camera.setFlashMode(cameraId, flashMode);
@@ -1520,10 +1542,7 @@ void main() {
final MockCameraControl mockCameraControl = MockCameraControl();
// Set directly for test versus calling createCamera.
- camera.camera = MockCamera();
-
- when(camera.camera!.getCameraControl())
- .thenAnswer((_) async => mockCameraControl);
+ camera.cameraControl = mockCameraControl;
await camera.setFlashMode(cameraId, FlashMode.torch);
@@ -1538,10 +1557,7 @@ void main() {
final MockCameraControl mockCameraControl = MockCameraControl();
// Set directly for test versus calling createCamera.
- camera.camera = MockCamera();
-
- when(camera.camera!.getCameraControl())
- .thenAnswer((_) async => mockCameraControl);
+ camera.cameraControl = mockCameraControl;
for (final FlashMode flashMode in FlashMode.values) {
camera.torchEnabled = true;
@@ -1614,6 +1630,25 @@ void main() {
expect(await camera.getExposureOffsetStepSize(55), 0.2);
});
+ test(
+ 'getExposureOffsetStepSize returns -1 when exposure compensation not supported on device',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final ExposureState exposureState = ExposureState.detached(
+ exposureCompensationRange:
+ ExposureCompensationRange(minCompensation: 0, maxCompensation: 0),
+ exposureCompensationStep: 0);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraInfo = mockCameraInfo;
+
+ when(mockCameraInfo.getExposureState())
+ .thenAnswer((_) async => exposureState);
+
+ expect(await camera.getExposureOffsetStepSize(55), -1);
+ });
+
test('getMaxZoomLevel returns expected exposure offset', () async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockCameraInfo mockCameraInfo = MockCameraInfo();
@@ -1657,10 +1692,7 @@ void main() {
final MockCameraControl mockCameraControl = MockCameraControl();
// Set directly for test versus calling createCamera.
- camera.camera = MockCamera();
-
- when(camera.camera!.getCameraControl())
- .thenAnswer((_) async => mockCameraControl);
+ camera.cameraControl = mockCameraControl;
await camera.setZoomLevel(cameraId, zoomRatio);
@@ -1981,4 +2013,452 @@ void main() {
await camera.unlockCaptureOrientation(cameraId);
expect(camera.captureOrientationLocked, isFalse);
});
+
+ test(
+ 'setExposurePoint clears current auto-exposure metering point as expected',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 93;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+ camera.cameraInfo = mockCameraInfo;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ // Verify nothing happens if no current focus and metering action has been
+ // enabled.
+ await camera.setExposurePoint(cameraId, null);
+ verifyNever(mockCameraControl.startFocusAndMetering(any));
+ verifyNever(mockCameraControl.cancelFocusAndMetering());
+
+ // Verify current auto-exposure metering point is removed if previously set.
+ final (MeteringPoint, int?) autofocusMeteringPointInfo = (
+ MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAf
+ );
+ List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[
+ (
+ MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAe
+ ),
+ autofocusMeteringPointInfo
+ ];
+
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setExposurePoint(cameraId, null);
+
+ final VerificationResult verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ final FocusMeteringAction capturedAction =
+ verificationResult.captured.single as FocusMeteringAction;
+ final List<(MeteringPoint, int?)> capturedMeteringPointInfos =
+ capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(1));
+ expect(
+ capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo));
+
+ // Verify current focus and metering action is cleared if only previously
+ // set metering point was for auto-exposure.
+ meteringPointInfos = <(MeteringPoint, int?)>[
+ (
+ MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAe
+ )
+ ];
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setExposurePoint(cameraId, null);
+
+ verify(mockCameraControl.cancelFocusAndMetering());
+ });
+
+ test('setExposurePoint throws CameraException if invalid point specified',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 23;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ const Point invalidExposurePoint = Point(3, -1);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ expect(() => camera.setExposurePoint(cameraId, invalidExposurePoint),
+ throwsA(isA()));
+ });
+
+ test(
+ 'setExposurePoint adds new exposure point to focus metering action to start as expected when previous metering points have been set',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 9;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+ camera.cameraInfo = mockCameraInfo;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ // Verify current auto-exposure metering point is removed if previously set.
+ double exposurePointX = 0.8;
+ double exposurePointY = 0.1;
+ Point exposurePoint = Point(exposurePointX, exposurePointY);
+ final (MeteringPoint, int?) autofocusMeteringPointInfo = (
+ MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAf
+ );
+ List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[
+ (
+ MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAe
+ ),
+ autofocusMeteringPointInfo
+ ];
+
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setExposurePoint(cameraId, exposurePoint);
+
+ VerificationResult verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ FocusMeteringAction capturedAction =
+ verificationResult.captured.single as FocusMeteringAction;
+ List<(MeteringPoint, int?)> capturedMeteringPointInfos =
+ capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(2));
+ expect(
+ capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo));
+ expect(capturedMeteringPointInfos[1].$1.x, equals(exposurePointX));
+ expect(capturedMeteringPointInfos[1].$1.y, equals(exposurePointY));
+ expect(
+ capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAe));
+
+ // Verify exposure point is set when no auto-exposure metering point
+ // previously set, but an auto-focus point metering point has been.
+ exposurePointX = 0.2;
+ exposurePointY = 0.9;
+ exposurePoint = Point(exposurePointX, exposurePointY);
+ meteringPointInfos = <(MeteringPoint, int?)>[autofocusMeteringPointInfo];
+
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setExposurePoint(cameraId, exposurePoint);
+
+ verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ capturedAction = verificationResult.captured.single as FocusMeteringAction;
+ capturedMeteringPointInfos = capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(2));
+ expect(
+ capturedMeteringPointInfos.first, equals(autofocusMeteringPointInfo));
+ expect(capturedMeteringPointInfos[1].$1.x, equals(exposurePointX));
+ expect(capturedMeteringPointInfos[1].$1.y, equals(exposurePointY));
+ expect(
+ capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAe));
+ });
+
+ test(
+ 'setExposurePoint adds new exposure point to focus metering action to start as expected when no previous metering points have been set',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 19;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ const double exposurePointX = 0.8;
+ const double exposurePointY = 0.1;
+ const Point exposurePoint =
+ Point(exposurePointX, exposurePointY);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+ camera.cameraInfo = MockCameraInfo();
+ camera.currentFocusMeteringAction = null;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ await camera.setExposurePoint(cameraId, exposurePoint);
+
+ final VerificationResult verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ final FocusMeteringAction capturedAction =
+ verificationResult.captured.single as FocusMeteringAction;
+ final List<(MeteringPoint, int?)> capturedMeteringPointInfos =
+ capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(1));
+ expect(capturedMeteringPointInfos.first.$1.x, equals(exposurePointX));
+ expect(capturedMeteringPointInfos.first.$1.y, equals(exposurePointY));
+ expect(capturedMeteringPointInfos.first.$2,
+ equals(FocusMeteringAction.flagAe));
+ });
+
+ test(
+ 'setExposureOffset throws exception if exposure compensation not supported',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 6;
+ const double offset = 2;
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final ExposureState exposureState = ExposureState.detached(
+ exposureCompensationRange:
+ ExposureCompensationRange(minCompensation: 3, maxCompensation: 4),
+ exposureCompensationStep: 0);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraInfo = mockCameraInfo;
+
+ when(mockCameraInfo.getExposureState())
+ .thenAnswer((_) async => exposureState);
+
+ expect(() => camera.setExposureOffset(cameraId, offset),
+ throwsA(isA()));
+ });
+
+ test(
+ 'setExposureOffset throws exception if exposure compensation could not be set',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 11;
+ const double offset = 3;
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final CameraControl mockCameraControl = MockCameraControl();
+ final ExposureState exposureState = ExposureState.detached(
+ exposureCompensationRange:
+ ExposureCompensationRange(minCompensation: 3, maxCompensation: 4),
+ exposureCompensationStep: 0.2);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraInfo = mockCameraInfo;
+ camera.cameraControl = mockCameraControl;
+
+ when(mockCameraInfo.getExposureState())
+ .thenAnswer((_) async => exposureState);
+ when(mockCameraControl.setExposureCompensationIndex(15)).thenThrow(
+ PlatformException(
+ code: 'TEST_ERROR',
+ message:
+ 'This is a test error message indicating exposure offset could not be set.'));
+
+ expect(() => camera.setExposureOffset(cameraId, offset),
+ throwsA(isA()));
+ });
+
+ test(
+ 'setExposureOffset behaves as expected to successful attempt to set exposure compensation index',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 11;
+ const double offset = 3;
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+ final CameraControl mockCameraControl = MockCameraControl();
+ final ExposureState exposureState = ExposureState.detached(
+ exposureCompensationRange:
+ ExposureCompensationRange(minCompensation: 3, maxCompensation: 4),
+ exposureCompensationStep: 0.2);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraInfo = mockCameraInfo;
+ camera.cameraControl = mockCameraControl;
+
+ when(mockCameraInfo.getExposureState())
+ .thenAnswer((_) async => exposureState);
+
+ // Exposure index * exposure offset step size = exposure offset, i.e.
+ // 15 * 0.2 = 3.
+ expect(await camera.setExposureOffset(cameraId, offset), equals(3));
+ });
+
+ test('setFocusPoint clears current auto-exposure metering point as expected',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 93;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+ camera.cameraInfo = mockCameraInfo;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ // Verify nothing happens if no current focus and metering action has been
+ // enabled.
+ await camera.setFocusPoint(cameraId, null);
+ verifyNever(mockCameraControl.startFocusAndMetering(any));
+ verifyNever(mockCameraControl.cancelFocusAndMetering());
+
+ // Verify current auto-exposure metering point is removed if previously set.
+ final (MeteringPoint, int?) autoexposureMeteringPointInfo = (
+ MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAe
+ );
+ List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[
+ (
+ MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAf
+ ),
+ autoexposureMeteringPointInfo
+ ];
+
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setFocusPoint(cameraId, null);
+
+ final VerificationResult verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ final FocusMeteringAction capturedAction =
+ verificationResult.captured.single as FocusMeteringAction;
+ final List<(MeteringPoint, int?)> capturedMeteringPointInfos =
+ capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(1));
+ expect(capturedMeteringPointInfos.first,
+ equals(autoexposureMeteringPointInfo));
+
+ // Verify current focus and metering action is cleared if only previously
+ // set metering point was for auto-exposure.
+ meteringPointInfos = <(MeteringPoint, int?)>[
+ (
+ MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAf
+ )
+ ];
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setFocusPoint(cameraId, null);
+
+ verify(mockCameraControl.cancelFocusAndMetering());
+ });
+
+ test('setFocusPoint throws CameraException if invalid point specified',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 23;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ const Point invalidFocusPoint = Point(-3, 1);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ expect(() => camera.setFocusPoint(cameraId, invalidFocusPoint),
+ throwsA(isA()));
+ });
+
+ test(
+ 'setFocusPoint adds new exposure point to focus metering action to start as expected when previous metering points have been set',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 9;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ final MockCameraInfo mockCameraInfo = MockCameraInfo();
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+ camera.cameraInfo = mockCameraInfo;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ // Verify current auto-exposure metering point is removed if previously set.
+ double focusPointX = 0.8;
+ double focusPointY = 0.1;
+ Point exposurePoint = Point(focusPointX, focusPointY);
+ final (MeteringPoint, int?) autoExposureMeteringPointInfo = (
+ MeteringPoint.detached(x: 0.3, y: 0.7, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAe
+ );
+ List<(MeteringPoint, int?)> meteringPointInfos = <(MeteringPoint, int?)>[
+ (
+ MeteringPoint.detached(x: 0.2, y: 0.5, cameraInfo: mockCameraInfo),
+ FocusMeteringAction.flagAf
+ ),
+ autoExposureMeteringPointInfo
+ ];
+
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setFocusPoint(cameraId, exposurePoint);
+
+ VerificationResult verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ FocusMeteringAction capturedAction =
+ verificationResult.captured.single as FocusMeteringAction;
+ List<(MeteringPoint, int?)> capturedMeteringPointInfos =
+ capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(2));
+ expect(capturedMeteringPointInfos.first,
+ equals(autoExposureMeteringPointInfo));
+ expect(capturedMeteringPointInfos[1].$1.x, equals(focusPointX));
+ expect(capturedMeteringPointInfos[1].$1.y, equals(focusPointY));
+ expect(
+ capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAf));
+
+ // Verify exposure point is set when no auto-exposure metering point
+ // previously set, but an auto-focus point metering point has been.
+ focusPointX = 0.2;
+ focusPointY = 0.9;
+ exposurePoint = Point(focusPointX, focusPointY);
+ meteringPointInfos = <(MeteringPoint, int?)>[autoExposureMeteringPointInfo];
+
+ camera.currentFocusMeteringAction =
+ FocusMeteringAction.detached(meteringPointInfos: meteringPointInfos);
+
+ await camera.setFocusPoint(cameraId, exposurePoint);
+
+ verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ capturedAction = verificationResult.captured.single as FocusMeteringAction;
+ capturedMeteringPointInfos = capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(2));
+ expect(capturedMeteringPointInfos.first,
+ equals(autoExposureMeteringPointInfo));
+ expect(capturedMeteringPointInfos[1].$1.x, equals(focusPointX));
+ expect(capturedMeteringPointInfos[1].$1.y, equals(focusPointY));
+ expect(
+ capturedMeteringPointInfos[1].$2, equals(FocusMeteringAction.flagAf));
+ });
+
+ test(
+ 'setFocusPoint adds new exposure point to focus metering action to start as expected when no previous metering points have been set',
+ () async {
+ final AndroidCameraCameraX camera = AndroidCameraCameraX();
+ const int cameraId = 19;
+ final MockCameraControl mockCameraControl = MockCameraControl();
+ const double focusPointX = 0.8;
+ const double focusPointY = 0.1;
+ const Point exposurePoint = Point(focusPointX, focusPointY);
+
+ // Set directly for test versus calling createCamera.
+ camera.cameraControl = mockCameraControl;
+ camera.cameraInfo = MockCameraInfo();
+ camera.currentFocusMeteringAction = null;
+
+ camera.proxy = getProxyForExposureAndFocus();
+
+ await camera.setFocusPoint(cameraId, exposurePoint);
+
+ final VerificationResult verificationResult =
+ verify(mockCameraControl.startFocusAndMetering(captureAny));
+ final FocusMeteringAction capturedAction =
+ verificationResult.captured.single as FocusMeteringAction;
+ final List<(MeteringPoint, int?)> capturedMeteringPointInfos =
+ capturedAction.meteringPointInfos;
+ expect(capturedMeteringPointInfos.length, equals(1));
+ expect(capturedMeteringPointInfos.first.$1.x, equals(focusPointX));
+ expect(capturedMeteringPointInfos.first.$1.y, equals(focusPointY));
+ expect(capturedMeteringPointInfos.first.$2,
+ equals(FocusMeteringAction.flagAf));
+ });
}
diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart
index 09eb9436c7a..58d89c49b20 100644
--- a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart
@@ -6,11 +6,13 @@
import 'dart:async' as _i3;
import 'package:camera_android_camerax/src/camera_control.dart' as _i2;
-import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i5;
-import 'package:camera_android_camerax/src/capture_request_options.dart' as _i4;
+import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7;
+import 'package:camera_android_camerax/src/capture_request_options.dart' as _i6;
+import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i5;
+import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i4;
import 'package:mockito/mockito.dart' as _i1;
-import 'test_camerax_library.g.dart' as _i6;
+import 'test_camerax_library.g.dart' as _i8;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -53,6 +55,37 @@ class MockCameraControl extends _i1.Mock implements _i2.CameraControl {
returnValue: _i3.Future.value(),
returnValueForMissingStub: _i3.Future.value(),
) as _i3.Future);
+
+ @override
+ _i3.Future<_i4.FocusMeteringResult?> startFocusAndMetering(
+ _i5.FocusMeteringAction? action) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #startFocusAndMetering,
+ [action],
+ ),
+ returnValue: _i3.Future<_i4.FocusMeteringResult?>.value(),
+ ) as _i3.Future<_i4.FocusMeteringResult?>);
+
+ @override
+ _i3.Future cancelFocusAndMetering() => (super.noSuchMethod(
+ Invocation.method(
+ #cancelFocusAndMetering,
+ [],
+ ),
+ returnValue: _i3.Future.value(),
+ returnValueForMissingStub: _i3.Future.value(),
+ ) as _i3.Future);
+
+ @override
+ _i3.Future setExposureCompensationIndex(int? index) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setExposureCompensationIndex,
+ [index],
+ ),
+ returnValue: _i3.Future.value(),
+ ) as _i3.Future);
}
/// A class which mocks [CaptureRequestOptions].
@@ -60,36 +93,24 @@ class MockCameraControl extends _i1.Mock implements _i2.CameraControl {
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
class MockCaptureRequestOptions extends _i1.Mock
- implements _i4.CaptureRequestOptions {
+ implements _i6.CaptureRequestOptions {
MockCaptureRequestOptions() {
_i1.throwOnMissingStub(this);
}
@override
- List<(_i5.CaptureRequestKeySupportedType, dynamic)> get requestedOptions =>
+ List<(_i7.CaptureRequestKeySupportedType, Object?)> get requestedOptions =>
(super.noSuchMethod(
Invocation.getter(#requestedOptions),
- returnValue: <(_i5.CaptureRequestKeySupportedType, dynamic)>[],
- ) as List<(_i5.CaptureRequestKeySupportedType, dynamic)>);
-
- @override
- set requestedOptions(
- List<(_i5.CaptureRequestKeySupportedType, dynamic)>?
- _requestedOptions) =>
- super.noSuchMethod(
- Invocation.setter(
- #requestedOptions,
- _requestedOptions,
- ),
- returnValueForMissingStub: null,
- );
+ returnValue: <(_i7.CaptureRequestKeySupportedType, Object?)>[],
+ ) as List<(_i7.CaptureRequestKeySupportedType, Object?)>);
}
/// A class which mocks [TestCamera2CameraControlHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestCamera2CameraControlHostApi extends _i1.Mock
- implements _i6.TestCamera2CameraControlHostApi {
+ implements _i8.TestCamera2CameraControlHostApi {
MockTestCamera2CameraControlHostApi() {
_i1.throwOnMissingStub(this);
}
@@ -132,7 +153,7 @@ class MockTestCamera2CameraControlHostApi extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockTestInstanceManagerHostApi extends _i1.Mock
- implements _i6.TestInstanceManagerHostApi {
+ implements _i8.TestInstanceManagerHostApi {
MockTestInstanceManagerHostApi() {
_i1.throwOnMissingStub(this);
}
diff --git a/packages/camera/camera_android_camerax/test/camera_control_test.dart b/packages/camera/camera_android_camerax/test/camera_control_test.dart
index 63c7a7d9aa9..22ba8aaf247 100644
--- a/packages/camera/camera_android_camerax/test/camera_control_test.dart
+++ b/packages/camera/camera_android_camerax/test/camera_control_test.dart
@@ -6,6 +6,7 @@ import 'package:camera_android_camerax/src/camera_control.dart';
import 'package:camera_android_camerax/src/focus_metering_action.dart';
import 'package:camera_android_camerax/src/focus_metering_result.dart';
import 'package:camera_android_camerax/src/instance_manager.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@@ -13,7 +14,11 @@ import 'package:mockito/mockito.dart';
import 'camera_control_test.mocks.dart';
import 'test_camerax_library.g.dart';
-@GenerateMocks([TestCameraControlHostApi, TestInstanceManagerHostApi])
+@GenerateMocks([
+ TestCameraControlHostApi,
+ TestInstanceManagerHostApi,
+ FocusMeteringAction
+])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -90,8 +95,7 @@ void main() {
instanceManager: instanceManager,
);
const int cameraControlIdentifier = 75;
- final FocusMeteringAction action =
- FocusMeteringAction.detached(instanceManager: instanceManager);
+ final FocusMeteringAction action = MockFocusMeteringAction();
const int actionId = 5;
final FocusMeteringResult result =
FocusMeteringResult.detached(instanceManager: instanceManager);
@@ -105,8 +109,7 @@ void main() {
instanceManager.addHostCreatedInstance(
action,
actionId,
- onCopy: (_) =>
- FocusMeteringAction.detached(instanceManager: instanceManager),
+ onCopy: (_) => MockFocusMeteringAction(),
);
instanceManager.addHostCreatedInstance(
result,
@@ -122,6 +125,41 @@ void main() {
verify(mockApi.startFocusAndMetering(cameraControlIdentifier, actionId));
});
+ test('startFocusAndMetering returns null result if operation was canceled',
+ () async {
+ final MockTestCameraControlHostApi mockApi =
+ MockTestCameraControlHostApi();
+ TestCameraControlHostApi.setup(mockApi);
+
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+
+ final CameraControl cameraControl = CameraControl.detached(
+ instanceManager: instanceManager,
+ );
+ const int cameraControlIdentifier = 75;
+ final FocusMeteringAction action = MockFocusMeteringAction();
+ const int actionId = 5;
+
+ instanceManager.addHostCreatedInstance(
+ cameraControl,
+ cameraControlIdentifier,
+ onCopy: (_) => CameraControl.detached(instanceManager: instanceManager),
+ );
+ instanceManager.addHostCreatedInstance(
+ action,
+ actionId,
+ onCopy: (_) => MockFocusMeteringAction(),
+ );
+
+ when(mockApi.startFocusAndMetering(cameraControlIdentifier, actionId))
+ .thenAnswer((_) => Future.value());
+
+ expect(await cameraControl.startFocusAndMetering(action), isNull);
+ verify(mockApi.startFocusAndMetering(cameraControlIdentifier, actionId));
+ });
+
test(
'cancelFocusAndMetering makes call on Java side to cancel focus and metering',
() async {
@@ -182,6 +220,72 @@ void main() {
mockApi.setExposureCompensationIndex(cameraControlIdentifier, index));
});
+ test(
+ 'setExposureCompensationIndex returns null when operation was canceled',
+ () async {
+ final MockTestCameraControlHostApi mockApi =
+ MockTestCameraControlHostApi();
+ TestCameraControlHostApi.setup(mockApi);
+
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+
+ final CameraControl cameraControl = CameraControl.detached(
+ instanceManager: instanceManager,
+ );
+ const int cameraControlIdentifier = 40;
+
+ instanceManager.addHostCreatedInstance(
+ cameraControl,
+ cameraControlIdentifier,
+ onCopy: (_) => CameraControl.detached(instanceManager: instanceManager),
+ );
+
+ const int index = 2;
+ when(mockApi.setExposureCompensationIndex(cameraControlIdentifier, index))
+ .thenAnswer((_) => Future.value());
+
+ expect(await cameraControl.setExposureCompensationIndex(index), isNull);
+ verify(
+ mockApi.setExposureCompensationIndex(cameraControlIdentifier, index));
+ });
+
+ test(
+ 'setExposureCompensationIndex throws PlatformException when one is thrown from native side',
+ () async {
+ final MockTestCameraControlHostApi mockApi =
+ MockTestCameraControlHostApi();
+ TestCameraControlHostApi.setup(mockApi);
+
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+
+ final CameraControl cameraControl = CameraControl.detached(
+ instanceManager: instanceManager,
+ );
+ const int cameraControlIdentifier = 40;
+
+ instanceManager.addHostCreatedInstance(
+ cameraControl,
+ cameraControlIdentifier,
+ onCopy: (_) => CameraControl.detached(instanceManager: instanceManager),
+ );
+
+ const int index = 1;
+ when(mockApi.setExposureCompensationIndex(cameraControlIdentifier, index))
+ .thenThrow(PlatformException(
+ code: 'TEST_ERROR',
+ details: 'Platform exception thrown from Java side.'));
+
+ expect(() => cameraControl.setExposureCompensationIndex(index),
+ throwsA(isA()));
+
+ verify(
+ mockApi.setExposureCompensationIndex(cameraControlIdentifier, index));
+ });
+
test('flutterApiCreate makes call to add instance to instance manager', () {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
diff --git a/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart
index a11525af986..57040bcac62 100644
--- a/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/camera_control_test.mocks.dart
@@ -5,6 +5,8 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i3;
+import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i4;
+import 'package:camera_android_camerax/src/metering_point.dart' as _i5;
import 'package:mockito/mockito.dart' as _i1;
import 'test_camerax_library.g.dart' as _i2;
@@ -66,7 +68,7 @@ class MockTestCameraControlHostApi extends _i1.Mock
) as _i3.Future);
@override
- _i3.Future startFocusAndMetering(
+ _i3.Future startFocusAndMetering(
int? identifier,
int? focusMeteringActionId,
) =>
@@ -78,8 +80,8 @@ class MockTestCameraControlHostApi extends _i1.Mock
focusMeteringActionId,
],
),
- returnValue: _i3.Future.value(0),
- ) as _i3.Future);
+ returnValue: _i3.Future.value(),
+ ) as _i3.Future);
@override
_i3.Future cancelFocusAndMetering(int? identifier) =>
@@ -93,7 +95,7 @@ class MockTestCameraControlHostApi extends _i1.Mock
) as _i3.Future);
@override
- _i3.Future setExposureCompensationIndex(
+ _i3.Future setExposureCompensationIndex(
int? identifier,
int? index,
) =>
@@ -105,8 +107,8 @@ class MockTestCameraControlHostApi extends _i1.Mock
index,
],
),
- returnValue: _i3.Future.value(0),
- ) as _i3.Future);
+ returnValue: _i3.Future.value(),
+ ) as _i3.Future);
}
/// A class which mocks [TestInstanceManagerHostApi].
@@ -127,3 +129,20 @@ class MockTestInstanceManagerHostApi extends _i1.Mock
returnValueForMissingStub: null,
);
}
+
+/// A class which mocks [FocusMeteringAction].
+///
+/// See the documentation for Mockito's code generation for more information.
+// ignore: must_be_immutable
+class MockFocusMeteringAction extends _i1.Mock
+ implements _i4.FocusMeteringAction {
+ MockFocusMeteringAction() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ List<(_i5.MeteringPoint, int?)> get meteringPointInfos => (super.noSuchMethod(
+ Invocation.getter(#meteringPointInfos),
+ returnValue: <(_i5.MeteringPoint, int?)>[],
+ ) as List<(_i5.MeteringPoint, int?)>);
+}
diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart
index 657fb1aa3da..a74338e2735 100644
--- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart
+++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart
@@ -37,6 +37,9 @@ void main() {
);
FocusMeteringAction.detached(
+ meteringPointInfos: <(MeteringPoint, int?)>[
+ (MockMeteringPoint(), FocusMeteringAction.flagAwb)
+ ],
instanceManager: instanceManager,
);
diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart
index 717215ca228..d1ceef9daad 100644
--- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart
@@ -1,13 +1,14 @@
-// Mocks generated by Mockito 5.4.3 from annotations
+// Mocks generated by Mockito 5.4.4 from annotations
// in camera_android_camerax/test/focus_metering_action_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i4;
-import 'package:camera_android_camerax/src/metering_point.dart' as _i2;
+import 'package:camera_android_camerax/src/camera_info.dart' as _i2;
+import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i5;
+import 'package:camera_android_camerax/src/metering_point.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
-import 'test_camerax_library.g.dart' as _i3;
+import 'test_camerax_library.g.dart' as _i4;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -22,11 +23,21 @@ import 'test_camerax_library.g.dart' as _i3;
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
+class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo {
+ _FakeCameraInfo_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
/// A class which mocks [MeteringPoint].
///
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
-class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint {
+class MockMeteringPoint extends _i1.Mock implements _i3.MeteringPoint {
MockMeteringPoint() {
_i1.throwOnMissingStub(this);
}
@@ -42,13 +53,22 @@ class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint {
Invocation.getter(#y),
returnValue: 0.0,
) as double);
+
+ @override
+ _i2.CameraInfo get cameraInfo => (super.noSuchMethod(
+ Invocation.getter(#cameraInfo),
+ returnValue: _FakeCameraInfo_0(
+ this,
+ Invocation.getter(#cameraInfo),
+ ),
+ ) as _i2.CameraInfo);
}
/// A class which mocks [TestFocusMeteringActionHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestFocusMeteringActionHostApi extends _i1.Mock
- implements _i3.TestFocusMeteringActionHostApi {
+ implements _i4.TestFocusMeteringActionHostApi {
MockTestFocusMeteringActionHostApi() {
_i1.throwOnMissingStub(this);
}
@@ -56,7 +76,7 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock
@override
void create(
int? identifier,
- List<_i4.MeteringPointInfo?>? meteringPointInfos,
+ List<_i5.MeteringPointInfo?>? meteringPointInfos,
) =>
super.noSuchMethod(
Invocation.method(
@@ -74,7 +94,7 @@ class MockTestFocusMeteringActionHostApi extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockTestInstanceManagerHostApi extends _i1.Mock
- implements _i3.TestInstanceManagerHostApi {
+ implements _i4.TestInstanceManagerHostApi {
MockTestInstanceManagerHostApi() {
_i1.throwOnMissingStub(this);
}
diff --git a/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart
index d35cdc15efb..be52b17bda4 100644
--- a/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart
@@ -1,12 +1,13 @@
-// Mocks generated by Mockito 5.4.3 from annotations
+// Mocks generated by Mockito 5.4.4 from annotations
// in camera_android_camerax/test/focus_metering_result_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:camera_android_camerax/src/metering_point.dart' as _i2;
+import 'package:camera_android_camerax/src/camera_info.dart' as _i2;
+import 'package:camera_android_camerax/src/metering_point.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
-import 'test_camerax_library.g.dart' as _i3;
+import 'test_camerax_library.g.dart' as _i4;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -21,11 +22,21 @@ import 'test_camerax_library.g.dart' as _i3;
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
+class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo {
+ _FakeCameraInfo_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
/// A class which mocks [MeteringPoint].
///
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
-class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint {
+class MockMeteringPoint extends _i1.Mock implements _i3.MeteringPoint {
MockMeteringPoint() {
_i1.throwOnMissingStub(this);
}
@@ -41,13 +52,22 @@ class MockMeteringPoint extends _i1.Mock implements _i2.MeteringPoint {
Invocation.getter(#y),
returnValue: 0.0,
) as double);
+
+ @override
+ _i2.CameraInfo get cameraInfo => (super.noSuchMethod(
+ Invocation.getter(#cameraInfo),
+ returnValue: _FakeCameraInfo_0(
+ this,
+ Invocation.getter(#cameraInfo),
+ ),
+ ) as _i2.CameraInfo);
}
/// A class which mocks [TestFocusMeteringResultHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestFocusMeteringResultHostApi extends _i1.Mock
- implements _i3.TestFocusMeteringResultHostApi {
+ implements _i4.TestFocusMeteringResultHostApi {
MockTestFocusMeteringResultHostApi() {
_i1.throwOnMissingStub(this);
}
@@ -66,7 +86,7 @@ class MockTestFocusMeteringResultHostApi extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockTestInstanceManagerHostApi extends _i1.Mock
- implements _i3.TestInstanceManagerHostApi {
+ implements _i4.TestInstanceManagerHostApi {
MockTestInstanceManagerHostApi() {
_i1.throwOnMissingStub(this);
}
diff --git a/packages/camera/camera_android_camerax/test/metering_point_test.dart b/packages/camera/camera_android_camerax/test/metering_point_test.dart
index ba3daae96b1..5aa92164377 100644
--- a/packages/camera/camera_android_camerax/test/metering_point_test.dart
+++ b/packages/camera/camera_android_camerax/test/metering_point_test.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:camera_android_camerax/src/camera_info.dart';
import 'package:camera_android_camerax/src/instance_manager.dart';
import 'package:camera_android_camerax/src/metering_point.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -11,7 +12,8 @@ import 'package:mockito/mockito.dart';
import 'metering_point_test.mocks.dart';
import 'test_camerax_library.g.dart';
-@GenerateMocks([TestInstanceManagerHostApi, TestMeteringPointHostApi])
+@GenerateMocks(
+ [TestInstanceManagerHostApi, TestMeteringPointHostApi, CameraInfo])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -33,11 +35,12 @@ void main() {
x: 0,
y: 0.3,
size: 4,
+ cameraInfo: MockCameraInfo(),
instanceManager: instanceManager,
);
verifyNever(mockApi.create(argThat(isA()), argThat(isA()),
- argThat(isA()), argThat(isA())));
+ argThat(isA()), argThat(isA()), argThat(isA())));
});
test('create calls create on the Java side', () async {
@@ -52,14 +55,27 @@ void main() {
const double x = 0.5;
const double y = 0.6;
const double size = 3;
+ final CameraInfo mockCameraInfo = MockCameraInfo();
+ const int mockCameraInfoId = 4;
+
+ instanceManager.addHostCreatedInstance(mockCameraInfo, mockCameraInfoId,
+ onCopy: (CameraInfo original) => MockCameraInfo());
+
MeteringPoint(
x: x,
y: y,
size: size,
+ cameraInfo: mockCameraInfo,
instanceManager: instanceManager,
);
- verify(mockApi.create(argThat(isA()), x, y, size));
+ verify(mockApi.create(
+ argThat(isA()),
+ x,
+ y,
+ size,
+ mockCameraInfoId,
+ ));
});
test('getDefaultPointSize returns expected size', () async {
diff --git a/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart b/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart
index ba199f66c63..e7a3d9fadb1 100644
--- a/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart
@@ -1,11 +1,18 @@
-// Mocks generated by Mockito 5.4.3 from annotations
+// Mocks generated by Mockito 5.4.4 from annotations
// in camera_android_camerax/test/metering_point_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i6;
+
+import 'package:camera_android_camerax/src/camera_info.dart' as _i5;
+import 'package:camera_android_camerax/src/camera_state.dart' as _i7;
+import 'package:camera_android_camerax/src/exposure_state.dart' as _i3;
+import 'package:camera_android_camerax/src/live_data.dart' as _i2;
+import 'package:camera_android_camerax/src/zoom_state.dart' as _i8;
import 'package:mockito/mockito.dart' as _i1;
-import 'test_camerax_library.g.dart' as _i2;
+import 'test_camerax_library.g.dart' as _i4;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -20,11 +27,32 @@ import 'test_camerax_library.g.dart' as _i2;
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
+class _FakeLiveData_0 extends _i1.SmartFake
+ implements _i2.LiveData {
+ _FakeLiveData_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeExposureState_1 extends _i1.SmartFake implements _i3.ExposureState {
+ _FakeExposureState_1(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
/// A class which mocks [TestInstanceManagerHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestInstanceManagerHostApi extends _i1.Mock
- implements _i2.TestInstanceManagerHostApi {
+ implements _i4.TestInstanceManagerHostApi {
MockTestInstanceManagerHostApi() {
_i1.throwOnMissingStub(this);
}
@@ -43,7 +71,7 @@ class MockTestInstanceManagerHostApi extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockTestMeteringPointHostApi extends _i1.Mock
- implements _i2.TestMeteringPointHostApi {
+ implements _i4.TestMeteringPointHostApi {
MockTestMeteringPointHostApi() {
_i1.throwOnMissingStub(this);
}
@@ -54,6 +82,7 @@ class MockTestMeteringPointHostApi extends _i1.Mock
double? x,
double? y,
double? size,
+ int? cameraInfoId,
) =>
super.noSuchMethod(
Invocation.method(
@@ -63,6 +92,7 @@ class MockTestMeteringPointHostApi extends _i1.Mock
x,
y,
size,
+ cameraInfoId,
],
),
returnValueForMissingStub: null,
@@ -77,3 +107,70 @@ class MockTestMeteringPointHostApi extends _i1.Mock
returnValue: 0.0,
) as double);
}
+
+/// A class which mocks [CameraInfo].
+///
+/// See the documentation for Mockito's code generation for more information.
+// ignore: must_be_immutable
+class MockCameraInfo extends _i1.Mock implements _i5.CameraInfo {
+ MockCameraInfo() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i6.Future getSensorRotationDegrees() => (super.noSuchMethod(
+ Invocation.method(
+ #getSensorRotationDegrees,
+ [],
+ ),
+ returnValue: _i6.Future.value(0),
+ ) as _i6.Future);
+
+ @override
+ _i6.Future<_i2.LiveData<_i7.CameraState>> getCameraState() =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getCameraState,
+ [],
+ ),
+ returnValue: _i6.Future<_i2.LiveData<_i7.CameraState>>.value(
+ _FakeLiveData_0<_i7.CameraState>(
+ this,
+ Invocation.method(
+ #getCameraState,
+ [],
+ ),
+ )),
+ ) as _i6.Future<_i2.LiveData<_i7.CameraState>>);
+
+ @override
+ _i6.Future<_i3.ExposureState> getExposureState() => (super.noSuchMethod(
+ Invocation.method(
+ #getExposureState,
+ [],
+ ),
+ returnValue: _i6.Future<_i3.ExposureState>.value(_FakeExposureState_1(
+ this,
+ Invocation.method(
+ #getExposureState,
+ [],
+ ),
+ )),
+ ) as _i6.Future<_i3.ExposureState>);
+
+ @override
+ _i6.Future<_i2.LiveData<_i8.ZoomState>> getZoomState() => (super.noSuchMethod(
+ Invocation.method(
+ #getZoomState,
+ [],
+ ),
+ returnValue: _i6.Future<_i2.LiveData<_i8.ZoomState>>.value(
+ _FakeLiveData_0<_i8.ZoomState>(
+ this,
+ Invocation.method(
+ #getZoomState,
+ [],
+ ),
+ )),
+ ) as _i6.Future<_i2.LiveData<_i8.ZoomState>>);
+}
diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
index 4450f97c82d..c847327cce5 100644
--- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart
@@ -1898,11 +1898,11 @@ abstract class TestCameraControlHostApi {
Future setZoomRatio(int identifier, double ratio);
- Future startFocusAndMetering(int identifier, int focusMeteringActionId);
+ Future startFocusAndMetering(int identifier, int focusMeteringActionId);
Future cancelFocusAndMetering(int identifier);
- Future setExposureCompensationIndex(int identifier, int index);
+ Future setExposureCompensationIndex(int identifier, int index);
static void setup(TestCameraControlHostApi? api,
{BinaryMessenger? binaryMessenger}) {
@@ -1977,7 +1977,7 @@ abstract class TestCameraControlHostApi {
final int? arg_focusMeteringActionId = (args[1] as int?);
assert(arg_focusMeteringActionId != null,
'Argument for dev.flutter.pigeon.CameraControlHostApi.startFocusAndMetering was null, expected non-null int.');
- final int output = await api.startFocusAndMetering(
+ final int? output = await api.startFocusAndMetering(
arg_identifier!, arg_focusMeteringActionId!);
return [output];
});
@@ -2027,7 +2027,7 @@ abstract class TestCameraControlHostApi {
final int? arg_index = (args[1] as int?);
assert(arg_index != null,
'Argument for dev.flutter.pigeon.CameraControlHostApi.setExposureCompensationIndex was null, expected non-null int.');
- final int output = await api.setExposureCompensationIndex(
+ final int? output = await api.setExposureCompensationIndex(
arg_identifier!, arg_index!);
return [output];
});
@@ -2138,7 +2138,8 @@ abstract class TestMeteringPointHostApi {
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec codec = StandardMessageCodec();
- void create(int identifier, double x, double y, double? size);
+ void create(
+ int identifier, double x, double y, double? size, int cameraInfoId);
double getDefaultPointSize();
@@ -2168,7 +2169,11 @@ abstract class TestMeteringPointHostApi {
assert(arg_y != null,
'Argument for dev.flutter.pigeon.MeteringPointHostApi.create was null, expected non-null double.');
final double? arg_size = (args[3] as double?);
- api.create(arg_identifier!, arg_x!, arg_y!, arg_size);
+ final int? arg_cameraInfoId = (args[4] as int?);
+ assert(arg_cameraInfoId != null,
+ 'Argument for dev.flutter.pigeon.MeteringPointHostApi.create was null, expected non-null int.');
+ api.create(
+ arg_identifier!, arg_x!, arg_y!, arg_size, arg_cameraInfoId!);
return [];
});
}