diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 12dd55552974..1c901a82bfac 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -20,3 +20,4 @@ * Updates internal Java InstanceManager to only stop finalization callbacks when stopped. * Implements image streaming. * Provides LifecycleOwner implementation for Activities that use the plugin that do not implement it themselves. +* Implements retrieval of camera information. 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 31f996d26e24..0289c93cb7cf 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 @@ -48,8 +48,10 @@ public void setUp( // Set up Host APIs. GeneratedCameraXLibrary.InstanceManagerHostApi.setup( binaryMessenger, () -> instanceManager.clear()); + GeneratedCameraXLibrary.CameraHostApi.setup( + binaryMessenger, new CameraHostApiImpl(binaryMessenger, instanceManager)); GeneratedCameraXLibrary.CameraInfoHostApi.setup( - binaryMessenger, new CameraInfoHostApiImpl(instanceManager)); + binaryMessenger, new CameraInfoHostApiImpl(binaryMessenger, instanceManager)); GeneratedCameraXLibrary.CameraSelectorHostApi.setup( binaryMessenger, new CameraSelectorHostApiImpl(binaryMessenger, instanceManager)); GeneratedCameraXLibrary.JavaObjectHostApi.setup( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraHostApiImpl.java new file mode 100644 index 000000000000..2c40b13c008d --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraHostApiImpl.java @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import androidx.annotation.NonNull; +import androidx.camera.core.Camera; +import androidx.camera.core.CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraHostApi; +import java.util.Objects; + +public class CameraHostApiImpl implements CameraHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + + public CameraHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + } + + /** + * Retrieves the {@link CameraInfo} instance that contains information about the {@link Camera} + * instance with the specified identifier. + */ + @Override + @NonNull + public Long getCameraInfo(@NonNull Long identifier) { + Camera camera = (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier)); + CameraInfo cameraInfo = camera.getCameraInfo(); + + CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl = + new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); + cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {}); + + return instanceManager.getIdentifierForStrongReference(cameraInfo); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoFlutterApiImpl.java index c538e420cc7e..d19349bcd839 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoFlutterApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoFlutterApiImpl.java @@ -18,6 +18,8 @@ public CameraInfoFlutterApiImpl( } void create(CameraInfo cameraInfo, Reply reply) { - create(instanceManager.addHostCreatedInstance(cameraInfo), reply); + if (!instanceManager.containsInstance(cameraInfo)) { + create(instanceManager.addHostCreatedInstance(cameraInfo), reply); + } } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java index d960b7fff70a..f9fd7722fc87 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java @@ -6,20 +6,64 @@ import androidx.annotation.NonNull; import androidx.camera.core.CameraInfo; +import androidx.camera.core.ExposureState; +import androidx.camera.core.ZoomState; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoHostApi; import java.util.Objects; public class CameraInfoHostApiImpl implements CameraInfoHostApi { + private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; - public CameraInfoHostApiImpl(InstanceManager instanceManager) { + public CameraInfoHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; } + /** Retrieves the sensor rotation in degrees, relative to the device's default orientation. */ @Override + @NonNull public Long getSensorRotationDegrees(@NonNull Long identifier) { CameraInfo cameraInfo = (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier)); return Long.valueOf(cameraInfo.getSensorRotationDegrees()); } + + /** + * Retrieves the {@link ExposureState} of the {@link CameraInfo} with the specified identifier. + */ + @Override + @NonNull + public Long getExposureState(@NonNull Long identifier) { + CameraInfo cameraInfo = + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier)); + ExposureState exposureState = cameraInfo.getExposureState(); + + ExposureStateFlutterApiImpl exposureStateFlutterApiImpl = + new ExposureStateFlutterApiImpl(binaryMessenger, instanceManager); + exposureStateFlutterApiImpl.create(exposureState, result -> {}); + + return instanceManager.getIdentifierForStrongReference(exposureState); + } + + /** + * Retrieves the current {@link ZoomState} value of the {@link CameraInfo} with the specified + * identifier. + */ + @NonNull + @Override + public Long getZoomState(@NonNull Long identifier) { + CameraInfo cameraInfo = + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier)); + // TODO(camsim99): Create/return LiveData once https://github.com/flutter/packages/pull/3419 lands. + ZoomState zoomState = cameraInfo.getZoomState().getValue(); + + ZoomStateFlutterApiImpl zoomStateFlutterApiImpl = + new ZoomStateFlutterApiImpl(binaryMessenger, instanceManager); + zoomStateFlutterApiImpl.create(zoomState, result -> {}); + + return instanceManager.getIdentifierForStrongReference(zoomState); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ExposureStateFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ExposureStateFlutterApiImpl.java new file mode 100644 index 000000000000..1f31f603a8a4 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ExposureStateFlutterApiImpl.java @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import android.util.Range; +import androidx.annotation.NonNull; +import androidx.camera.core.ExposureState; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ExposureCompensationRange; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ExposureStateFlutterApi; + +public class ExposureStateFlutterApiImpl extends ExposureStateFlutterApi { + private final InstanceManager instanceManager; + + public ExposureStateFlutterApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + /** + * Creates a {@link ExposureState} on the Dart side with its exposure compensation range that can + * be used to set the exposure compensation index and its exposure compensation step, the smallest + * step by which the exposure compensation can be changed. + */ + void create(@NonNull ExposureState exposureState, @NonNull Reply reply) { + if (instanceManager.containsInstance(exposureState)) { + return; + } + + final Range exposureCompensationRangeFromState = + exposureState.getExposureCompensationRange(); + ExposureCompensationRange exposureCompensationRange = + new ExposureCompensationRange.Builder() + .setMinCompensation(exposureCompensationRangeFromState.getLower().longValue()) + .setMaxCompensation(exposureCompensationRangeFromState.getUpper().longValue()) + .build(); + final Double exposureCompensationStep = + exposureState.getExposureCompensationStep().doubleValue(); + + create( + instanceManager.addHostCreatedInstance(exposureState), + exposureCompensationRange, + exposureCompensationStep, + reply); + } +} 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 31cea4add1f1..882c1d146323 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 @@ -207,6 +207,89 @@ ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class ExposureCompensationRange { + private @NonNull Long minCompensation; + + public @NonNull Long getMinCompensation() { + return minCompensation; + } + + public void setMinCompensation(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"minCompensation\" is null."); + } + this.minCompensation = setterArg; + } + + private @NonNull Long maxCompensation; + + public @NonNull Long getMaxCompensation() { + return maxCompensation; + } + + public void setMaxCompensation(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"maxCompensation\" is null."); + } + this.maxCompensation = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + ExposureCompensationRange() {} + + public static final class Builder { + + private @Nullable Long minCompensation; + + public @NonNull Builder setMinCompensation(@NonNull Long setterArg) { + this.minCompensation = setterArg; + return this; + } + + private @Nullable Long maxCompensation; + + public @NonNull Builder setMaxCompensation(@NonNull Long setterArg) { + this.maxCompensation = setterArg; + return this; + } + + public @NonNull ExposureCompensationRange build() { + ExposureCompensationRange pigeonReturn = new ExposureCompensationRange(); + pigeonReturn.setMinCompensation(minCompensation); + pigeonReturn.setMaxCompensation(maxCompensation); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(minCompensation); + toListResult.add(maxCompensation); + return toListResult; + } + + static @NonNull ExposureCompensationRange fromList(@NonNull ArrayList list) { + ExposureCompensationRange pigeonResult = new ExposureCompensationRange(); + Object minCompensation = list.get(0); + pigeonResult.setMinCompensation( + (minCompensation == null) + ? null + : ((minCompensation instanceof Integer) + ? (Integer) minCompensation + : (Long) minCompensation)); + Object maxCompensation = list.get(1); + pigeonResult.setMaxCompensation( + (maxCompensation == null) + ? null + : ((maxCompensation instanceof Integer) + ? (Integer) maxCompensation + : (Long) maxCompensation)); + return pigeonResult; + } + } + public interface Result { @SuppressWarnings("UnknownNullness") void success(T result); @@ -326,6 +409,12 @@ public interface CameraInfoHostApi { @NonNull Long getSensorRotationDegrees(@NonNull Long identifier); + @NonNull + Long getExposureState(@NonNull Long identifier); + + @NonNull + Long getZoomState(@NonNull Long identifier); + /** The codec used by CameraInfoHostApi. */ static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); @@ -361,6 +450,57 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable CameraInfo channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.CameraInfoHostApi.getExposureState", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + Long output = + api.getExposureState( + (identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.CameraInfoHostApi.getZoomState", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + Long output = + api.getZoomState((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ @@ -723,6 +863,44 @@ public void create(@NonNull Long identifierArg, @NonNull Reply callback) { channelReply -> callback.reply(null)); } } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface CameraHostApi { + + @NonNull + Long getCameraInfo(@NonNull Long identifier); + + /** The codec used by CameraHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** Sets up an instance of `CameraHostApi` to handle messages through the `binaryMessenger`. */ + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable CameraHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.CameraHostApi.getCameraInfo", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + Long output = + api.getCameraInfo((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class CameraFlutterApi { private final @NonNull BinaryMessenger binaryMessenger; @@ -1217,6 +1395,97 @@ public void error(Throwable error) { } } + private static class ExposureStateFlutterApiCodec extends StandardMessageCodec { + public static final ExposureStateFlutterApiCodec INSTANCE = new ExposureStateFlutterApiCodec(); + + private ExposureStateFlutterApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return ExposureCompensationRange.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof ExposureCompensationRange) { + stream.write(128); + writeValue(stream, ((ExposureCompensationRange) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class ExposureStateFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public ExposureStateFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by ExposureStateFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return ExposureStateFlutterApiCodec.INSTANCE; + } + + public void create( + @NonNull Long identifierArg, + @NonNull ExposureCompensationRange exposureCompensationRangeArg, + @NonNull Double exposureCompensationStepArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ExposureStateFlutterApi.create", getCodec()); + channel.send( + new ArrayList( + Arrays.asList( + identifierArg, exposureCompensationRangeArg, exposureCompensationStepArg)), + channelReply -> callback.reply(null)); + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class ZoomStateFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public ZoomStateFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by ZoomStateFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void create( + @NonNull Long identifierArg, + @NonNull Double minZoomRatioArg, + @NonNull Double maxZoomRatioArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ZoomStateFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Arrays.asList(identifierArg, minZoomRatioArg, maxZoomRatioArg)), + channelReply -> callback.reply(null)); + } + } + private static class ImageAnalysisHostApiCodec extends StandardMessageCodec { public static final ImageAnalysisHostApiCodec INSTANCE = new ImageAnalysisHostApiCodec(); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ZoomStateFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ZoomStateFlutterApiImpl.java new file mode 100644 index 000000000000..4f5abdd6d181 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ZoomStateFlutterApiImpl.java @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import androidx.annotation.NonNull; +import androidx.camera.core.ZoomState; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ZoomStateFlutterApi; + +public class ZoomStateFlutterApiImpl extends ZoomStateFlutterApi { + private final InstanceManager instanceManager; + + public ZoomStateFlutterApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + /** + * Creates a {@link ZoomState} on the Dart side with its minimum zoom ratio and maximum zoom + * ratio. + */ + void create(@NonNull ZoomState zoomState, @NonNull Reply reply) { + if (instanceManager.containsInstance(zoomState)) { + return; + } + + final Float minZoomRatio = zoomState.getMinZoomRatio(); + final Float maxZoomRatio = zoomState.getMaxZoomRatio(); + create( + instanceManager.addHostCreatedInstance(zoomState), + minZoomRatio.doubleValue(), + maxZoomRatio.doubleValue(), + reply); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java index 0cd85848e71a..5cfd06935b85 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java @@ -7,11 +7,15 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import androidx.camera.core.CameraInfo; +import androidx.camera.core.ExposureState; +import androidx.camera.core.ZoomState; +import androidx.lifecycle.LiveData; import io.flutter.plugin.common.BinaryMessenger; import java.util.Objects; import org.junit.After; @@ -41,8 +45,9 @@ public void tearDown() { } @Test - public void getSensorRotationDegreesTest() { - final CameraInfoHostApiImpl cameraInfoHostApi = new CameraInfoHostApiImpl(testInstanceManager); + public void getSensorRotationDegrees_retrievesExpectedSensorRotation() { + final CameraInfoHostApiImpl cameraInfoHostApi = + new CameraInfoHostApiImpl(mockBinaryMessenger, testInstanceManager); testInstanceManager.addDartCreatedInstance(mockCameraInfo, 1); @@ -53,7 +58,47 @@ public void getSensorRotationDegreesTest() { } @Test - public void flutterApiCreateTest() { + public void getExposureState_retrievesExpectedExposureState() { + final CameraInfoHostApiImpl cameraInfoHostApiImpl = + new CameraInfoHostApiImpl(mockBinaryMessenger, testInstanceManager); + final ExposureState mockExposureState = mock(ExposureState.class); + final Long mockCameraInfoIdentifier = 27L; + final Long mockExposureStateIdentifier = 47L; + + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoIdentifier); + testInstanceManager.addDartCreatedInstance(mockExposureState, mockExposureStateIdentifier); + + when(mockCameraInfo.getExposureState()).thenReturn(mockExposureState); + + assertEquals( + cameraInfoHostApiImpl.getExposureState(mockCameraInfoIdentifier), + mockExposureStateIdentifier); + verify(mockCameraInfo).getExposureState(); + } + + @Test + @SuppressWarnings("unchecked") + public void getZoomState_retrievesExpectedZoomState() { + final CameraInfoHostApiImpl cameraInfoHostApiImpl = + new CameraInfoHostApiImpl(mockBinaryMessenger, testInstanceManager); + final LiveData mockLiveZoomState = (LiveData) mock(LiveData.class); + final ZoomState mockZoomState = mock(ZoomState.class); + final Long mockCameraInfoIdentifier = 20L; + final Long mockZoomStateIdentifier = 74L; + + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoIdentifier); + testInstanceManager.addDartCreatedInstance(mockZoomState, mockZoomStateIdentifier); + + when(mockCameraInfo.getZoomState()).thenReturn(mockLiveZoomState); + when(mockLiveZoomState.getValue()).thenReturn(mockZoomState); + + assertEquals( + cameraInfoHostApiImpl.getZoomState(mockCameraInfoIdentifier), mockZoomStateIdentifier); + verify(mockCameraInfo).getZoomState(); + } + + @Test + public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() { final CameraInfoFlutterApiImpl spyFlutterApi = spy(new CameraInfoFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java index af4fb2ce8e61..fb6b0b3cf353 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java @@ -4,12 +4,16 @@ package io.flutter.plugins.camerax; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import androidx.camera.core.Camera; +import androidx.camera.core.CameraInfo; import io.flutter.plugin.common.BinaryMessenger; import java.util.Objects; import org.junit.After; @@ -39,7 +43,24 @@ public void tearDown() { } @Test - public void flutterApiCreateTest() { + public void getCameraInfo_retrievesExpectedCameraInfoInstance() { + final CameraHostApiImpl cameraHostApiImpl = + new CameraHostApiImpl(mockBinaryMessenger, testInstanceManager); + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final Long cameraIdentifier = 34L; + final Long mockCameraInfoIdentifier = 97L; + + testInstanceManager.addDartCreatedInstance(camera, cameraIdentifier); + testInstanceManager.addDartCreatedInstance(mockCameraInfo, mockCameraInfoIdentifier); + + when(camera.getCameraInfo()).thenReturn(mockCameraInfo); + + assertEquals(cameraHostApiImpl.getCameraInfo(cameraIdentifier), mockCameraInfoIdentifier); + verify(camera).getCameraInfo(); + } + + @Test + public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() { final CameraFlutterApiImpl spyFlutterApi = spy(new CameraFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ExposureStateTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ExposureStateTest.java new file mode 100644 index 000000000000..7eef68d28d12 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ExposureStateTest.java @@ -0,0 +1,84 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.util.Range; +import android.util.Rational; +import androidx.camera.core.ExposureState; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ExposureCompensationRange; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +public class ExposureStateTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public ExposureState mockExposureState; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.stopFinalizationListener(); + } + + @Config(sdk = 21) + @Test + public void create_makesExpectedCallToCreateInstanceOnDartSide() { + // SDK version configured because ExposureState requires Android 21. + ExposureStateFlutterApiImpl exposureStateFlutterApiImpl = + spy(new ExposureStateFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); + final int minExposureCompensation = 0; + final int maxExposureCompensation = 1; + Range testExposueCompensationRange = + new Range(minExposureCompensation, maxExposureCompensation); + Rational textExposureCompensationStep = new Rational(1, 5); // Makes expected Double value 0.2. + + when(mockExposureState.getExposureCompensationRange()).thenReturn(testExposueCompensationRange); + when(mockExposureState.getExposureCompensationStep()).thenReturn(textExposureCompensationStep); + + final ArgumentCaptor exposureCompensationRangeCaptor = + ArgumentCaptor.forClass(ExposureCompensationRange.class); + + exposureStateFlutterApiImpl.create(mockExposureState, reply -> {}); + + final long identifier = + Objects.requireNonNull( + testInstanceManager.getIdentifierForStrongReference(mockExposureState)); + verify(exposureStateFlutterApiImpl) + .create(eq(identifier), exposureCompensationRangeCaptor.capture(), eq(0.2), any()); + + ExposureCompensationRange exposureCompensationRange = + exposureCompensationRangeCaptor.getValue(); + assertEquals( + exposureCompensationRange.getMinCompensation().intValue(), minExposureCompensation); + assertEquals( + exposureCompensationRange.getMaxCompensation().intValue(), maxExposureCompensation); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ZoomStateTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ZoomStateTest.java new file mode 100644 index 000000000000..a4f53ad80c5d --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ZoomStateTest.java @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.camera.core.ZoomState; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class ZoomStateTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public ZoomState mockZoomState; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.stopFinalizationListener(); + } + + @Test + public void create_makesExpectedCallToCreateInstanceOnDartSide() { + ZoomStateFlutterApiImpl zoomStateFlutterApiImpl = + spy(new ZoomStateFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); + final Float testMinZoomRatio = 0F; + final Float testMaxZoomRatio = 1F; + + when(mockZoomState.getMinZoomRatio()).thenReturn(testMinZoomRatio); + when(mockZoomState.getMaxZoomRatio()).thenReturn(testMaxZoomRatio); + + zoomStateFlutterApiImpl.create(mockZoomState, reply -> {}); + + final long identifier = + Objects.requireNonNull(testInstanceManager.getIdentifierForStrongReference(mockZoomState)); + verify(zoomStateFlutterApiImpl) + .create( + eq(identifier), + eq(testMinZoomRatio.doubleValue()), + eq(testMaxZoomRatio.doubleValue()), + any()); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 7975c9851074..174a373e3b9b 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 @@ -13,6 +13,7 @@ import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camerax_library.g.dart'; +import 'exposure_state.dart'; import 'image_analysis.dart'; import 'image_capture.dart'; import 'image_proxy.dart'; @@ -22,6 +23,7 @@ import 'process_camera_provider.dart'; import 'surface.dart'; import 'system_services.dart'; import 'use_case.dart'; +import 'zoom_state.dart'; /// The Android implementation of [CameraPlatform] that uses the CameraX library. class AndroidCameraCameraX extends CameraPlatform { @@ -39,6 +41,10 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting Camera? camera; + /// The [CameraInfo] instance that corresponds to the [camera] instance. + @visibleForTesting + CameraInfo? cameraInfo; + /// The [Preview] instance that can be configured to present a live camera preview. @visibleForTesting Preview? preview; @@ -190,6 +196,7 @@ class AndroidCameraCameraX extends CameraPlatform { // instance as bound but not paused. camera = await processCameraProvider! .bindToLifecycle(cameraSelector!, [preview!, imageCapture!]); + cameraInfo = await camera!.getCameraInfo(); _previewIsPaused = false; return flutterSurfaceTextureId; @@ -269,6 +276,55 @@ class AndroidCameraCameraX extends CameraPlatform { }); } + /// Gets the minimum supported exposure offset for the selected camera in EV units. + /// + /// [cameraId] not used. + @override + Future getMinExposureOffset(int cameraId) async { + final ExposureState exposureState = await cameraInfo!.getExposureState(); + return exposureState.exposureCompensationRange.minCompensation * + exposureState.exposureCompensationStep; + } + + /// Gets the maximum supported exposure offset for the selected camera in EV units. + /// + /// [cameraId] not used. + @override + Future getMaxExposureOffset(int cameraId) async { + final ExposureState exposureState = await cameraInfo!.getExposureState(); + return exposureState.exposureCompensationRange.maxCompensation * + exposureState.exposureCompensationStep; + } + + /// Gets the supported step size for exposure offset for the selected camera in EV units. + /// + /// Returns 0 when exposure compensation is not supported. + /// + /// [cameraId] not used. + @override + Future getExposureOffsetStepSize(int cameraId) async { + final ExposureState exposureState = await cameraInfo!.getExposureState(); + return exposureState.exposureCompensationStep; + } + + /// Gets the maximum supported zoom level for the selected camera. + /// + /// [cameraId] not used. + @override + Future getMaxZoomLevel(int cameraId) async { + final ZoomState exposureState = await cameraInfo!.getZoomState(); + return exposureState.maxZoomRatio; + } + + /// Gets the minimum supported zoom level for the selected camera. + /// + /// [cameraId] not used. + @override + Future getMinZoomLevel(int cameraId) async { + final ZoomState exposureState = await cameraInfo!.getZoomState(); + return exposureState.minZoomRatio; + } + /// The ui orientation changed. @override Stream onDeviceOrientationChanged() { @@ -357,6 +413,7 @@ class AndroidCameraCameraX extends CameraPlatform { camera = await processCameraProvider! .bindToLifecycle(cameraSelector!, [preview!]); + cameraInfo = await camera!.getCameraInfo(); } /// Configures the [imageAnalysis] instance for image streaming and binds it @@ -389,6 +446,7 @@ class AndroidCameraCameraX extends CameraPlatform { planes: cameraImagePlanes, height: imageProxy.height, width: imageProxy.width); + cameraImageDataStreamController?.add(cameraImageData); imageProxy.close(); } @@ -406,6 +464,7 @@ class AndroidCameraCameraX extends CameraPlatform { // https://github.com/flutter/packages/pull/3419 lands. camera = await processCameraProvider! .bindToLifecycle(cameraSelector!, [imageAnalysis!]); + cameraInfo = await camera!.getCameraInfo(); } /// Unbinds [useCase] from camera lifecycle controlled by the @@ -423,7 +482,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// The [onListen] callback for the stream controller used for image /// streaming. - void _onFrameStreamListen() { + Future _onFrameStreamListen() async { _configureAndBindImageAnalysisToLifecycle(); } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index 493071ec852a..c5692ae105f2 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -7,11 +7,13 @@ import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camerax_library.g.dart'; +import 'exposure_state.dart'; import 'image_proxy.dart'; import 'java_object.dart'; import 'plane_proxy.dart'; import 'process_camera_provider.dart'; import 'system_services.dart'; +import 'zoom_state.dart'; /// Handles initialization of Flutter APIs for the Android CameraX library. class AndroidCameraXCameraFlutterApis { @@ -23,6 +25,8 @@ class AndroidCameraXCameraFlutterApis { CameraSelectorFlutterApiImpl? cameraSelectorFlutterApi, ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApi, SystemServicesFlutterApiImpl? systemServicesFlutterApi, + ExposureStateFlutterApiImpl? exposureStateFlutterApiImpl, + ZoomStateFlutterApiImpl? zoomStateFlutterApiImpl, AnalyzerFlutterApiImpl? analyzerFlutterApiImpl, ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl, PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl, @@ -38,6 +42,10 @@ class AndroidCameraXCameraFlutterApis { this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl(); this.systemServicesFlutterApi = systemServicesFlutterApi ?? SystemServicesFlutterApiImpl(); + this.exposureStateFlutterApiImpl = + exposureStateFlutterApiImpl ?? ExposureStateFlutterApiImpl(); + this.zoomStateFlutterApiImpl = + zoomStateFlutterApiImpl ?? ZoomStateFlutterApiImpl(); this.analyzerFlutterApiImpl = analyzerFlutterApiImpl ?? AnalyzerFlutterApiImpl(); this.imageProxyFlutterApiImpl = @@ -73,6 +81,12 @@ class AndroidCameraXCameraFlutterApis { /// Flutter Api for [SystemServices]. late final SystemServicesFlutterApiImpl systemServicesFlutterApi; + /// Flutter Api for [ExposureState]. + late final ExposureStateFlutterApiImpl exposureStateFlutterApiImpl; + + /// Flutter Api for [ZoomState]. + late final ZoomStateFlutterApiImpl zoomStateFlutterApiImpl; + /// Flutter Api implementation for [Analyzer]. late final AnalyzerFlutterApiImpl analyzerFlutterApiImpl; @@ -91,6 +105,8 @@ class AndroidCameraXCameraFlutterApis { ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi); CameraFlutterApi.setup(cameraFlutterApi); SystemServicesFlutterApi.setup(systemServicesFlutterApi); + ExposureStateFlutterApi.setup(exposureStateFlutterApiImpl); + ZoomStateFlutterApi.setup(zoomStateFlutterApiImpl); AnalyzerFlutterApi.setup(analyzerFlutterApiImpl); ImageProxyFlutterApi.setup(imageProxyFlutterApiImpl); PlaneProxyFlutterApi.setup(planeProxyFlutterApiImpl); diff --git a/packages/camera/camera_android_camerax/lib/src/camera.dart b/packages/camera/camera_android_camerax/lib/src/camera.dart index 51e0813cdd32..65821f0734cc 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart' show BinaryMessenger; 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'; @@ -15,10 +16,53 @@ import 'java_object.dart'; /// See https://developer.android.com/reference/androidx/camera/core/Camera. class Camera extends JavaObject { /// Constructs a [Camera] that is not automatically attached to a native object. - Camera.detached({super.binaryMessenger, super.instanceManager}) - : super.detached() { + Camera.detached( + {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = CameraHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } + + late final CameraHostApiImpl _api; + + /// Retrieve the [CameraInfo] instance that contains information about this + /// instance. + Future getCameraInfo() async { + return _api.getCameraInfoFromInstance(this); + } +} + +/// Host API implementation of [Camera]. +class CameraHostApiImpl extends CameraHostApi { + /// Constructs a [CameraHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. + CameraHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager}) + : super(binaryMessenger: binaryMessenger) { + this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + } + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + late final InstanceManager instanceManager; + + /// Gets the [CameraInfo] associated with the specified instance of [Camera]. + Future getCameraInfoFromInstance(Camera instance) async { + final int identifier = instanceManager.getIdentifier(instance)!; + final int cameraInfoId = await getCameraInfo(identifier); + + return instanceManager + .getInstanceWithWeakReference(cameraInfoId)!; + } } /// Flutter API implementation of [Camera]. @@ -26,7 +70,7 @@ class CameraFlutterApiImpl implements CameraFlutterApi { /// Constructs a [CameraFlutterApiImpl]. /// /// An [instanceManager] is typically passed when a copy of an instance - /// contained by an `InstanceManager` is being created. + /// contained by an [InstanceManager] is being created. CameraFlutterApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, diff --git a/packages/camera/camera_android_camerax/lib/src/camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera_info.dart index 8c2c7bcf0aec..f2fdc1fdc879 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_info.dart @@ -6,8 +6,10 @@ import 'package:flutter/services.dart' show BinaryMessenger; import 'android_camera_camerax_flutter_api_impls.dart'; import 'camerax_library.g.dart'; +import 'exposure_state.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'zoom_state.dart'; /// Represents the metadata of a camera. /// @@ -19,22 +21,29 @@ class CameraInfo extends JavaObject { : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { - _api = CameraInfoHostApiImpl( + _api = _CameraInfoHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } - late final CameraInfoHostApiImpl _api; + late final _CameraInfoHostApiImpl _api; - /// Gets sensor orientation degrees of camera. + /// Gets sensor orientation degrees of the camera. Future getSensorRotationDegrees() => _api.getSensorRotationDegreesFromInstance(this); + + /// Gets the exposure state of the camera. + Future getExposureState() => + _api.getExposureStateFromInstance(this); + + /// Gets the zoom state of the camera. + Future getZoomState() => _api.getZoomStateFromInstance(this); } /// Host API implementation of [CameraInfo]. -class CameraInfoHostApiImpl extends CameraInfoHostApi { - /// Constructs a [CameraInfoHostApiImpl]. - CameraInfoHostApiImpl( +class _CameraInfoHostApiImpl extends CameraInfoHostApi { + /// Constructs a [_CameraInfoHostApiImpl]. + _CameraInfoHostApiImpl( {super.binaryMessenger, InstanceManager? instanceManager}) { this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager; } @@ -42,7 +51,7 @@ class CameraInfoHostApiImpl extends CameraInfoHostApi { /// Maintains instances stored to communicate with native language objects. late final InstanceManager instanceManager; - /// Gets sensor orientation degrees of [CameraInfo]. + /// Gets sensor orientation degrees of the specified [CameraInfo] instance. Future getSensorRotationDegreesFromInstance( CameraInfo instance, ) async { @@ -50,6 +59,23 @@ class CameraInfoHostApiImpl extends CameraInfoHostApi { instanceManager.getIdentifier(instance)!); return sensorRotationDegrees; } + + /// Gets the [ExposureState] of the specified [CameraInfo] instance. + Future getExposureStateFromInstance( + CameraInfo instance) async { + final int? identifier = instanceManager.getIdentifier(instance); + final int exposureStateIdentifier = await getExposureState(identifier!); + return instanceManager + .getInstanceWithWeakReference(exposureStateIdentifier)!; + } + + /// Gets the [ZoomState] of the specified [CameraInfo] instance. + Future getZoomStateFromInstance(CameraInfo instance) async { + final int? identifier = instanceManager.getIdentifier(instance); + final int zoomStateIdentifier = await getZoomState(identifier!); + return instanceManager + .getInstanceWithWeakReference(zoomStateIdentifier)!; + } } /// Flutter API implementation of [CameraInfo]. 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 5e8e95548d38..f2d2e17152fb 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 @@ -63,6 +63,32 @@ class CameraPermissionsErrorData { } } +class ExposureCompensationRange { + ExposureCompensationRange({ + required this.minCompensation, + required this.maxCompensation, + }); + + int minCompensation; + + int maxCompensation; + + Object encode() { + return [ + minCompensation, + maxCompensation, + ]; + } + + static ExposureCompensationRange decode(Object result) { + result as List; + return ExposureCompensationRange( + minCompensation: result[0]! as int, + maxCompensation: result[1]! as int, + ); + } +} + class InstanceManagerHostApi { /// Constructor for [InstanceManagerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -196,6 +222,60 @@ class CameraInfoHostApi { return (replyList[0] as int?)!; } } + + Future getExposureState(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraInfoHostApi.getExposureState', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as int?)!; + } + } + + Future getZoomState(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraInfoHostApi.getZoomState', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as int?)!; + } + } } abstract class CameraInfoFlutterApi { @@ -516,6 +596,44 @@ abstract class ProcessCameraProviderFlutterApi { } } +class CameraHostApi { + /// Constructor for [CameraHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + CameraHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future getCameraInfo(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraHostApi.getCameraInfo', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as int?)!; + } + } +} + abstract class CameraFlutterApi { static const MessageCodec codec = StandardMessageCodec(); @@ -945,6 +1063,104 @@ class ImageCaptureHostApi { } } +class _ExposureStateFlutterApiCodec extends StandardMessageCodec { + const _ExposureStateFlutterApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ExposureCompensationRange) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ExposureCompensationRange.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class ExposureStateFlutterApi { + static const MessageCodec codec = _ExposureStateFlutterApiCodec(); + + void create( + int identifier, + ExposureCompensationRange exposureCompensationRange, + double exposureCompensationStep); + + static void setup(ExposureStateFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ExposureStateFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ExposureStateFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ExposureStateFlutterApi.create was null, expected non-null int.'); + final ExposureCompensationRange? arg_exposureCompensationRange = + (args[1] as ExposureCompensationRange?); + assert(arg_exposureCompensationRange != null, + 'Argument for dev.flutter.pigeon.ExposureStateFlutterApi.create was null, expected non-null ExposureCompensationRange.'); + final double? arg_exposureCompensationStep = (args[2] as double?); + assert(arg_exposureCompensationStep != null, + 'Argument for dev.flutter.pigeon.ExposureStateFlutterApi.create was null, expected non-null double.'); + api.create(arg_identifier!, arg_exposureCompensationRange!, + arg_exposureCompensationStep!); + return; + }); + } + } + } +} + +abstract class ZoomStateFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier, double minZoomRatio, double maxZoomRatio); + + static void setup(ZoomStateFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ZoomStateFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ZoomStateFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ZoomStateFlutterApi.create was null, expected non-null int.'); + final double? arg_minZoomRatio = (args[1] as double?); + assert(arg_minZoomRatio != null, + 'Argument for dev.flutter.pigeon.ZoomStateFlutterApi.create was null, expected non-null double.'); + final double? arg_maxZoomRatio = (args[2] as double?); + assert(arg_maxZoomRatio != null, + 'Argument for dev.flutter.pigeon.ZoomStateFlutterApi.create was null, expected non-null double.'); + api.create(arg_identifier!, arg_minZoomRatio!, arg_maxZoomRatio!); + return; + }); + } + } + } +} + class _ImageAnalysisHostApiCodec extends StandardMessageCodec { const _ImageAnalysisHostApiCodec(); @override diff --git a/packages/camera/camera_android_camerax/lib/src/exposure_state.dart b/packages/camera/camera_android_camerax/lib/src/exposure_state.dart new file mode 100644 index 000000000000..4599a929995e --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/exposure_state.dart @@ -0,0 +1,76 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart' show BinaryMessenger; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// Represents exposure related information of a camera. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ExposureState. +class ExposureState extends JavaObject { + /// Constructs a [ExposureState] that is not automatically attached to a native object. + ExposureState.detached( + {super.binaryMessenger, + super.instanceManager, + required this.exposureCompensationRange, + required this.exposureCompensationStep}) + : super.detached() { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + /// Gets the maximum and minimum exposure compensation values for the camera + /// represented by this instance. + final ExposureCompensationRange exposureCompensationRange; + + /// Gets the smallest step by which the exposure compensation can be changed for + /// the camera represented by this instance. + final double exposureCompensationStep; +} + +/// Flutter API implementation of [ExposureState]. +class ExposureStateFlutterApiImpl implements ExposureStateFlutterApi { + /// Constructs a [ExposureStateFlutterApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. + ExposureStateFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create( + int identifier, + ExposureCompensationRange exposureCompensationRange, + double exposureCompensationStep) { + instanceManager.addHostCreatedInstance( + ExposureState.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + exposureCompensationRange: exposureCompensationRange, + exposureCompensationStep: exposureCompensationStep), + identifier, + onCopy: (ExposureState original) { + return ExposureState.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + exposureCompensationRange: original.exposureCompensationRange, + exposureCompensationStep: original.exposureCompensationStep); + }, + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/zoom_state.dart b/packages/camera/camera_android_camerax/lib/src/zoom_state.dart new file mode 100644 index 000000000000..98068820b154 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/zoom_state.dart @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart' show BinaryMessenger; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// Represents zoom related information of a camera. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ZoomState. +class ZoomState extends JavaObject { + /// Constructs a [CameraInfo] that is not automatically attached to a native object. + ZoomState.detached( + {super.binaryMessenger, + super.instanceManager, + required this.minZoomRatio, + required this.maxZoomRatio}) + : super.detached() { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + /// The minimum zoom ratio of the camera represented by this instance. + final double minZoomRatio; + + /// The maximum zoom ratio of the camera represented by this instance. + final double maxZoomRatio; +} + +/// Flutter API implementation of [ZoomState]. +class ZoomStateFlutterApiImpl implements ZoomStateFlutterApi { + /// Constructs a [ZoomStateFlutterApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. + ZoomStateFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create(int identifier, double minZoomRatio, double maxZoomRatio) { + instanceManager.addHostCreatedInstance( + ZoomState.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + minZoomRatio: minZoomRatio, + maxZoomRatio: maxZoomRatio), + identifier, + onCopy: (ZoomState original) { + return ZoomState.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + minZoomRatio: original.minZoomRatio, + maxZoomRatio: original.maxZoomRatio); + }, + ); + } +} diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 3d492709c900..c8b3885d064b 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -46,6 +46,16 @@ class CameraPermissionsErrorData { String description; } +class ExposureCompensationRange { + ExposureCompensationRange({ + required this.minCompensation, + required this.maxCompensation, + }); + + int minCompensation; + int maxCompensation; +} + @HostApi(dartHostTestHandler: 'TestInstanceManagerHostApi') abstract class InstanceManagerHostApi { /// Clear the native `InstanceManager`. @@ -67,6 +77,10 @@ abstract class JavaObjectFlutterApi { @HostApi(dartHostTestHandler: 'TestCameraInfoHostApi') abstract class CameraInfoHostApi { int getSensorRotationDegrees(int identifier); + + int getExposureState(int identifier); + + int getZoomState(int identifier); } @FlutterApi() @@ -108,6 +122,11 @@ abstract class ProcessCameraProviderFlutterApi { void create(int identifier); } +@HostApi(dartHostTestHandler: 'TestCameraHostApi') +abstract class CameraHostApi { + int getCameraInfo(int identifier); +} + @FlutterApi() abstract class CameraFlutterApi { void create(int identifier); @@ -152,6 +171,19 @@ abstract class ImageCaptureHostApi { String takePicture(int identifier); } +@FlutterApi() +abstract class ExposureStateFlutterApi { + void create( + int identifier, + ExposureCompensationRange exposureCompensationRange, + double exposureCompensationStep); +} + +@FlutterApi() +abstract class ZoomStateFlutterApi { + void create(int identifier, double minZoomRatio, double maxZoomRatio); +} + @HostApi(dartHostTestHandler: 'TestImageAnalysisHostApi') abstract class ImageAnalysisHostApi { void create(int identifier, ResolutionInfo? targetResolutionIdentifier); 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 2db12ebde63b..e7a6707d6e3f 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -11,6 +11,7 @@ import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/exposure_state.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'; @@ -19,6 +20,7 @@ import 'package:camera_android_camerax/src/preview.dart'; import 'package:camera_android_camerax/src/process_camera_provider.dart'; import 'package:camera_android_camerax/src/system_services.dart'; import 'package:camera_android_camerax/src/use_case.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/widgets.dart'; @@ -34,6 +36,7 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -41,6 +44,7 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) @GenerateMocks([BuildContext]) void main() { @@ -113,7 +117,9 @@ void main() { 'createCamera requests permissions, starts listening for device orientation changes, and returns flutter surface texture ID', () async { final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); - camera.processCameraProvider = MockProcessCameraProvider(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; const CameraDescription testCameraDescription = CameraDescription( @@ -124,8 +130,14 @@ void main() { const bool enableAudio = true; const int testSurfaceTextureId = 6; + camera.processCameraProvider = mockProcessCameraProvider; + when(camera.testPreview.setSurfaceProvider()) .thenAnswer((_) async => testSurfaceTextureId); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(MockCameraInfo())); expect( await camera.createCamera(testCameraDescription, testResolutionPreset, @@ -153,7 +165,10 @@ void main() { 'createCamera binds Preview and ImageCapture use cases to ProcessCameraProvider instance', () async { final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); - camera.processCameraProvider = MockProcessCameraProvider(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; const CameraDescription testCameraDescription = CameraDescription( @@ -163,11 +178,24 @@ void main() { const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; const bool enableAudio = true; + camera.processCameraProvider = mockProcessCameraProvider; + + when(mockProcessCameraProvider.bindToLifecycle( + camera.mockBackCameraSelector, + [camera.testPreview, camera.testImageCapture])) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + await camera.createCamera(testCameraDescription, testResolutionPreset, enableAudio: enableAudio); + // Verify expected UseCases were bound. verify(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!, [camera.testPreview, camera.testImageCapture])); + + // Verify the camera's CameraInfo instance got updated. + expect(camera.cameraInfo, equals(mockCameraInfo)); }); test( @@ -179,7 +207,8 @@ void main() { test('initializeCamera sends expected CameraInitializedEvent', () async { final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); - camera.processCameraProvider = MockProcessCameraProvider(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); const int cameraId = 10; const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; @@ -209,18 +238,22 @@ void main() { FocusMode.auto, false); + camera.processCameraProvider = mockProcessCameraProvider; + // Call createCamera. when(camera.testPreview.setSurfaceProvider()) .thenAnswer((_) async => cameraId); - await camera.createCamera(testCameraDescription, testResolutionPreset, - enableAudio: enableAudio); - when(camera.processCameraProvider!.bindToLifecycle(camera.cameraSelector!, - [camera.testPreview, camera.testImageCapture])) + when(mockProcessCameraProvider.bindToLifecycle(any, any)) .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(MockCameraInfo())); when(camera.testPreview.getResolutionInfo()) .thenAnswer((_) async => testResolutionInfo); + await camera.createCamera(testCameraDescription, testResolutionPreset, + enableAudio: enableAudio); + // Start listening to camera events stream to verify the proper CameraInitializedEvent is sent. camera.cameraEventStreamController.stream.listen((CameraEvent event) { expect(event, const TypeMatcher()); @@ -327,44 +360,72 @@ void main() { test('resumePreview does not bind preview to lifecycle if already bound', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); - camera.processCameraProvider = MockProcessCameraProvider(); + camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); when(camera.processCameraProvider!.isBound(camera.preview!)) .thenAnswer((_) async => true); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + await camera.resumePreview(78); verifyNever(camera.processCameraProvider! .bindToLifecycle(camera.cameraSelector!, [camera.preview!])); + expect(camera.cameraInfo, isNot(mockCameraInfo)); }); test('resumePreview binds preview to lifecycle if not already bound', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); - camera.processCameraProvider = MockProcessCameraProvider(); + camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + await camera.resumePreview(78); verify(camera.processCameraProvider! .bindToLifecycle(camera.cameraSelector!, [camera.preview!])); + expect(camera.cameraInfo, equals(mockCameraInfo)); }); test( 'buildPreview returns a FutureBuilder that does not return a Texture until the preview is bound to the lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); const int textureId = 75; - camera.processCameraProvider = MockProcessCameraProvider(); + camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(MockCameraInfo())); + final FutureBuilder previewWidget = camera.buildPreview(textureId) as FutureBuilder; @@ -386,12 +447,21 @@ void main() { 'buildPreview returns a FutureBuilder that returns a Texture once the preview is bound to the lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); const int textureId = 75; - camera.processCameraProvider = MockProcessCameraProvider(); + camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + final FutureBuilder previewWidget = camera.buildPreview(textureId) as FutureBuilder; @@ -419,15 +489,103 @@ void main() { expect(imageFile.path, equals(testPicturePath)); }); + test('getMinExposureOffset returns expected exposure offset', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.2); + + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + + // We expect the minimum exposure to be the minimum exposure compensation * exposure compensation step. + // Delta is included due to avoid catching rounding errors. + expect(await camera.getMinExposureOffset(35), closeTo(0.6, 0.0000000001)); + }); + + test('getMaxExposureOffset returns expected exposure offset', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.2); + + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + + // We expect the maximum exposure to be the maximum exposure compensation * exposure compensation step. + expect(await camera.getMaxExposureOffset(35), 0.8); + }); + + test('getExposureOffsetStepSize returns expected exposure offset', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(minCompensation: 3, maxCompensation: 4), + exposureCompensationStep: 0.2); + + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getExposureState()) + .thenAnswer((_) async => exposureState); + + expect(await camera.getExposureOffsetStepSize(55), 0.2); + }); + + test('getMaxZoomLevel returns expected exposure offset', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + const double maxZoomRatio = 1; + final ZoomState zoomState = + ZoomState.detached(maxZoomRatio: maxZoomRatio, minZoomRatio: 0); + + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getZoomState()).thenAnswer((_) async => zoomState); + + expect(await camera.getMaxZoomLevel(55), maxZoomRatio); + }); + + test('getMinZoomLevel returns expected exposure offset', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + const double minZoomRatio = 0; + final ZoomState zoomState = + ZoomState.detached(maxZoomRatio: 1, minZoomRatio: minZoomRatio); + + camera.cameraInfo = mockCameraInfo; + + when(mockCameraInfo.getZoomState()).thenAnswer((_) async => zoomState); + + expect(await camera.getMinZoomLevel(55), minZoomRatio); + }); + test( 'onStreamedFrameAvailable emits CameraImageData when picked up from CameraImageData stream controller', () async { final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); const int cameraId = 22; - camera.processCameraProvider = MockProcessCameraProvider(); + + camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = MockCameraSelector(); camera.createDetachedCallbacks = true; + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(mockCamera)); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(MockCameraInfo())); + final CameraImageData mockCameraImageData = MockCameraImageData(); final Stream imageStream = camera.onStreamedFrameAvailable(cameraId); @@ -449,6 +607,7 @@ void main() { MockProcessCameraProvider(); final CameraSelector mockCameraSelector = MockCameraSelector(); final Camera mockCamera = MockCamera(); + final CameraInfo mockCameraInfo = MockCameraInfo(); final MockImageProxy mockImageProxy = MockImageProxy(); final MockPlaneProxy mockPlane = MockPlaneProxy(); final List mockPlanes = [mockPlane]; @@ -466,6 +625,7 @@ void main() { when(mockProcessCameraProvider.bindToLifecycle( mockCameraSelector, [camera.mockImageAnalysis])) .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockImageProxy.getPlanes()) .thenAnswer((_) => Future>.value(mockPlanes)); when(mockPlane.buffer).thenReturn(buffer); @@ -475,18 +635,13 @@ void main() { when(mockImageProxy.height).thenReturn(imageHeight); when(mockImageProxy.width).thenReturn(imageWidth); + final Completer imageDataCompleter = + Completer(); final StreamSubscription onStreamedFrameAvailableSubscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) { - // Test Analyzer correctly process ImageProxy instances. - expect(imageData.planes.length, equals(0)); - expect(imageData.planes[0].bytes, equals(buffer)); - expect(imageData.planes[0].bytesPerRow, equals(rowStride)); - expect(imageData.planes[0].bytesPerPixel, equals(pixelStride)); - expect(imageData.format.raw, equals(imageFormat)); - expect(imageData.height, equals(imageHeight)); - expect(imageData.width, equals(imageWidth)); + imageDataCompleter.complete(imageData); }); // Test ImageAnalysis use case is bound to ProcessCameraProvider. @@ -497,6 +652,20 @@ void main() { mockCameraSelector, [camera.mockImageAnalysis])); await capturedAnalyzer.analyze(mockImageProxy); + final CameraImageData imageData = await imageDataCompleter.future; + + // Test Analyzer correctly process ImageProxy instances. + expect(imageData.planes.length, equals(1)); + expect(imageData.planes[0].bytes, equals(buffer)); + expect(imageData.planes[0].bytesPerRow, equals(rowStride)); + expect(imageData.planes[0].bytesPerPixel, equals(pixelStride)); + expect(imageData.format.raw, equals(imageFormat)); + expect(imageData.height, equals(imageHeight)); + expect(imageData.width, equals(imageWidth)); + + // Verify camera and cameraInfo were properly updated. + expect(camera.camera, equals(mockCamera)); + expect(camera.cameraInfo, equals(mockCameraInfo)); onStreamedFrameAvailableSubscription.cancel(); }); @@ -517,6 +686,7 @@ void main() { when(mockProcessCameraProvider.bindToLifecycle( mockCameraSelector, [camera.mockImageAnalysis])) .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => MockCameraInfo()); final StreamSubscription imageStreamSubscription = camera .onStreamedFrameAvailable(cameraId) diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 56673fc45926..b7365a2e90d4 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -3,30 +3,32 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i9; -import 'dart:typed_data' as _i16; - -import 'package:camera_android_camerax/src/analyzer.dart' as _i12; -import 'package:camera_android_camerax/src/camera.dart' as _i4; -import 'package:camera_android_camerax/src/camera_info.dart' as _i8; -import 'package:camera_android_camerax/src/camera_selector.dart' as _i10; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i3; -import 'package:camera_android_camerax/src/image_analysis.dart' as _i11; -import 'package:camera_android_camerax/src/image_capture.dart' as _i13; -import 'package:camera_android_camerax/src/image_proxy.dart' as _i14; -import 'package:camera_android_camerax/src/plane_proxy.dart' as _i15; -import 'package:camera_android_camerax/src/preview.dart' as _i17; +import 'dart:async' as _i11; +import 'dart:typed_data' as _i18; + +import 'package:camera_android_camerax/src/analyzer.dart' as _i14; +import 'package:camera_android_camerax/src/camera.dart' as _i7; +import 'package:camera_android_camerax/src/camera_info.dart' as _i2; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i12; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i6; +import 'package:camera_android_camerax/src/exposure_state.dart' as _i3; +import 'package:camera_android_camerax/src/image_analysis.dart' as _i13; +import 'package:camera_android_camerax/src/image_capture.dart' as _i15; +import 'package:camera_android_camerax/src/image_proxy.dart' as _i16; +import 'package:camera_android_camerax/src/plane_proxy.dart' as _i17; +import 'package:camera_android_camerax/src/preview.dart' as _i19; import 'package:camera_android_camerax/src/process_camera_provider.dart' - as _i18; -import 'package:camera_android_camerax/src/use_case.dart' as _i19; + as _i20; +import 'package:camera_android_camerax/src/use_case.dart' as _i21; +import 'package:camera_android_camerax/src/zoom_state.dart' as _i4; import 'package:camera_platform_interface/camera_platform_interface.dart' - as _i2; -import 'package:flutter/foundation.dart' as _i7; -import 'package:flutter/services.dart' as _i6; -import 'package:flutter/widgets.dart' as _i5; + as _i5; +import 'package:flutter/foundation.dart' as _i10; +import 'package:flutter/services.dart' as _i9; +import 'package:flutter/widgets.dart' as _i8; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i20; +import 'test_camerax_library.g.dart' as _i22; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -39,9 +41,8 @@ import 'test_camerax_library.g.dart' as _i20; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeCameraImageFormat_0 extends _i1.SmartFake - implements _i2.CameraImageFormat { - _FakeCameraImageFormat_0( +class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo { + _FakeCameraInfo_0( Object parent, Invocation parentInvocation, ) : super( @@ -50,9 +51,8 @@ class _FakeCameraImageFormat_0 extends _i1.SmartFake ); } -class _FakeResolutionInfo_1 extends _i1.SmartFake - implements _i3.ResolutionInfo { - _FakeResolutionInfo_1( +class _FakeExposureState_1 extends _i1.SmartFake implements _i3.ExposureState { + _FakeExposureState_1( Object parent, Invocation parentInvocation, ) : super( @@ -61,8 +61,8 @@ class _FakeResolutionInfo_1 extends _i1.SmartFake ); } -class _FakeCamera_2 extends _i1.SmartFake implements _i4.Camera { - _FakeCamera_2( +class _FakeZoomState_2 extends _i1.SmartFake implements _i4.ZoomState { + _FakeZoomState_2( Object parent, Invocation parentInvocation, ) : super( @@ -71,8 +71,51 @@ class _FakeCamera_2 extends _i1.SmartFake implements _i4.Camera { ); } -class _FakeWidget_3 extends _i1.SmartFake implements _i5.Widget { - _FakeWidget_3( +class _FakeCameraImageFormat_3 extends _i1.SmartFake + implements _i5.CameraImageFormat { + _FakeCameraImageFormat_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeExposureCompensationRange_4 extends _i1.SmartFake + implements _i6.ExposureCompensationRange { + _FakeExposureCompensationRange_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResolutionInfo_5 extends _i1.SmartFake + implements _i6.ResolutionInfo { + _FakeResolutionInfo_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCamera_6 extends _i1.SmartFake implements _i7.Camera { + _FakeCamera_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWidget_7 extends _i1.SmartFake implements _i8.Widget { + _FakeWidget_7( Object parent, Invocation parentInvocation, ) : super( @@ -81,13 +124,13 @@ class _FakeWidget_3 extends _i1.SmartFake implements _i5.Widget { ); @override - String toString({_i6.DiagnosticLevel? minLevel = _i6.DiagnosticLevel.info}) => + String toString({_i9.DiagnosticLevel? minLevel = _i9.DiagnosticLevel.info}) => super.toString(); } -class _FakeInheritedWidget_4 extends _i1.SmartFake - implements _i5.InheritedWidget { - _FakeInheritedWidget_4( +class _FakeInheritedWidget_8 extends _i1.SmartFake + implements _i8.InheritedWidget { + _FakeInheritedWidget_8( Object parent, Invocation parentInvocation, ) : super( @@ -96,13 +139,13 @@ class _FakeInheritedWidget_4 extends _i1.SmartFake ); @override - String toString({_i6.DiagnosticLevel? minLevel = _i6.DiagnosticLevel.info}) => + String toString({_i9.DiagnosticLevel? minLevel = _i9.DiagnosticLevel.info}) => super.toString(); } -class _FakeDiagnosticsNode_5 extends _i1.SmartFake - implements _i7.DiagnosticsNode { - _FakeDiagnosticsNode_5( +class _FakeDiagnosticsNode_9 extends _i1.SmartFake + implements _i10.DiagnosticsNode { + _FakeDiagnosticsNode_9( Object parent, Invocation parentInvocation, ) : super( @@ -112,8 +155,8 @@ class _FakeDiagnosticsNode_5 extends _i1.SmartFake @override String toString({ - _i7.TextTreeConfiguration? parentConfiguration, - _i6.DiagnosticLevel? minLevel = _i6.DiagnosticLevel.info, + _i10.TextTreeConfiguration? parentConfiguration, + _i9.DiagnosticLevel? minLevel = _i9.DiagnosticLevel.info, }) => super.toString(); } @@ -121,40 +164,107 @@ class _FakeDiagnosticsNode_5 extends _i1.SmartFake /// A class which mocks [Camera]. /// /// See the documentation for Mockito's code generation for more information. -class MockCamera extends _i1.Mock implements _i4.Camera {} +class MockCamera extends _i1.Mock implements _i7.Camera { + @override + _i11.Future<_i2.CameraInfo> getCameraInfo() => (super.noSuchMethod( + Invocation.method( + #getCameraInfo, + [], + ), + returnValue: _i11.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + this, + Invocation.method( + #getCameraInfo, + [], + ), + )), + returnValueForMissingStub: + _i11.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + this, + Invocation.method( + #getCameraInfo, + [], + ), + )), + ) as _i11.Future<_i2.CameraInfo>); +} /// A class which mocks [CameraInfo]. /// /// See the documentation for Mockito's code generation for more information. -class MockCameraInfo extends _i1.Mock implements _i8.CameraInfo { +class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { @override - _i9.Future getSensorRotationDegrees() => (super.noSuchMethod( + _i11.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( #getSensorRotationDegrees, [], ), - returnValue: _i9.Future.value(0), - returnValueForMissingStub: _i9.Future.value(0), - ) as _i9.Future); + returnValue: _i11.Future.value(0), + returnValueForMissingStub: _i11.Future.value(0), + ) as _i11.Future); + @override + _i11.Future<_i3.ExposureState> getExposureState() => (super.noSuchMethod( + Invocation.method( + #getExposureState, + [], + ), + returnValue: _i11.Future<_i3.ExposureState>.value(_FakeExposureState_1( + this, + Invocation.method( + #getExposureState, + [], + ), + )), + returnValueForMissingStub: + _i11.Future<_i3.ExposureState>.value(_FakeExposureState_1( + this, + Invocation.method( + #getExposureState, + [], + ), + )), + ) as _i11.Future<_i3.ExposureState>); + @override + _i11.Future<_i4.ZoomState> getZoomState() => (super.noSuchMethod( + Invocation.method( + #getZoomState, + [], + ), + returnValue: _i11.Future<_i4.ZoomState>.value(_FakeZoomState_2( + this, + Invocation.method( + #getZoomState, + [], + ), + )), + returnValueForMissingStub: + _i11.Future<_i4.ZoomState>.value(_FakeZoomState_2( + this, + Invocation.method( + #getZoomState, + [], + ), + )), + ) as _i11.Future<_i4.ZoomState>); } /// A class which mocks [CameraImageData]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraImageData extends _i1.Mock implements _i2.CameraImageData { +class MockCameraImageData extends _i1.Mock implements _i5.CameraImageData { @override - _i2.CameraImageFormat get format => (super.noSuchMethod( + _i5.CameraImageFormat get format => (super.noSuchMethod( Invocation.getter(#format), - returnValue: _FakeCameraImageFormat_0( + returnValue: _FakeCameraImageFormat_3( this, Invocation.getter(#format), ), - returnValueForMissingStub: _FakeCameraImageFormat_0( + returnValueForMissingStub: _FakeCameraImageFormat_3( this, Invocation.getter(#format), ), - ) as _i2.CameraImageFormat); + ) as _i5.CameraImageFormat); @override int get height => (super.noSuchMethod( Invocation.getter(#height), @@ -168,82 +278,108 @@ class MockCameraImageData extends _i1.Mock implements _i2.CameraImageData { returnValueForMissingStub: 0, ) as int); @override - List<_i2.CameraImagePlane> get planes => (super.noSuchMethod( + List<_i5.CameraImagePlane> get planes => (super.noSuchMethod( Invocation.getter(#planes), - returnValue: <_i2.CameraImagePlane>[], - returnValueForMissingStub: <_i2.CameraImagePlane>[], - ) as List<_i2.CameraImagePlane>); + returnValue: <_i5.CameraImagePlane>[], + returnValueForMissingStub: <_i5.CameraImagePlane>[], + ) as List<_i5.CameraImagePlane>); } /// A class which mocks [CameraSelector]. /// /// See the documentation for Mockito's code generation for more information. -class MockCameraSelector extends _i1.Mock implements _i10.CameraSelector { +class MockCameraSelector extends _i1.Mock implements _i12.CameraSelector { @override - _i9.Future> filter(List<_i8.CameraInfo>? cameraInfos) => + _i11.Future> filter(List<_i2.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), - returnValue: _i9.Future>.value(<_i8.CameraInfo>[]), + returnValue: + _i11.Future>.value(<_i2.CameraInfo>[]), returnValueForMissingStub: - _i9.Future>.value(<_i8.CameraInfo>[]), - ) as _i9.Future>); + _i11.Future>.value(<_i2.CameraInfo>[]), + ) as _i11.Future>); +} + +/// A class which mocks [ExposureState]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockExposureState extends _i1.Mock implements _i3.ExposureState { + @override + _i6.ExposureCompensationRange get exposureCompensationRange => + (super.noSuchMethod( + Invocation.getter(#exposureCompensationRange), + returnValue: _FakeExposureCompensationRange_4( + this, + Invocation.getter(#exposureCompensationRange), + ), + returnValueForMissingStub: _FakeExposureCompensationRange_4( + this, + Invocation.getter(#exposureCompensationRange), + ), + ) as _i6.ExposureCompensationRange); + @override + double get exposureCompensationStep => (super.noSuchMethod( + Invocation.getter(#exposureCompensationStep), + returnValue: 0.0, + returnValueForMissingStub: 0.0, + ) as double); } /// A class which mocks [ImageAnalysis]. /// /// See the documentation for Mockito's code generation for more information. -class MockImageAnalysis extends _i1.Mock implements _i11.ImageAnalysis { +class MockImageAnalysis extends _i1.Mock implements _i13.ImageAnalysis { @override - _i9.Future setAnalyzer(_i12.Analyzer? analyzer) => (super.noSuchMethod( + _i11.Future setAnalyzer(_i14.Analyzer? analyzer) => (super.noSuchMethod( Invocation.method( #setAnalyzer, [analyzer], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i9.Future clearAnalyzer() => (super.noSuchMethod( + _i11.Future clearAnalyzer() => (super.noSuchMethod( Invocation.method( #clearAnalyzer, [], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); } /// A class which mocks [ImageCapture]. /// /// See the documentation for Mockito's code generation for more information. -class MockImageCapture extends _i1.Mock implements _i13.ImageCapture { +class MockImageCapture extends _i1.Mock implements _i15.ImageCapture { @override - _i9.Future setFlashMode(int? newFlashMode) => (super.noSuchMethod( + _i11.Future setFlashMode(int? newFlashMode) => (super.noSuchMethod( Invocation.method( #setFlashMode, [newFlashMode], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i9.Future takePicture() => (super.noSuchMethod( + _i11.Future takePicture() => (super.noSuchMethod( Invocation.method( #takePicture, [], ), - returnValue: _i9.Future.value(''), - returnValueForMissingStub: _i9.Future.value(''), - ) as _i9.Future); + returnValue: _i11.Future.value(''), + returnValueForMissingStub: _i11.Future.value(''), + ) as _i11.Future); } /// A class which mocks [ImageProxy]. /// /// See the documentation for Mockito's code generation for more information. -class MockImageProxy extends _i1.Mock implements _i14.ImageProxy { +class MockImageProxy extends _i1.Mock implements _i16.ImageProxy { @override int get format => (super.noSuchMethod( Invocation.getter(#format), @@ -263,37 +399,37 @@ class MockImageProxy extends _i1.Mock implements _i14.ImageProxy { returnValueForMissingStub: 0, ) as int); @override - _i9.Future> getPlanes() => (super.noSuchMethod( + _i11.Future> getPlanes() => (super.noSuchMethod( Invocation.method( #getPlanes, [], ), returnValue: - _i9.Future>.value(<_i15.PlaneProxy>[]), + _i11.Future>.value(<_i17.PlaneProxy>[]), returnValueForMissingStub: - _i9.Future>.value(<_i15.PlaneProxy>[]), - ) as _i9.Future>); + _i11.Future>.value(<_i17.PlaneProxy>[]), + ) as _i11.Future>); @override - _i9.Future close() => (super.noSuchMethod( + _i11.Future close() => (super.noSuchMethod( Invocation.method( #close, [], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); } /// A class which mocks [PlaneProxy]. /// /// See the documentation for Mockito's code generation for more information. -class MockPlaneProxy extends _i1.Mock implements _i15.PlaneProxy { +class MockPlaneProxy extends _i1.Mock implements _i17.PlaneProxy { @override - _i16.Uint8List get buffer => (super.noSuchMethod( + _i18.Uint8List get buffer => (super.noSuchMethod( Invocation.getter(#buffer), - returnValue: _i16.Uint8List(0), - returnValueForMissingStub: _i16.Uint8List(0), - ) as _i16.Uint8List); + returnValue: _i18.Uint8List(0), + returnValueForMissingStub: _i18.Uint8List(0), + ) as _i18.Uint8List); @override int get pixelStride => (super.noSuchMethod( Invocation.getter(#pixelStride), @@ -311,16 +447,16 @@ class MockPlaneProxy extends _i1.Mock implements _i15.PlaneProxy { /// A class which mocks [Preview]. /// /// See the documentation for Mockito's code generation for more information. -class MockPreview extends _i1.Mock implements _i17.Preview { +class MockPreview extends _i1.Mock implements _i19.Preview { @override - _i9.Future setSurfaceProvider() => (super.noSuchMethod( + _i11.Future setSurfaceProvider() => (super.noSuchMethod( Invocation.method( #setSurfaceProvider, [], ), - returnValue: _i9.Future.value(0), - returnValueForMissingStub: _i9.Future.value(0), - ) as _i9.Future); + returnValue: _i11.Future.value(0), + returnValueForMissingStub: _i11.Future.value(0), + ) as _i11.Future); @override void releaseFlutterSurfaceTexture() => super.noSuchMethod( Invocation.method( @@ -330,12 +466,13 @@ class MockPreview extends _i1.Mock implements _i17.Preview { returnValueForMissingStub: null, ); @override - _i9.Future<_i3.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( + _i11.Future<_i6.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [], ), - returnValue: _i9.Future<_i3.ResolutionInfo>.value(_FakeResolutionInfo_1( + returnValue: + _i11.Future<_i6.ResolutionInfo>.value(_FakeResolutionInfo_5( this, Invocation.method( #getResolutionInfo, @@ -343,36 +480,37 @@ class MockPreview extends _i1.Mock implements _i17.Preview { ), )), returnValueForMissingStub: - _i9.Future<_i3.ResolutionInfo>.value(_FakeResolutionInfo_1( + _i11.Future<_i6.ResolutionInfo>.value(_FakeResolutionInfo_5( this, Invocation.method( #getResolutionInfo, [], ), )), - ) as _i9.Future<_i3.ResolutionInfo>); + ) as _i11.Future<_i6.ResolutionInfo>); } /// A class which mocks [ProcessCameraProvider]. /// /// See the documentation for Mockito's code generation for more information. class MockProcessCameraProvider extends _i1.Mock - implements _i18.ProcessCameraProvider { + implements _i20.ProcessCameraProvider { @override - _i9.Future> getAvailableCameraInfos() => + _i11.Future> getAvailableCameraInfos() => (super.noSuchMethod( Invocation.method( #getAvailableCameraInfos, [], ), - returnValue: _i9.Future>.value(<_i8.CameraInfo>[]), + returnValue: + _i11.Future>.value(<_i2.CameraInfo>[]), returnValueForMissingStub: - _i9.Future>.value(<_i8.CameraInfo>[]), - ) as _i9.Future>); + _i11.Future>.value(<_i2.CameraInfo>[]), + ) as _i11.Future>); @override - _i9.Future<_i4.Camera> bindToLifecycle( - _i10.CameraSelector? cameraSelector, - List<_i19.UseCase>? useCases, + _i11.Future<_i7.Camera> bindToLifecycle( + _i12.CameraSelector? cameraSelector, + List<_i21.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( @@ -382,7 +520,7 @@ class MockProcessCameraProvider extends _i1.Mock useCases, ], ), - returnValue: _i9.Future<_i4.Camera>.value(_FakeCamera_2( + returnValue: _i11.Future<_i7.Camera>.value(_FakeCamera_6( this, Invocation.method( #bindToLifecycle, @@ -392,7 +530,7 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - returnValueForMissingStub: _i9.Future<_i4.Camera>.value(_FakeCamera_2( + returnValueForMissingStub: _i11.Future<_i7.Camera>.value(_FakeCamera_6( this, Invocation.method( #bindToLifecycle, @@ -402,18 +540,18 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - ) as _i9.Future<_i4.Camera>); + ) as _i11.Future<_i7.Camera>); @override - _i9.Future isBound(_i19.UseCase? useCase) => (super.noSuchMethod( + _i11.Future isBound(_i21.UseCase? useCase) => (super.noSuchMethod( Invocation.method( #isBound, [useCase], ), - returnValue: _i9.Future.value(false), - returnValueForMissingStub: _i9.Future.value(false), - ) as _i9.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - void unbind(List<_i19.UseCase>? useCases) => super.noSuchMethod( + void unbind(List<_i21.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], @@ -434,7 +572,7 @@ class MockProcessCameraProvider extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i20.TestInstanceManagerHostApi { + implements _i22.TestInstanceManagerHostApi { @override void clear() => super.noSuchMethod( Invocation.method( @@ -445,22 +583,40 @@ class MockTestInstanceManagerHostApi extends _i1.Mock ); } +/// A class which mocks [ZoomState]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockZoomState extends _i1.Mock implements _i4.ZoomState { + @override + double get minZoomRatio => (super.noSuchMethod( + Invocation.getter(#minZoomRatio), + returnValue: 0.0, + returnValueForMissingStub: 0.0, + ) as double); + @override + double get maxZoomRatio => (super.noSuchMethod( + Invocation.getter(#maxZoomRatio), + returnValue: 0.0, + returnValueForMissingStub: 0.0, + ) as double); +} + /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. -class MockBuildContext extends _i1.Mock implements _i5.BuildContext { +class MockBuildContext extends _i1.Mock implements _i8.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override - _i5.Widget get widget => (super.noSuchMethod( + _i8.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), - returnValue: _FakeWidget_3( + returnValue: _FakeWidget_7( this, Invocation.getter(#widget), ), - ) as _i5.Widget); + ) as _i8.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), @@ -472,8 +628,8 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { returnValue: false, ) as bool); @override - _i5.InheritedWidget dependOnInheritedElement( - _i5.InheritedElement? ancestor, { + _i8.InheritedWidget dependOnInheritedElement( + _i8.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( @@ -482,7 +638,7 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { [ancestor], {#aspect: aspect}, ), - returnValue: _FakeInheritedWidget_4( + returnValue: _FakeInheritedWidget_8( this, Invocation.method( #dependOnInheritedElement, @@ -490,9 +646,9 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { {#aspect: aspect}, ), ), - ) as _i5.InheritedWidget); + ) as _i8.InheritedWidget); @override - void visitAncestorElements(bool Function(_i5.Element)? visitor) => + void visitAncestorElements(bool Function(_i8.Element)? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, @@ -501,7 +657,7 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { returnValueForMissingStub: null, ); @override - void visitChildElements(_i5.ElementVisitor? visitor) => super.noSuchMethod( + void visitChildElements(_i8.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], @@ -509,7 +665,7 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { returnValueForMissingStub: null, ); @override - void dispatchNotification(_i5.Notification? notification) => + void dispatchNotification(_i8.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, @@ -518,9 +674,9 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { returnValueForMissingStub: null, ); @override - _i7.DiagnosticsNode describeElement( + _i10.DiagnosticsNode describeElement( String? name, { - _i7.DiagnosticsTreeStyle? style = _i7.DiagnosticsTreeStyle.errorProperty, + _i10.DiagnosticsTreeStyle? style = _i10.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( @@ -528,7 +684,7 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { [name], {#style: style}, ), - returnValue: _FakeDiagnosticsNode_5( + returnValue: _FakeDiagnosticsNode_9( this, Invocation.method( #describeElement, @@ -536,11 +692,11 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { {#style: style}, ), ), - ) as _i7.DiagnosticsNode); + ) as _i10.DiagnosticsNode); @override - _i7.DiagnosticsNode describeWidget( + _i10.DiagnosticsNode describeWidget( String? name, { - _i7.DiagnosticsTreeStyle? style = _i7.DiagnosticsTreeStyle.errorProperty, + _i10.DiagnosticsTreeStyle? style = _i10.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( @@ -548,7 +704,7 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { [name], {#style: style}, ), - returnValue: _FakeDiagnosticsNode_5( + returnValue: _FakeDiagnosticsNode_9( this, Invocation.method( #describeWidget, @@ -556,9 +712,9 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { {#style: style}, ), ), - ) as _i7.DiagnosticsNode); + ) as _i10.DiagnosticsNode); @override - List<_i7.DiagnosticsNode> describeMissingAncestor( + List<_i10.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( @@ -566,21 +722,21 @@ class MockBuildContext extends _i1.Mock implements _i5.BuildContext { [], {#expectedAncestorType: expectedAncestorType}, ), - returnValue: <_i7.DiagnosticsNode>[], - ) as List<_i7.DiagnosticsNode>); + returnValue: <_i10.DiagnosticsNode>[], + ) as List<_i10.DiagnosticsNode>); @override - _i7.DiagnosticsNode describeOwnershipChain(String? name) => + _i10.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), - returnValue: _FakeDiagnosticsNode_5( + returnValue: _FakeDiagnosticsNode_9( this, Invocation.method( #describeOwnershipChain, [name], ), ), - ) as _i7.DiagnosticsNode); + ) as _i10.DiagnosticsNode); } diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera_info_test.dart index f0527bd1f944..9826b88e367e 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.dart @@ -4,7 +4,9 @@ import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/exposure_state.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/zoom_state.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -22,7 +24,9 @@ void main() { group('CameraInfo', () { tearDown(() => TestCameraInfoHostApi.setup(null)); - test('getSensorRotationDegreesTest', () async { + test( + 'getSensorRotationDegrees makes call to retrieve expected sensor rotation', + () async { final MockTestCameraInfoHostApi mockApi = MockTestCameraInfoHostApi(); TestCameraInfoHostApi.setup(mockApi); @@ -46,7 +50,80 @@ void main() { verify(mockApi.getSensorRotationDegrees(0)); }); - test('flutterApiCreateTest', () { + test('getExposureState makes call to retrieve expected ExposureState', + () async { + final MockTestCameraInfoHostApi mockApi = MockTestCameraInfoHostApi(); + TestCameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraInfo cameraInfo = CameraInfo.detached( + instanceManager: instanceManager, + ); + const int cameraInfoIdentifier = 4; + final ExposureState exposureState = ExposureState.detached( + exposureCompensationRange: + ExposureCompensationRange(maxCompensation: 0, minCompensation: 1), + exposureCompensationStep: 4, + instanceManager: instanceManager, + ); + const int exposureStateIdentifier = 45; + + instanceManager.addHostCreatedInstance( + cameraInfo, + cameraInfoIdentifier, + onCopy: (_) => CameraInfo.detached(), + ); + instanceManager.addHostCreatedInstance( + exposureState, + exposureStateIdentifier, + onCopy: (_) => ExposureState.detached( + exposureCompensationRange: ExposureCompensationRange( + maxCompensation: 0, minCompensation: 1), + exposureCompensationStep: 4), + ); + + when(mockApi.getExposureState(cameraInfoIdentifier)) + .thenReturn(exposureStateIdentifier); + expect(await cameraInfo.getExposureState(), equals(exposureState)); + + verify(mockApi.getExposureState(cameraInfoIdentifier)); + }); + + test('getZoomState makes call to retrieve expected ZoomState', () async { + final MockTestCameraInfoHostApi mockApi = MockTestCameraInfoHostApi(); + TestCameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CameraInfo cameraInfo = CameraInfo.detached( + instanceManager: instanceManager, + ); + const int cameraInfoIdentifier = 2; + final ZoomState zoomState = + ZoomState.detached(minZoomRatio: 0, maxZoomRatio: 1); + const int zoomStateIdentifier = 55; + + instanceManager.addHostCreatedInstance( + cameraInfo, + cameraInfoIdentifier, + onCopy: (_) => CameraInfo.detached(), + ); + instanceManager.addHostCreatedInstance(zoomState, zoomStateIdentifier, + onCopy: (_) => ZoomState.detached(minZoomRatio: 0, maxZoomRatio: 1)); + + when(mockApi.getZoomState(cameraInfoIdentifier)) + .thenReturn(zoomStateIdentifier); + expect(await cameraInfo.getZoomState(), equals(zoomState)); + + verify(mockApi.getZoomState(cameraInfoIdentifier)); + }); + + test('flutterApi create makes call to create expected instance type', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart index 3c153337bfa6..968dc6e8b621 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart @@ -35,6 +35,22 @@ class MockTestCameraInfoHostApi extends _i1.Mock ), returnValue: 0, ) as int); + @override + int getExposureState(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getExposureState, + [identifier], + ), + returnValue: 0, + ) as int); + @override + int getZoomState(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getZoomState, + [identifier], + ), + returnValue: 0, + ) as int); } /// A class which mocks [TestInstanceManagerHostApi]. diff --git a/packages/camera/camera_android_camerax/test/camera_test.dart b/packages/camera/camera_android_camerax/test/camera_test.dart index 5d08dd65c49c..742a60799e6f 100644 --- a/packages/camera/camera_android_camerax/test/camera_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_test.dart @@ -3,14 +3,16 @@ // found in the LICENSE file. import 'package:camera_android_camerax/src/camera.dart'; +import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'camera_test.mocks.dart'; import 'test_camerax_library.g.dart'; -@GenerateMocks([TestInstanceManagerHostApi]) +@GenerateMocks([TestCameraHostApi, TestInstanceManagerHostApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -18,6 +20,38 @@ void main() { TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); group('Camera', () { + test('getCameraInfo makes call to retrieve expected CameraInfo', () async { + final MockTestCameraHostApi mockApi = MockTestCameraHostApi(); + TestCameraHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final Camera camera = Camera.detached( + instanceManager: instanceManager, + ); + const int cameraIdentifier = 24; + final CameraInfo cameraInfo = CameraInfo.detached(); + const int cameraInfoIdentifier = 88; + instanceManager.addHostCreatedInstance( + camera, + cameraIdentifier, + onCopy: (_) => Camera.detached(instanceManager: instanceManager), + ); + instanceManager.addHostCreatedInstance( + cameraInfo, + cameraInfoIdentifier, + onCopy: (_) => CameraInfo.detached(instanceManager: instanceManager), + ); + + when(mockApi.getCameraInfo(cameraIdentifier)) + .thenAnswer((_) => cameraInfoIdentifier); + + expect(await camera.getCameraInfo(), equals(cameraInfo)); + verify(mockApi.getCameraInfo(cameraIdentifier)); + }); + test('flutterApiCreateTest', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, diff --git a/packages/camera/camera_android_camerax/test/camera_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_test.mocks.dart index 915ba5584b4d..7f3af283f1a3 100644 --- a/packages/camera/camera_android_camerax/test/camera_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_test.mocks.dart @@ -18,6 +18,24 @@ import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +/// A class which mocks [TestCameraHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestCameraHostApi extends _i1.Mock implements _i2.TestCameraHostApi { + MockTestCameraHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + int getCameraInfo(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getCameraInfo, + [identifier], + ), + returnValue: 0, + ) as int); +} + /// A class which mocks [TestInstanceManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. diff --git a/packages/camera/camera_android_camerax/test/exposure_state_test.dart b/packages/camera/camera_android_camerax/test/exposure_state_test.dart new file mode 100644 index 000000000000..a5897f10c44c --- /dev/null +++ b/packages/camera/camera_android_camerax/test/exposure_state_test.dart @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/exposure_state.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_camerax_library.g.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ExposureState', () { + tearDown(() => TestCameraInfoHostApi.setup(null)); + + test('flutterApi create makes call to create expected ExposureState', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ExposureStateFlutterApiImpl flutterApi = + ExposureStateFlutterApiImpl( + instanceManager: instanceManager, + ); + const int exposureStateIdentifier = 68; + final ExposureCompensationRange exposureCompensationRange = + ExposureCompensationRange(minCompensation: 5, maxCompensation: 7); + const double exposureCompensationStep = 0.3; + + flutterApi.create(exposureStateIdentifier, exposureCompensationRange, + exposureCompensationStep); + + final ExposureState instance = + instanceManager.getInstanceWithWeakReference(exposureStateIdentifier)! + as ExposureState; + expect(instance.exposureCompensationRange, + equals(exposureCompensationRange)); + expect( + instance.exposureCompensationStep, equals(exposureCompensationStep)); + }); + }); +} 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 0af4cd0029f8..40bd8f2e4335 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 @@ -86,6 +86,10 @@ abstract class TestCameraInfoHostApi { int getSensorRotationDegrees(int identifier); + int getExposureState(int identifier); + + int getZoomState(int identifier); + static void setup(TestCameraInfoHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -111,6 +115,50 @@ abstract class TestCameraInfoHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraInfoHostApi.getExposureState', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CameraInfoHostApi.getExposureState was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CameraInfoHostApi.getExposureState was null, expected non-null int.'); + final int output = api.getExposureState(arg_identifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraInfoHostApi.getZoomState', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CameraInfoHostApi.getZoomState was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CameraInfoHostApi.getZoomState was null, expected non-null int.'); + final int output = api.getZoomState(arg_identifier!); + return [output]; + }); + } + } } } @@ -347,6 +395,40 @@ abstract class TestProcessCameraProviderHostApi { } } +abstract class TestCameraHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + int getCameraInfo(int identifier); + + static void setup(TestCameraHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraHostApi.getCameraInfo', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CameraHostApi.getCameraInfo was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CameraHostApi.getCameraInfo was null, expected non-null int.'); + final int output = api.getCameraInfo(arg_identifier!); + return [output]; + }); + } + } + } +} + class _TestSystemServicesHostApiCodec extends StandardMessageCodec { const _TestSystemServicesHostApiCodec(); @override diff --git a/packages/camera/camera_android_camerax/test/zoom_state_test.dart b/packages/camera/camera_android_camerax/test/zoom_state_test.dart new file mode 100644 index 000000000000..7adc7b23b93b --- /dev/null +++ b/packages/camera/camera_android_camerax/test/zoom_state_test.dart @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/zoom_state.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_camerax_library.g.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ZoomState', () { + tearDown(() => TestCameraInfoHostApi.setup(null)); + + test('flutterApi create makes call to create expected ZoomState', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ZoomStateFlutterApiImpl flutterApi = ZoomStateFlutterApiImpl( + instanceManager: instanceManager, + ); + const int zoomStateIdentifier = 68; + const double minZoomRatio = 0; + const double maxZoomRatio = 1; + + flutterApi.create(zoomStateIdentifier, minZoomRatio, maxZoomRatio); + + final ZoomState instance = instanceManager + .getInstanceWithWeakReference(zoomStateIdentifier)! as ZoomState; + expect(instance.minZoomRatio, equals(minZoomRatio)); + expect(instance.maxZoomRatio, equals(maxZoomRatio)); + }); + }); +}