diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ab60e8aba87a..1cddca1cbbc3 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.5.0+12 + +* Wraps classes needed to implement resolution configuration for image capture, image analysis, and preview. +* Removes usages of deprecated APIs for resolution configuration. +* Bumps CameraX version to 1.3.0-beta01. + ## 0.5.0+11 * Fixes issue with image data not being emitted after relistening to stream returned by `onStreamedFrameAvailable`. diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle index f10f13336081..5530a3bb1043 100644 --- a/packages/camera/camera_android_camerax/android/build.gradle +++ b/packages/camera/camera_android_camerax/android/build.gradle @@ -61,7 +61,7 @@ android { dependencies { // CameraX core library using the camera2 implementation must use same version number. - def camerax_version = "1.3.0-alpha05" + def camerax_version = "1.3.0-beta01" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java new file mode 100644 index 000000000000..91e0def66eaf --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.camera.core.resolutionselector.AspectRatioStrategy; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.AspectRatioStrategyHostApi; + +/** + * Host API implementation for {@link AspectRatioStrategy}. + * + *

This class handles instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class AspectRatioStrategyHostApiImpl implements AspectRatioStrategyHostApi { + private final InstanceManager instanceManager; + private final AspectRatioStrategyProxy proxy; + + /** Proxy for constructors and static method of {@link AspectRatioStrategy}. */ + @VisibleForTesting + public static class AspectRatioStrategyProxy { + /** Creates an instance of {@link AspectRatioStrategy}. */ + @NonNull + public AspectRatioStrategy create( + @NonNull Long preferredAspectRatio, @NonNull Long fallbackRule) { + return new AspectRatioStrategy(preferredAspectRatio.intValue(), fallbackRule.intValue()); + } + } + + /** + * Constructs an {@link AspectRatioStrategyHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public AspectRatioStrategyHostApiImpl(@NonNull InstanceManager instanceManager) { + this(instanceManager, new AspectRatioStrategyProxy()); + } + + /** + * Constructs an {@link AspectRatioStrategyHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructors and static method of {@link AspectRatioStrategy} + */ + @VisibleForTesting + AspectRatioStrategyHostApiImpl( + @NonNull InstanceManager instanceManager, @NonNull AspectRatioStrategyProxy proxy) { + this.instanceManager = instanceManager; + this.proxy = proxy; + } + + /** + * Creates an {@link AspectRatioStrategy} instance with the preferred aspect ratio and fallback + * rule specified. + */ + @Override + public void create( + @NonNull Long identifier, @NonNull Long preferredAspectRatio, @NonNull Long fallbackRule) { + instanceManager.addDartCreatedInstance( + proxy.create(preferredAspectRatio, fallbackRule), identifier); + } +} 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 dd8ab514652c..dbae2a468052 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 @@ -97,6 +97,12 @@ public void setUp( binaryMessenger, pendingRecordingHostApiImpl); videoCaptureHostApiImpl = new VideoCaptureHostApiImpl(binaryMessenger, instanceManager); GeneratedCameraXLibrary.VideoCaptureHostApi.setup(binaryMessenger, videoCaptureHostApiImpl); + GeneratedCameraXLibrary.ResolutionSelectorHostApi.setup( + binaryMessenger, new ResolutionSelectorHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.ResolutionStrategyHostApi.setup( + binaryMessenger, new ResolutionStrategyHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.AspectRatioStrategyHostApi.setup( + binaryMessenger, new AspectRatioStrategyHostApiImpl(instanceManager)); } @Override 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 c6f627993772..680ecb805250 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 @@ -76,6 +76,23 @@ private CameraStateType(final int index) { } } + /** + * The types (T) properly wrapped to be used as a LiveData. + * + *

If you need to add another type to support a type S to use a LiveData in this plugin, + * ensure the following is done on the Dart side: + * + *

* In `../lib/src/live_data.dart`, add new cases for S in + * `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of type S from a + * LiveData instance and in `LiveDataFlutterApiImpl#create` to create the expected type of + * LiveData when requested. + * + *

On the native side, ensure the following is done: + * + *

* Update `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for + * instances of type S. * Update `ObserverFlutterApiWrapper#onChanged` to properly handle + * receiving calls with instances of type S if a LiveData instance is observed. + */ public enum LiveDataSupportedType { CAMERA_STATE(0), ZOOM_STATE(1); @@ -1297,8 +1314,6 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: return ResolutionInfo.fromList((ArrayList) readValue(buffer)); - case (byte) 129: - return ResolutionInfo.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -1309,9 +1324,6 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof ResolutionInfo) { stream.write(128); writeValue(stream, ((ResolutionInfo) value).toList()); - } else if (value instanceof ResolutionInfo) { - stream.write(129); - writeValue(stream, ((ResolutionInfo) value).toList()); } else { super.writeValue(stream, value); } @@ -1322,9 +1334,7 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { public interface PreviewHostApi { void create( - @NonNull Long identifier, - @Nullable Long rotation, - @Nullable ResolutionInfo targetResolution); + @NonNull Long identifier, @Nullable Long rotation, @Nullable Long resolutionSelectorId); @NonNull Long setSurfaceProvider(@NonNull Long identifier); @@ -1351,12 +1361,14 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable PreviewHos ArrayList args = (ArrayList) message; Number identifierArg = (Number) args.get(0); Number rotationArg = (Number) args.get(1); - ResolutionInfo targetResolutionArg = (ResolutionInfo) args.get(2); + Number resolutionSelectorIdArg = (Number) args.get(2); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), (rotationArg == null) ? null : rotationArg.longValue(), - targetResolutionArg); + (resolutionSelectorIdArg == null) + ? null + : resolutionSelectorIdArg.longValue()); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -1911,40 +1923,11 @@ public void create(@NonNull Long identifierArg, @NonNull Reply callback) { channelReply -> callback.reply(null)); } } - - private static class ImageCaptureHostApiCodec extends StandardMessageCodec { - public static final ImageCaptureHostApiCodec INSTANCE = new ImageCaptureHostApiCodec(); - - private ImageCaptureHostApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return ResolutionInfo.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof ResolutionInfo) { - stream.write(128); - writeValue(stream, ((ResolutionInfo) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface ImageCaptureHostApi { void create( - @NonNull Long identifier, - @Nullable Long flashMode, - @Nullable ResolutionInfo targetResolution); + @NonNull Long identifier, @Nullable Long flashMode, @Nullable Long resolutionSelectorId); void setFlashMode(@NonNull Long identifier, @NonNull Long flashMode); @@ -1952,7 +1935,7 @@ void create( /** The codec used by ImageCaptureHostApi. */ static @NonNull MessageCodec getCodec() { - return ImageCaptureHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } /** * Sets up an instance of `ImageCaptureHostApi` to handle messages through the @@ -1970,12 +1953,14 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ImageCaptu ArrayList args = (ArrayList) message; Number identifierArg = (Number) args.get(0); Number flashModeArg = (Number) args.get(1); - ResolutionInfo targetResolutionArg = (ResolutionInfo) args.get(2); + Number resolutionSelectorIdArg = (Number) args.get(2); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), (flashModeArg == null) ? null : flashModeArg.longValue(), - targetResolutionArg); + (resolutionSelectorIdArg == null) + ? null + : resolutionSelectorIdArg.longValue()); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -2046,6 +2031,182 @@ public void error(Throwable error) { } } + private static class ResolutionStrategyHostApiCodec extends StandardMessageCodec { + public static final ResolutionStrategyHostApiCodec INSTANCE = + new ResolutionStrategyHostApiCodec(); + + private ResolutionStrategyHostApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return ResolutionInfo.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof ResolutionInfo) { + stream.write(128); + writeValue(stream, ((ResolutionInfo) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface ResolutionStrategyHostApi { + + void create( + @NonNull Long identifier, @Nullable ResolutionInfo boundSize, @Nullable Long fallbackRule); + + /** The codec used by ResolutionStrategyHostApi. */ + static @NonNull MessageCodec getCodec() { + return ResolutionStrategyHostApiCodec.INSTANCE; + } + /** + * Sets up an instance of `ResolutionStrategyHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable ResolutionStrategyHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ResolutionStrategyHostApi.create", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + ResolutionInfo boundSizeArg = (ResolutionInfo) args.get(1); + Number fallbackRuleArg = (Number) args.get(2); + try { + api.create( + (identifierArg == null) ? null : identifierArg.longValue(), + boundSizeArg, + (fallbackRuleArg == null) ? null : fallbackRuleArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface ResolutionSelectorHostApi { + + void create( + @NonNull Long identifier, + @Nullable Long resolutionStrategyIdentifier, + @Nullable Long aspectRatioStrategyIdentifier); + + /** The codec used by ResolutionSelectorHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `ResolutionSelectorHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable ResolutionSelectorHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ResolutionSelectorHostApi.create", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + Number resolutionStrategyIdentifierArg = (Number) args.get(1); + Number aspectRatioStrategyIdentifierArg = (Number) args.get(2); + try { + api.create( + (identifierArg == null) ? null : identifierArg.longValue(), + (resolutionStrategyIdentifierArg == null) + ? null + : resolutionStrategyIdentifierArg.longValue(), + (aspectRatioStrategyIdentifierArg == null) + ? null + : aspectRatioStrategyIdentifierArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface AspectRatioStrategyHostApi { + + void create( + @NonNull Long identifier, @NonNull Long preferredAspectRatio, @NonNull Long fallbackRule); + + /** The codec used by AspectRatioStrategyHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `AspectRatioStrategyHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable AspectRatioStrategyHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.AspectRatioStrategyHostApi.create", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + Number preferredAspectRatioArg = (Number) args.get(1); + Number fallbackRuleArg = (Number) args.get(2); + try { + api.create( + (identifierArg == null) ? null : identifierArg.longValue(), + (preferredAspectRatioArg == null) + ? null + : preferredAspectRatioArg.longValue(), + (fallbackRuleArg == null) ? null : fallbackRuleArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + private static class CameraStateFlutterApiCodec extends StandardMessageCodec { public static final CameraStateFlutterApiCodec INSTANCE = new CameraStateFlutterApiCodec(); @@ -2194,37 +2355,10 @@ public void create( channelReply -> callback.reply(null)); } } - - private static class ImageAnalysisHostApiCodec extends StandardMessageCodec { - public static final ImageAnalysisHostApiCodec INSTANCE = new ImageAnalysisHostApiCodec(); - - private ImageAnalysisHostApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return ResolutionInfo.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof ResolutionInfo) { - stream.write(128); - writeValue(stream, ((ResolutionInfo) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface ImageAnalysisHostApi { - void create(@NonNull Long identifier, @Nullable ResolutionInfo targetResolutionIdentifier); + void create(@NonNull Long identifier, @Nullable Long resolutionSelectorId); void setAnalyzer(@NonNull Long identifier, @NonNull Long analyzerIdentifier); @@ -2232,7 +2366,7 @@ public interface ImageAnalysisHostApi { /** The codec used by ImageAnalysisHostApi. */ static @NonNull MessageCodec getCodec() { - return ImageAnalysisHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } /** * Sets up an instance of `ImageAnalysisHostApi` to handle messages through the @@ -2250,11 +2384,13 @@ static void setup( ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; Number identifierArg = (Number) args.get(0); - ResolutionInfo targetResolutionIdentifierArg = (ResolutionInfo) args.get(1); + Number resolutionSelectorIdArg = (Number) args.get(1); try { api.create( (identifierArg == null) ? null : identifierArg.longValue(), - targetResolutionIdentifierArg); + (resolutionSelectorIdArg == null) + ? null + : resolutionSelectorIdArg.longValue()); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java index 5e786176d50c..58491477ae15 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java @@ -9,10 +9,10 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.resolutionselector.ResolutionSelector; import androidx.core.content.ContextCompat; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ImageAnalysisHostApi; -import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; import java.util.Objects; public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi { @@ -38,11 +38,13 @@ public void setContext(@NonNull Context context) { /** Creates an {@link ImageAnalysis} instance with the target resolution if specified. */ @Override - public void create(@NonNull Long identifier, @Nullable ResolutionInfo targetResolution) { + public void create(@NonNull Long identifier, @Nullable Long resolutionSelectorId) { ImageAnalysis.Builder imageAnalysisBuilder = cameraXProxy.createImageAnalysisBuilder(); - if (targetResolution != null) { - imageAnalysisBuilder.setTargetResolution(CameraXProxy.sizeFromResolution(targetResolution)); + if (resolutionSelectorId != null) { + ResolutionSelector resolutionSelector = + Objects.requireNonNull(instanceManager.getInstance(resolutionSelectorId)); + imageAnalysisBuilder.setResolutionSelector(resolutionSelector); } ImageAnalysis imageAnalysis = imageAnalysisBuilder.build(); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java index f2e71aa3eeee..88ec2debb7a8 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java @@ -10,6 +10,7 @@ import androidx.annotation.VisibleForTesting; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; +import androidx.camera.core.resolutionselector.ResolutionSelector; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ImageCaptureHostApi; import java.io.File; @@ -52,17 +53,19 @@ public void setContext(@NonNull Context context) { */ @Override public void create( - @NonNull Long identifier, - @Nullable Long flashMode, - @Nullable GeneratedCameraXLibrary.ResolutionInfo targetResolution) { + @NonNull Long identifier, @Nullable Long flashMode, @Nullable Long resolutionSelectorId) { ImageCapture.Builder imageCaptureBuilder = cameraXProxy.createImageCaptureBuilder(); + if (flashMode != null) { // This sets the requested flash mode, but may fail silently. imageCaptureBuilder.setFlashMode(flashMode.intValue()); } - if (targetResolution != null) { - imageCaptureBuilder.setTargetResolution(CameraXProxy.sizeFromResolution(targetResolution)); + if (resolutionSelectorId != null) { + ResolutionSelector resolutionSelector = + Objects.requireNonNull(instanceManager.getInstance(resolutionSelectorId)); + imageCaptureBuilder.setResolutionSelector(resolutionSelector); } + ImageCapture imageCapture = imageCaptureBuilder.build(); instanceManager.addDartCreatedInstance(imageCapture, identifier); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java index 6f93fd0f87cb..07b581ebf96a 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting; import androidx.camera.core.Preview; import androidx.camera.core.SurfaceRequest; +import androidx.camera.core.resolutionselector.ResolutionSelector; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.PreviewHostApi; import io.flutter.view.TextureRegistry; @@ -38,16 +39,18 @@ public PreviewHostApiImpl( /** Creates a {@link Preview} with the target rotation and resolution if specified. */ @Override public void create( - @NonNull Long identifier, - @Nullable Long rotation, - @Nullable GeneratedCameraXLibrary.ResolutionInfo targetResolution) { + @NonNull Long identifier, @Nullable Long rotation, @Nullable Long resolutionSelectorId) { Preview.Builder previewBuilder = cameraXProxy.createPreviewBuilder(); + if (rotation != null) { previewBuilder.setTargetRotation(rotation.intValue()); } - if (targetResolution != null) { - previewBuilder.setTargetResolution(CameraXProxy.sizeFromResolution(targetResolution)); + if (resolutionSelectorId != null) { + ResolutionSelector resolutionSelector = + Objects.requireNonNull(instanceManager.getInstance(resolutionSelectorId)); + previewBuilder.setResolutionSelector(resolutionSelector); } + Preview preview = previewBuilder.build(); instanceManager.addDartCreatedInstance(preview, identifier); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java new file mode 100644 index 000000000000..be05e1bb8bef --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java @@ -0,0 +1,87 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.camera.core.resolutionselector.AspectRatioStrategy; +import androidx.camera.core.resolutionselector.ResolutionSelector; +import androidx.camera.core.resolutionselector.ResolutionStrategy; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionSelectorHostApi; +import java.util.Objects; + +/** + * Host API implementation for {@link ResolutionSelector}. + * + *

This class handles instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class ResolutionSelectorHostApiImpl implements ResolutionSelectorHostApi { + private final InstanceManager instanceManager; + private final ResolutionSelectorProxy proxy; + + /** Proxy for constructors and static method of {@link ResolutionSelector}. */ + @VisibleForTesting + public static class ResolutionSelectorProxy { + /** Creates an instance of {@link ResolutionSelector}. */ + @NonNull + public ResolutionSelector create( + @Nullable ResolutionStrategy resolutionStrategy, + @Nullable AspectRatioStrategy aspectRatioStrategy) { + final ResolutionSelector.Builder builder = new ResolutionSelector.Builder(); + if (resolutionStrategy != null) { + builder.setResolutionStrategy(resolutionStrategy); + } + if (aspectRatioStrategy != null) { + builder.setAspectRatioStrategy(aspectRatioStrategy); + } + return builder.build(); + } + } + + /** + * Constructs a {@link ResolutionSelectorHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public ResolutionSelectorHostApiImpl(@NonNull InstanceManager instanceManager) { + this(instanceManager, new ResolutionSelectorProxy()); + } + + /** + * Constructs a {@link ResolutionSelectorHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructors and static method of {@link ResolutionSelector} + */ + @VisibleForTesting + ResolutionSelectorHostApiImpl( + @NonNull InstanceManager instanceManager, @NonNull ResolutionSelectorProxy proxy) { + this.instanceManager = instanceManager; + this.proxy = proxy; + } + + /** + * Creates a {@link ResolutionSelector} instance with the {@link ResolutionStrategy} and {@link + * AspectRatio} that have the identifiers specified if provided. + */ + @Override + public void create( + @NonNull Long identifier, + @Nullable Long resolutionStrategyIdentifier, + @Nullable Long aspectRatioStrategyIdentifier) { + instanceManager.addDartCreatedInstance( + proxy.create( + resolutionStrategyIdentifier == null + ? null + : Objects.requireNonNull(instanceManager.getInstance(resolutionStrategyIdentifier)), + aspectRatioStrategyIdentifier == null + ? null + : Objects.requireNonNull( + instanceManager.getInstance(aspectRatioStrategyIdentifier))), + identifier); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java new file mode 100644 index 000000000000..c110c40446ef --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java @@ -0,0 +1,81 @@ +// 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.Size; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.camera.core.resolutionselector.ResolutionStrategy; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionStrategyHostApi; + +/** + * Host API implementation for {@link ResolutionStrategy}. + * + *

This class handles instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class ResolutionStrategyHostApiImpl implements ResolutionStrategyHostApi { + private final InstanceManager instanceManager; + private final ResolutionStrategyProxy proxy; + + /** Proxy for constructors and static method of {@link ResolutionStrategy}. */ + @VisibleForTesting + public static class ResolutionStrategyProxy { + + /** Creates an instance of {@link ResolutionStrategy}. */ + @NonNull + public ResolutionStrategy create(@NonNull Size boundSize, @NonNull Long fallbackRule) { + return new ResolutionStrategy(boundSize, fallbackRule.intValue()); + } + } + + /** + * Constructs a {@link ResolutionStrategyHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public ResolutionStrategyHostApiImpl(@NonNull InstanceManager instanceManager) { + this(instanceManager, new ResolutionStrategyProxy()); + } + + /** + * Constructs a {@link ResolutionStrategyHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructors and static method of {@link ResolutionStrategy} + */ + @VisibleForTesting + ResolutionStrategyHostApiImpl( + @NonNull InstanceManager instanceManager, @NonNull ResolutionStrategyProxy proxy) { + this.instanceManager = instanceManager; + this.proxy = proxy; + } + + /** + * Creates a {@link ResolutionStrategy} instance with the {@link + * GeneratedCameraXLibrary.ResolutionInfo} bound size and {@code fallbackRule} if specified. + */ + @Override + public void create( + @NonNull Long identifier, + @Nullable GeneratedCameraXLibrary.ResolutionInfo boundSize, + @Nullable Long fallbackRule) { + ResolutionStrategy resolutionStrategy; + if (boundSize == null && fallbackRule == null) { + // Strategy that chooses the highest available resolution does not have a bound size or fallback rule. + resolutionStrategy = ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY; + } else if (boundSize == null) { + throw new IllegalArgumentException( + "A bound size must be specified if a non-null fallback rule is specified to create a valid ResolutionStrategy."); + } else { + resolutionStrategy = + proxy.create( + new Size(boundSize.getWidth().intValue(), boundSize.getHeight().intValue()), + fallbackRule); + } + instanceManager.addDartCreatedInstance(resolutionStrategy, identifier); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AspectRatioStrategyTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AspectRatioStrategyTest.java new file mode 100644 index 000000000000..02e757dc39fa --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AspectRatioStrategyTest.java @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import androidx.camera.core.resolutionselector.AspectRatioStrategy; +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 AspectRatioStrategyTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public AspectRatioStrategy mockAspectRatioStrategy; + @Mock public AspectRatioStrategyHostApiImpl.AspectRatioStrategyProxy mockProxy; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void hostApiCreate_createsExpectedAspectRatioStrategyInstance() { + final Long preferredAspectRatio = 0L; + final Long fallbackRule = 1L; + + when(mockProxy.create(preferredAspectRatio, fallbackRule)).thenReturn(mockAspectRatioStrategy); + + final AspectRatioStrategyHostApiImpl hostApi = + new AspectRatioStrategyHostApiImpl(instanceManager, mockProxy); + + final long instanceIdentifier = 0; + hostApi.create(instanceIdentifier, preferredAspectRatio, fallbackRule); + + assertEquals(instanceManager.getInstance(instanceIdentifier), mockAspectRatioStrategy); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java index 38f77761da99..b9a6d299ae30 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java @@ -12,18 +12,16 @@ import static org.mockito.Mockito.when; import android.content.Context; -import android.util.Size; import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.resolutionselector.ResolutionSelector; import androidx.test.core.app.ApplicationProvider; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; import java.util.concurrent.Executor; 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; @@ -55,27 +53,19 @@ public void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdenti new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager); final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); final ImageAnalysis.Builder mockImageAnalysisBuilder = mock(ImageAnalysis.Builder.class); - final int targetResolutionWidth = 10; - final int targetResolutionHeight = 50; - final ResolutionInfo resolutionInfo = - new ResolutionInfo.Builder() - .setWidth(Long.valueOf(targetResolutionWidth)) - .setHeight(Long.valueOf(targetResolutionHeight)) - .build(); + final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class); final long instanceIdentifier = 0; + final long mockResolutionSelectorId = 25; hostApi.cameraXProxy = mockCameraXProxy; - - final ArgumentCaptor sizeCaptor = ArgumentCaptor.forClass(Size.class); + instanceManager.addDartCreatedInstance(mockResolutionSelector, mockResolutionSelectorId); when(mockCameraXProxy.createImageAnalysisBuilder()).thenReturn(mockImageAnalysisBuilder); when(mockImageAnalysisBuilder.build()).thenReturn(mockImageAnalysis); - hostApi.create(instanceIdentifier, resolutionInfo); + hostApi.create(instanceIdentifier, mockResolutionSelectorId); - verify(mockImageAnalysisBuilder).setTargetResolution(sizeCaptor.capture()); - assertEquals(sizeCaptor.getValue().getWidth(), targetResolutionWidth); - assertEquals(sizeCaptor.getValue().getHeight(), targetResolutionHeight); + verify(mockImageAnalysisBuilder).setResolutionSelector(mockResolutionSelector); assertEquals(instanceManager.getInstance(instanceIdentifier), mockImageAnalysis); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java index 881b6bea5a75..df6c8ee74b7f 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java @@ -4,7 +4,6 @@ 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; @@ -15,9 +14,9 @@ import static org.mockito.Mockito.when; import android.content.Context; -import android.util.Size; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; +import androidx.camera.core.resolutionselector.ResolutionSelector; import io.flutter.plugin.common.BinaryMessenger; import java.io.File; import java.io.IOException; @@ -27,7 +26,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnit; @@ -66,26 +64,18 @@ public void create_createsImageCaptureWithCorrectConfiguration() { final ImageCapture.Builder mockImageCaptureBuilder = mock(ImageCapture.Builder.class); final Long imageCaptureIdentifier = 74L; final Long flashMode = Long.valueOf(ImageCapture.FLASH_MODE_ON); - final int targetResolutionWidth = 10; - final int targetResolutionHeight = 50; - final GeneratedCameraXLibrary.ResolutionInfo resolutionInfo = - new GeneratedCameraXLibrary.ResolutionInfo.Builder() - .setWidth(Long.valueOf(targetResolutionWidth)) - .setHeight(Long.valueOf(targetResolutionHeight)) - .build(); + final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class); + final long mockResolutionSelectorId = 77; imageCaptureHostApiImpl.cameraXProxy = mockCameraXProxy; + testInstanceManager.addDartCreatedInstance(mockResolutionSelector, mockResolutionSelectorId); when(mockCameraXProxy.createImageCaptureBuilder()).thenReturn(mockImageCaptureBuilder); when(mockImageCaptureBuilder.build()).thenReturn(mockImageCapture); - final ArgumentCaptor sizeCaptor = ArgumentCaptor.forClass(Size.class); - - imageCaptureHostApiImpl.create(imageCaptureIdentifier, flashMode, resolutionInfo); + imageCaptureHostApiImpl.create(imageCaptureIdentifier, flashMode, mockResolutionSelectorId); verify(mockImageCaptureBuilder).setFlashMode(flashMode.intValue()); - verify(mockImageCaptureBuilder).setTargetResolution(sizeCaptor.capture()); - assertEquals(sizeCaptor.getValue().getWidth(), targetResolutionWidth); - assertEquals(sizeCaptor.getValue().getHeight(), targetResolutionHeight); + verify(mockImageCaptureBuilder).setResolutionSelector(mockResolutionSelector); verify(mockImageCaptureBuilder).build(); verify(testInstanceManager).addDartCreatedInstance(mockImageCapture, imageCaptureIdentifier); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java index 39b73abd7381..37a7c7704135 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java @@ -18,6 +18,7 @@ import android.view.Surface; import androidx.camera.core.Preview; import androidx.camera.core.SurfaceRequest; +import androidx.camera.core.resolutionselector.ResolutionSelector; import androidx.core.util.Consumer; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; @@ -63,27 +64,20 @@ public void create_createsPreviewWithCorrectConfiguration() { new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); final Preview.Builder mockPreviewBuilder = mock(Preview.Builder.class); final int targetRotation = 90; - final int targetResolutionWidth = 10; - final int targetResolutionHeight = 50; final Long previewIdentifier = 3L; - final GeneratedCameraXLibrary.ResolutionInfo resolutionInfo = - new GeneratedCameraXLibrary.ResolutionInfo.Builder() - .setWidth(Long.valueOf(targetResolutionWidth)) - .setHeight(Long.valueOf(targetResolutionHeight)) - .build(); + final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class); + final long mockResolutionSelectorId = 90; previewHostApi.cameraXProxy = mockCameraXProxy; + testInstanceManager.addDartCreatedInstance(mockResolutionSelector, mockResolutionSelectorId); when(mockCameraXProxy.createPreviewBuilder()).thenReturn(mockPreviewBuilder); when(mockPreviewBuilder.build()).thenReturn(mockPreview); - final ArgumentCaptor sizeCaptor = ArgumentCaptor.forClass(Size.class); - - previewHostApi.create(previewIdentifier, Long.valueOf(targetRotation), resolutionInfo); + previewHostApi.create( + previewIdentifier, Long.valueOf(targetRotation), mockResolutionSelectorId); verify(mockPreviewBuilder).setTargetRotation(targetRotation); - verify(mockPreviewBuilder).setTargetResolution(sizeCaptor.capture()); - assertEquals(sizeCaptor.getValue().getWidth(), targetResolutionWidth); - assertEquals(sizeCaptor.getValue().getHeight(), targetResolutionHeight); + verify(mockPreviewBuilder).setResolutionSelector(mockResolutionSelector); verify(mockPreviewBuilder).build(); verify(testInstanceManager).addDartCreatedInstance(mockPreview, previewIdentifier); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java new file mode 100644 index 000000000000..f323e4706c9b --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import androidx.camera.core.resolutionselector.AspectRatioStrategy; +import androidx.camera.core.resolutionselector.ResolutionSelector; +import androidx.camera.core.resolutionselector.ResolutionStrategy; +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 ResolutionSelectorTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public ResolutionSelector mockResolutionSelector; + @Mock public ResolutionSelectorHostApiImpl.ResolutionSelectorProxy mockProxy; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void hostApiCreate_createsExpectedResolutionSelectorInstance() { + final ResolutionStrategy mockResolutionStrategy = mock(ResolutionStrategy.class); + final long resolutionStrategyIdentifier = 14; + instanceManager.addDartCreatedInstance(mockResolutionStrategy, resolutionStrategyIdentifier); + + final AspectRatioStrategy mockAspectRatioStrategy = mock(AspectRatioStrategy.class); + final long aspectRatioStrategyIdentifier = 15; + instanceManager.addDartCreatedInstance(mockAspectRatioStrategy, aspectRatioStrategyIdentifier); + + when(mockProxy.create(mockResolutionStrategy, mockAspectRatioStrategy)) + .thenReturn(mockResolutionSelector); + final ResolutionSelectorHostApiImpl hostApi = + new ResolutionSelectorHostApiImpl(instanceManager, mockProxy); + + final long instanceIdentifier = 0; + hostApi.create(instanceIdentifier, resolutionStrategyIdentifier, aspectRatioStrategyIdentifier); + + assertEquals(instanceManager.getInstance(instanceIdentifier), mockResolutionSelector); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionStrategyTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionStrategyTest.java new file mode 100644 index 000000000000..7bbc6152da0c --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionStrategyTest.java @@ -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. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.util.Size; +import androidx.camera.core.resolutionselector.ResolutionStrategy; +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 ResolutionStrategyTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public ResolutionStrategy mockResolutionStrategy; + @Mock public ResolutionStrategyHostApiImpl.ResolutionStrategyProxy mockProxy; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void hostApiCreate_createsExpectedResolutionStrategyInstanceWhenArgumentsValid() { + final GeneratedCameraXLibrary.ResolutionInfo boundSize = + new GeneratedCameraXLibrary.ResolutionInfo.Builder().setWidth(50L).setHeight(30L).build(); + + final Long fallbackRule = 0L; + + when(mockProxy.create(any(Size.class), eq(fallbackRule))).thenReturn(mockResolutionStrategy); + + final ResolutionStrategyHostApiImpl hostApi = + new ResolutionStrategyHostApiImpl(instanceManager, mockProxy); + + final long instanceIdentifier = 0; + hostApi.create(instanceIdentifier, boundSize, fallbackRule); + + assertEquals(instanceManager.getInstance(instanceIdentifier), mockResolutionStrategy); + } + + @Test + public void hostApiCreate_throwsAssertionErrorWhenArgumentsInvalid() { + final Long fallbackRule = 8L; + final long instanceIdentifier = 0; + + final ResolutionStrategyHostApiImpl hostApi = + new ResolutionStrategyHostApiImpl(instanceManager, mockProxy); + + // We expect an exception to be thrown if fallback rule is specified but bound size is not. + assertThrows( + IllegalArgumentException.class, + () -> hostApi.create(instanceIdentifier, null, fallbackRule)); + } +} 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 9a58e6f4df86..7c5bf6b37e6e 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 @@ -116,10 +116,6 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting CameraSelector? cameraSelector; - /// The resolution preset used to create a camera that should be used for - /// capturing still images and recording video. - ResolutionPreset? _resolutionPreset; - /// The controller we need to broadcast the different camera events. /// /// It is a `broadcast` because multiple controllers will connect to @@ -233,19 +229,17 @@ class AndroidCameraCameraX extends CameraPlatform { processCameraProvider ??= await ProcessCameraProvider.getInstance(); processCameraProvider!.unbindAll(); + // TODO(camsim99): Implement resolution configuration for UseCases + // configured here. https://github.com/flutter/flutter/issues/120462 + // Configure Preview instance. - _resolutionPreset = resolutionPreset; final int targetRotation = _getTargetRotation(cameraDescription.sensorOrientation); - final ResolutionInfo? previewTargetResolution = - _getTargetResolutionForPreview(resolutionPreset); - preview = createPreview(targetRotation, previewTargetResolution); + preview = createPreview(targetRotation); final int flutterSurfaceTextureId = await preview!.setSurfaceProvider(); // Configure ImageCapture instance. - final ResolutionInfo? imageCaptureTargetResolution = - _getTargetResolutionForImageCapture(_resolutionPreset); - imageCapture = createImageCapture(null, imageCaptureTargetResolution); + imageCapture = createImageCapture(null); // Configure VideoCapture and Recorder instances. // TODO(gmackall): Enable video capture resolution configuration in createRecorder(). @@ -642,7 +636,7 @@ class AndroidCameraCameraX extends CameraPlatform { // TODO(camsim99): Support resolution configuration. // Defaults to YUV_420_888 image format. - imageAnalysis ??= createImageAnalysis(null); + imageAnalysis ??= createImageAnalysis(); unawaited(imageAnalysis!.setAnalyzer(analyzer)); if (await processCameraProvider!.isBound(imageAnalysis!)) { @@ -780,23 +774,6 @@ class AndroidCameraCameraX extends CameraPlatform { } } - /// Returns [ResolutionInfo] that maps to the specified resolution preset for - /// a camera preview. - ResolutionInfo? _getTargetResolutionForPreview(ResolutionPreset? resolution) { - // TODO(camsim99): Implement resolution configuration. - // https://github.com/flutter/flutter/issues/120462 - return null; - } - - /// Returns [ResolutionInfo] that maps to the specified resolution preset for - /// image capture. - ResolutionInfo? _getTargetResolutionForImageCapture( - ResolutionPreset? resolution) { - // TODO(camsim99): Implement resolution configuration. - // https://github.com/flutter/flutter/issues/120462 - return null; - } - // Methods for calls that need to be tested: /// Requests camera permissions. @@ -829,18 +806,15 @@ class AndroidCameraCameraX extends CameraPlatform { /// Returns a [Preview] configured with the specified target rotation and /// resolution. @visibleForTesting - Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) { - return Preview( - targetRotation: targetRotation, targetResolution: targetResolution); + Preview createPreview(int targetRotation) { + return Preview(targetRotation: targetRotation); } /// Returns an [ImageCapture] configured with specified flash mode and /// target resolution. @visibleForTesting - ImageCapture createImageCapture( - int? flashMode, ResolutionInfo? targetResolution) { - return ImageCapture( - targetFlashMode: flashMode, targetResolution: targetResolution); + ImageCapture createImageCapture(int? flashMode) { + return ImageCapture(targetFlashMode: flashMode); } /// Returns a [Recorder] for use in video capture. @@ -857,7 +831,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// Returns an [ImageAnalysis] configured with specified target resolution. @visibleForTesting - ImageAnalysis createImageAnalysis(ResolutionInfo? targetResolution) { - return ImageAnalysis(targetResolution: targetResolution); + ImageAnalysis createImageAnalysis() { + return ImageAnalysis(); } } diff --git a/packages/camera/camera_android_camerax/lib/src/aspect_ratio_strategy.dart b/packages/camera/camera_android_camerax/lib/src/aspect_ratio_strategy.dart new file mode 100644 index 000000000000..621f4ddb2edc --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/aspect_ratio_strategy.dart @@ -0,0 +1,138 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// The aspect ratio of a UseCase. +/// +/// Aspect ratio is the ratio of width to height. +/// +/// See https://developer.android.com/reference/androidx/camera/core/AspectRatio. +class AspectRatio { + AspectRatio._(); + + /// 4:3 standard aspect ratio. + /// + /// See https://developer.android.com/reference/androidx/camera/core/AspectRatio#RATIO_4_3(). + static const int ratio4To3 = 0; + + /// 16:9 standard aspect ratio. + /// + /// See https://developer.android.com/reference/androidx/camera/core/AspectRatio#RATIO_16_9(). + static const int ratio16To9 = 1; + + /// The aspect ratio representing no preference for aspect ratio. + /// + /// See https://developer.android.com/reference/androidx/camera/core/AspectRatio#RATIO_DEFAULT(). + static const int ratioDefault = -1; +} + +/// The aspect ratio strategy defines the sequence of aspect ratios that are +/// used to select the best size for a particular image. +/// +/// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/AspectRatioStrategy. +@immutable +class AspectRatioStrategy extends JavaObject { + /// Construct a [AspectRatioStrategy]. + AspectRatioStrategy({ + required this.preferredAspectRatio, + required this.fallbackRule, + super.binaryMessenger, + super.instanceManager, + }) : _api = _AspectRatioStrategyHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached() { + _api.createFromInstances(this, preferredAspectRatio, fallbackRule); + } + + /// Instantiates a [AspectRatioStrategy] without creating and attaching to an + /// instance of the associated native class. + /// + /// This should only be used outside of tests by subclasses created by this + /// library or to create a copy for an [InstanceManager]. + AspectRatioStrategy.detached({ + required this.preferredAspectRatio, + required this.fallbackRule, + super.binaryMessenger, + super.instanceManager, + }) : _api = _AspectRatioStrategyHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached(); + + /// CameraX doesn't fall back to select sizes of any other aspect ratio when + /// this fallback rule is used. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/AspectRatioStrategy#FALLBACK_RULE_NONE(). + static const int fallbackRuleNone = 0; + + /// CameraX automatically chooses the next best aspect ratio which contains + /// the closest field of view (FOV) of the camera sensor, from the remaining + /// options. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/AspectRatioStrategy#FALLBACK_RULE_AUTO(). + static const int fallbackRuleAuto = 1; + + final _AspectRatioStrategyHostApiImpl _api; + + /// The preferred aspect ratio captured by the camera. + final int preferredAspectRatio; + + /// The specified fallback rule for choosing the aspect ratio when the + /// preferred aspect ratio is not available. + final int fallbackRule; +} + +/// Host API implementation of [AspectRatioStrategy]. +class _AspectRatioStrategyHostApiImpl extends AspectRatioStrategyHostApi { + /// Constructs an [_AspectRatioStrategyHostApiImpl]. + /// + /// If [binaryMessenger] is null, the default [BinaryMessenger] will be used, + /// which routes to the host platform. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. If left null, it + /// will default to the global instance defined in [JavaObject]. + _AspectRatioStrategyHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Creates a [AspectRatioStrategy] on the native side with the preferred + /// aspect ratio and fallback rule specified. + Future createFromInstances( + AspectRatioStrategy instance, + int preferredAspectRatio, + int fallbackRule, + ) { + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (AspectRatioStrategy original) => AspectRatioStrategy.detached( + preferredAspectRatio: original.preferredAspectRatio, + fallbackRule: original.fallbackRule, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + preferredAspectRatio, + fallbackRule, + ); + } +} 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 2a1b2813f1aa..2d1dccf971c2 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 @@ -22,6 +22,22 @@ enum CameraStateType { pendingOpen, } +/// The types (T) properly wrapped to be used as a LiveData. +/// +/// If you need to add another type to support a type S to use a LiveData in +/// this plugin, ensure the following is done on the Dart side: +/// +/// * In `../lib/src/live_data.dart`, add new cases for S in +/// `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of +/// type S from a LiveData instance and in `LiveDataFlutterApiImpl#create` +/// to create the expected type of LiveData when requested. +/// +/// On the native side, ensure the following is done: +/// +/// * Update `LiveDataHostApiImpl#getValue` is updated to properly return +/// identifiers for instances of type S. +/// * Update `ObserverFlutterApiWrapper#onChanged` to properly handle receiving +/// calls with instances of type S if a LiveData instance is observed. enum LiveDataSupportedType { cameraState, zoomState, @@ -937,9 +953,6 @@ class _PreviewHostApiCodec extends StandardMessageCodec { if (value is ResolutionInfo) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is ResolutionInfo) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -950,8 +963,6 @@ class _PreviewHostApiCodec extends StandardMessageCodec { switch (type) { case 128: return ResolutionInfo.decode(readValue(buffer)!); - case 129: - return ResolutionInfo.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -969,12 +980,12 @@ class PreviewHostApi { static const MessageCodec codec = _PreviewHostApiCodec(); Future create(int arg_identifier, int? arg_rotation, - ResolutionInfo? arg_targetResolution) async { + int? arg_resolutionSelectorId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_identifier, arg_rotation, arg_targetResolution]) + final List? replyList = await channel.send( + [arg_identifier, arg_rotation, arg_resolutionSelectorId]) as List?; if (replyList == null) { throw PlatformException( @@ -1505,8 +1516,92 @@ abstract class RecordingFlutterApi { } } -class _ImageCaptureHostApiCodec extends StandardMessageCodec { - const _ImageCaptureHostApiCodec(); +class ImageCaptureHostApi { + /// Constructor for [ImageCaptureHostApi]. 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. + ImageCaptureHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future create(int arg_identifier, int? arg_flashMode, + int? arg_resolutionSelectorId) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageCaptureHostApi.create', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send( + [arg_identifier, arg_flashMode, arg_resolutionSelectorId]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future setFlashMode(int arg_identifier, int arg_flashMode) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageCaptureHostApi.setFlashMode', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_identifier, arg_flashMode]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future takePicture(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageCaptureHostApi.takePicture', 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 String?)!; + } + } +} + +class _ResolutionStrategyHostApiCodec extends StandardMessageCodec { + const _ResolutionStrategyHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is ResolutionInfo) { @@ -1528,23 +1623,23 @@ class _ImageCaptureHostApiCodec extends StandardMessageCodec { } } -class ImageCaptureHostApi { - /// Constructor for [ImageCaptureHostApi]. The [binaryMessenger] named argument is +class ResolutionStrategyHostApi { + /// Constructor for [ResolutionStrategyHostApi]. 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. - ImageCaptureHostApi({BinaryMessenger? binaryMessenger}) + ResolutionStrategyHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _ImageCaptureHostApiCodec(); + static const MessageCodec codec = _ResolutionStrategyHostApiCodec(); - Future create(int arg_identifier, int? arg_flashMode, - ResolutionInfo? arg_targetResolution) async { + Future create(int arg_identifier, ResolutionInfo? arg_boundSize, + int? arg_fallbackRule) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImageCaptureHostApi.create', codec, + 'dev.flutter.pigeon.ResolutionStrategyHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send( - [arg_identifier, arg_flashMode, arg_targetResolution]) + final List? replyList = await channel + .send([arg_identifier, arg_boundSize, arg_fallbackRule]) as List?; if (replyList == null) { throw PlatformException( @@ -1561,13 +1656,28 @@ class ImageCaptureHostApi { return; } } +} - Future setFlashMode(int arg_identifier, int arg_flashMode) async { +class ResolutionSelectorHostApi { + /// Constructor for [ResolutionSelectorHostApi]. 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. + ResolutionSelectorHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future create(int arg_identifier, int? arg_resolutionStrategyIdentifier, + int? arg_aspectRatioStrategyIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImageCaptureHostApi.setFlashMode', codec, + 'dev.flutter.pigeon.ResolutionSelectorHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_identifier, arg_flashMode]) as List?; + final List? replyList = await channel.send([ + arg_identifier, + arg_resolutionStrategyIdentifier, + arg_aspectRatioStrategyIdentifier + ]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -1583,13 +1693,28 @@ class ImageCaptureHostApi { return; } } +} - Future takePicture(int arg_identifier) async { +class AspectRatioStrategyHostApi { + /// Constructor for [AspectRatioStrategyHostApi]. 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. + AspectRatioStrategyHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future create(int arg_identifier, int arg_preferredAspectRatio, + int arg_fallbackRule) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.ImageCaptureHostApi.takePicture', codec, + 'dev.flutter.pigeon.AspectRatioStrategyHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_identifier]) as List?; + final List? replyList = await channel.send([ + arg_identifier, + arg_preferredAspectRatio, + arg_fallbackRule + ]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -1601,13 +1726,8 @@ class ImageCaptureHostApi { 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 String?)!; + return; } } } @@ -1767,29 +1887,6 @@ abstract class ZoomStateFlutterApi { } } -class _ImageAnalysisHostApiCodec extends StandardMessageCodec { - const _ImageAnalysisHostApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is ResolutionInfo) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return ResolutionInfo.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - class ImageAnalysisHostApi { /// Constructor for [ImageAnalysisHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -1798,16 +1895,15 @@ class ImageAnalysisHostApi { : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _ImageAnalysisHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - Future create(int arg_identifier, - ResolutionInfo? arg_targetResolutionIdentifier) async { + Future create(int arg_identifier, int? arg_resolutionSelectorId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImageAnalysisHostApi.create', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_identifier, arg_targetResolutionIdentifier]) - as List?; + final List? replyList = + await channel.send([arg_identifier, arg_resolutionSelectorId]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/camera/camera_android_camerax/lib/src/image_analysis.dart b/packages/camera/camera_android_camerax/lib/src/image_analysis.dart index 4d2a335a3c34..e28457c6d192 100644 --- a/packages/camera/camera_android_camerax/lib/src/image_analysis.dart +++ b/packages/camera/camera_android_camerax/lib/src/image_analysis.dart @@ -12,6 +12,7 @@ import 'android_camera_camerax_flutter_api_impls.dart'; import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'resolution_selector.dart'; import 'use_case.dart'; /// Use case for providing CPU accessible images for performing image analysis. @@ -23,13 +24,13 @@ class ImageAnalysis extends UseCase { ImageAnalysis( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, - this.targetResolution}) + this.resolutionSelector}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = _ImageAnalysisHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); - _api.createfromInstances(this, targetResolution); + _api.createfromInstances(this, resolutionSelector); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } @@ -37,7 +38,7 @@ class ImageAnalysis extends UseCase { ImageAnalysis.detached( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, - this.targetResolution}) + this.resolutionSelector}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { @@ -49,7 +50,10 @@ class ImageAnalysis extends UseCase { late final _ImageAnalysisHostApiImpl _api; /// Target resolution of the camera preview stream. - final ResolutionInfo? targetResolution; + /// + /// If not set, this [UseCase] will default to the behavior described in: + /// https://developer.android.com/reference/androidx/camera/core/ImageAnalysis.Builder#setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector). + final ResolutionSelector? resolutionSelector; /// Sets an [Analyzer] to receive and analyze images. Future setAnalyzer(Analyzer analyzer) => @@ -83,18 +87,20 @@ class _ImageAnalysisHostApiImpl extends ImageAnalysisHostApi { /// on the native side. Future createfromInstances( ImageAnalysis instance, - ResolutionInfo? targetResolution, + ResolutionSelector? resolutionSelector, ) { return create( instanceManager.addDartCreatedInstance( instance, onCopy: (ImageAnalysis original) => ImageAnalysis.detached( - targetResolution: original.targetResolution, + resolutionSelector: original.resolutionSelector, binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), ), - targetResolution, + resolutionSelector == null + ? null + : instanceManager.getIdentifier(resolutionSelector), ); } diff --git a/packages/camera/camera_android_camerax/lib/src/image_capture.dart b/packages/camera/camera_android_camerax/lib/src/image_capture.dart index f7f45d15ffbb..76fd9c4ae5c0 100644 --- a/packages/camera/camera_android_camerax/lib/src/image_capture.dart +++ b/packages/camera/camera_android_camerax/lib/src/image_capture.dart @@ -8,6 +8,7 @@ import 'package:meta/meta.dart' show immutable; import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'resolution_selector.dart'; import 'use_case.dart'; /// Use case for picture taking. @@ -20,14 +21,14 @@ class ImageCapture extends UseCase { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.targetFlashMode, - this.targetResolution, + this.resolutionSelector, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ) { _api = ImageCaptureHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); - _api.createFromInstance(this, targetFlashMode, targetResolution); + _api.createFromInstance(this, targetFlashMode, resolutionSelector); } /// Constructs a [ImageCapture] that is not automatically attached to a native object. @@ -35,7 +36,7 @@ class ImageCapture extends UseCase { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.targetFlashMode, - this.targetResolution, + this.resolutionSelector, }) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, @@ -50,7 +51,10 @@ class ImageCapture extends UseCase { final int? targetFlashMode; /// Target resolution of the image output from taking a picture. - final ResolutionInfo? targetResolution; + /// + /// If not set, this [UseCase] will default to the behavior described in: + /// https://developer.android.com/reference/androidx/camera/core/ImageCapture.Builder#setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector). + final ResolutionSelector? resolutionSelector; /// Constant for automatic flash mode. /// @@ -121,16 +125,21 @@ class ImageCaptureHostApiImpl extends ImageCaptureHostApi { /// Creates an [ImageCapture] instance with the flash mode and target resolution /// if specified. void createFromInstance(ImageCapture instance, int? targetFlashMode, - ResolutionInfo? targetResolution) { + ResolutionSelector? resolutionSelector) { final int identifier = instanceManager.addDartCreatedInstance(instance, onCopy: (ImageCapture original) { return ImageCapture.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, targetFlashMode: original.targetFlashMode, - targetResolution: original.targetResolution); + resolutionSelector: original.resolutionSelector); }); - create(identifier, targetFlashMode, targetResolution); + create( + identifier, + targetFlashMode, + resolutionSelector == null + ? null + : instanceManager.getIdentifier(resolutionSelector)); } /// Sets the flash mode for the specified [ImageCapture] instance to take diff --git a/packages/camera/camera_android_camerax/lib/src/preview.dart b/packages/camera/camera_android_camerax/lib/src/preview.dart index a5307463a58c..f0568078bedb 100644 --- a/packages/camera/camera_android_camerax/lib/src/preview.dart +++ b/packages/camera/camera_android_camerax/lib/src/preview.dart @@ -8,6 +8,7 @@ import 'package:meta/meta.dart' show immutable; import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'resolution_selector.dart'; import 'use_case.dart'; /// Use case that provides a camera preview stream for display. @@ -20,13 +21,13 @@ class Preview extends UseCase { {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.targetRotation, - this.targetResolution}) + this.resolutionSelector}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = PreviewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); - _api.createFromInstance(this, targetRotation, targetResolution); + _api.createFromInstance(this, targetRotation, resolutionSelector); } /// Constructs a [Preview] that is not automatically attached to a native object. @@ -34,7 +35,7 @@ class Preview extends UseCase { {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.targetRotation, - this.targetResolution}) + this.resolutionSelector}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { @@ -48,7 +49,10 @@ class Preview extends UseCase { final int? targetRotation; /// Target resolution of the camera preview stream. - final ResolutionInfo? targetResolution; + /// + /// If not set, this [UseCase] will default to the behavior described in: + /// https://developer.android.com/reference/androidx/camera/core/Preview.Builder#setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector). + final ResolutionSelector? resolutionSelector; /// Sets the surface provider for the preview stream. /// @@ -92,17 +96,22 @@ class PreviewHostApiImpl extends PreviewHostApi { /// Creates a [Preview] with the target rotation and target resolution if /// specified. - void createFromInstance( - Preview instance, int? targetRotation, ResolutionInfo? targetResolution) { + void createFromInstance(Preview instance, int? targetRotation, + ResolutionSelector? resolutionSelector) { final int identifier = instanceManager.addDartCreatedInstance(instance, onCopy: (Preview original) { return Preview.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, targetRotation: original.targetRotation, - targetResolution: original.targetResolution); + resolutionSelector: original.resolutionSelector); }); - create(identifier, targetRotation, targetResolution); + create( + identifier, + targetRotation, + resolutionSelector == null + ? null + : instanceManager.getIdentifier(resolutionSelector)); } /// Sets the surface provider of the specified [Preview] instance and returns diff --git a/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart b/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart new file mode 100644 index 000000000000..74191724e5ce --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/resolution_selector.dart @@ -0,0 +1,108 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'aspect_ratio_strategy.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; +import 'resolution_strategy.dart'; + +/// A set of requirements and priorities used to select a resolution for a +/// UseCase. +/// +/// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionSelector. +@immutable +class ResolutionSelector extends JavaObject { + /// Construct a [ResolutionSelector]. + ResolutionSelector({ + this.resolutionStrategy, + this.aspectRatioStrategy, + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionSelectorHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached() { + _api.createFromInstances(this, resolutionStrategy, aspectRatioStrategy); + } + + /// Instantiates a [ResolutionSelector] without creating and attaching to an + /// instance of the associated native class. + /// + /// This should only be used outside of tests by subclasses created by this + /// library or to create a copy for an [InstanceManager]. + ResolutionSelector.detached({ + this.resolutionStrategy, + this.aspectRatioStrategy, + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionSelectorHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached(); + + final _ResolutionSelectorHostApiImpl _api; + + /// Determines how the UseCase will choose the resolution of the captured + /// image. + final ResolutionStrategy? resolutionStrategy; + + /// Determines how the UseCase will choose the aspect ratio of the captured + /// image. + final AspectRatioStrategy? aspectRatioStrategy; +} + +/// Host API implementation of [ResolutionSelector]. +class _ResolutionSelectorHostApiImpl extends ResolutionSelectorHostApi { + /// Constructs an [_ResolutionSelectorHostApiImpl]. + /// + /// If [binaryMessenger] is null, the default [BinaryMessenger] will be used, + /// which routes to the host platform. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. If left null, it + /// will default to the global instance defined in [JavaObject]. + _ResolutionSelectorHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Creates a [ResolutionSelector] on the native side with the + /// [ResolutionStrategy] and [AspectRatioStrategy] if specified. + Future createFromInstances( + ResolutionSelector instance, + ResolutionStrategy? resolutionStrategy, + AspectRatioStrategy? aspectRatioStrategy, + ) { + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (ResolutionSelector original) => ResolutionSelector.detached( + resolutionStrategy: original.resolutionStrategy, + aspectRatioStrategy: original.aspectRatioStrategy, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + resolutionStrategy == null + ? null + : instanceManager.getIdentifier(resolutionStrategy)!, + aspectRatioStrategy == null + ? null + : instanceManager.getIdentifier(aspectRatioStrategy)!, + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/resolution_strategy.dart b/packages/camera/camera_android_camerax/lib/src/resolution_strategy.dart new file mode 100644 index 000000000000..80669f3f8d58 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/resolution_strategy.dart @@ -0,0 +1,189 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// The resolution strategy defines the resolution selection sequence to select +/// the best size. +/// +/// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy. +@immutable +class ResolutionStrategy extends JavaObject { + /// Constructs a [ResolutionStrategy]. + ResolutionStrategy({ + required Size this.boundSize, + this.fallbackRule, + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionStrategyHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached() { + _api.createFromInstances(this, boundSize, fallbackRule); + } + + /// Constructs a [ResolutionStrategy] that represents the strategy that + /// chooses the highest available resolution. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy#HIGHEST_AVAILABLE_STRATEGY(). + ResolutionStrategy.highestAvailableStrategy({ + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionStrategyHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + boundSize = null, + fallbackRule = null, + super.detached() { + _api.createFromInstances(this, boundSize, fallbackRule); + } + + /// Instantiates a [ResolutionStrategy] without creating and attaching to an + /// instance of the associated native class. + /// + /// This should only be used outside of tests by subclasses created by this + /// library or to create a copy for an [InstanceManager]. + ResolutionStrategy.detached({ + required this.boundSize, + this.fallbackRule, + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionStrategyHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + super.detached(); + + /// Instantiates a [ResolutionStrategy] that represents the strategy that + /// chooses the highest available resolution without creating and attaching to + /// an instance of the associated native class. + /// + /// This should only be used outside of tests by subclasses created by this + /// library or to create a copy for an [InstanceManager]. + ResolutionStrategy.detachedHighestAvailableStrategy({ + super.binaryMessenger, + super.instanceManager, + }) : _api = _ResolutionStrategyHostApiImpl( + instanceManager: instanceManager, + binaryMessenger: binaryMessenger, + ), + boundSize = null, + fallbackRule = null, + super.detached(); + + /// CameraX doesn't select an alternate size when the specified bound size is + /// unavailable. + /// + /// Applications will receive [PlatformException] when binding the [UseCase]s + /// with this fallback rule if the device doesn't support the specified bound + /// size. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy#FALLBACK_RULE_NONE(). + static const int fallbackRuleNone = 0; + + /// When the specified bound size is unavailable, CameraX falls back to select + /// the closest higher resolution size. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER(). + static const int fallbackRuleClosestHigherThenLower = 1; + + /// When the specified bound size is unavailable, CameraX falls back to the + /// closest higher resolution size. + /// + /// If CameraX still cannot find any available resolution, it will fallback to + /// select other lower resolutions. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER(). + static const int fallbackRuleClosestHigher = 2; + + /// When the specified bound size is unavailable, CameraX falls back to select + /// the closest lower resolution size. + /// + /// If CameraX still cannot find any available resolution, it will fallback to + /// select other higher resolutions. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER(). + static const int fallbackRuleClosestLowerThenHigher = 3; + + /// When the specified bound size is unavailable, CameraX falls back to the + /// closest lower resolution size. + /// + /// See https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER(). + static const int fallbackRuleClosestLower = 4; + + final _ResolutionStrategyHostApiImpl _api; + + /// The specified bound size for the desired resolution of the camera. + /// + /// If left null, [fallbackRule] must also be left null in order to create a + /// valid [ResolutionStrategy]. This will create the [ResolutionStrategy] + /// that chooses the highest available resolution, which can also be retrieved + /// by calling [getHighestAvailableStrategy]. + final Size? boundSize; + + /// The fallback rule for choosing an alternate size when the specified bound + /// size is unavailable. + /// + /// Must be left null if [boundSize] is specified as null. This will create + /// the [ResolutionStrategy] that chooses the highest available resolution, + /// which can also be retrieved by calling [getHighestAvailableStrategy]. + final int? fallbackRule; +} + +/// Host API implementation of [ResolutionStrategy]. +class _ResolutionStrategyHostApiImpl extends ResolutionStrategyHostApi { + /// Constructs an [_ResolutionStrategyHostApiImpl]. + /// + /// If [binaryMessenger] is null, the default [BinaryMessenger] will be used, + /// which routes to the host platform. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. If left null, it + /// will default to the global instance defined in [JavaObject]. + _ResolutionStrategyHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Creates a [ResolutionStrategy] on the native side with the bound [Size] + /// and fallback rule, if specified. + Future createFromInstances( + ResolutionStrategy instance, + Size? boundSize, + int? fallbackRule, + ) { + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (ResolutionStrategy original) => ResolutionStrategy.detached( + boundSize: original.boundSize, + fallbackRule: original.fallbackRule, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + boundSize == null + ? null + : ResolutionInfo( + width: boundSize.width.toInt(), + height: boundSize.height.toInt(), + ), + fallbackRule, + ); + } +} diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 601601dbf365..a22d7f6e3514 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -196,7 +196,7 @@ abstract class SystemServicesFlutterApi { @HostApi(dartHostTestHandler: 'TestPreviewHostApi') abstract class PreviewHostApi { - void create(int identifier, int? rotation, ResolutionInfo? targetResolution); + void create(int identifier, int? rotation, int? resolutionSelectorId); int setSurfaceProvider(int identifier); @@ -261,7 +261,7 @@ abstract class RecordingFlutterApi { @HostApi(dartHostTestHandler: 'TestImageCaptureHostApi') abstract class ImageCaptureHostApi { - void create(int identifier, int? flashMode, ResolutionInfo? targetResolution); + void create(int identifier, int? flashMode, int? resolutionSelectorId); void setFlashMode(int identifier, int flashMode); @@ -269,6 +269,25 @@ abstract class ImageCaptureHostApi { String takePicture(int identifier); } +@HostApi(dartHostTestHandler: 'TestResolutionStrategyHostApi') +abstract class ResolutionStrategyHostApi { + void create(int identifier, ResolutionInfo? boundSize, int? fallbackRule); +} + +@HostApi(dartHostTestHandler: 'TestResolutionSelectorHostApi') +abstract class ResolutionSelectorHostApi { + void create( + int identifier, + int? resolutionStrategyIdentifier, + int? aspectRatioStrategyIdentifier, + ); +} + +@HostApi(dartHostTestHandler: 'TestAspectRatioStrategyHostApi') +abstract class AspectRatioStrategyHostApi { + void create(int identifier, int preferredAspectRatio, int fallbackRule); +} + @FlutterApi() abstract class CameraStateFlutterApi { void create(int identifier, CameraStateTypeData type, int? errorIdentifier); @@ -289,7 +308,7 @@ abstract class ZoomStateFlutterApi { @HostApi(dartHostTestHandler: 'TestImageAnalysisHostApi') abstract class ImageAnalysisHostApi { - void create(int identifier, ResolutionInfo? targetResolutionIdentifier); + void create(int identifier, int? resolutionSelectorId); void setAnalyzer(int identifier, int analyzerIdentifier); diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 0fea9fc68240..f8927d03112b 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.5.0+11 +version: 0.5.0+12 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index bdebdce095d6..a0e60b60be88 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 @@ -1127,13 +1127,12 @@ class FakeAndroidCameraCameraX extends AndroidCameraCameraX { } @override - Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) { + Preview createPreview(int targetRotation) { return testPreview; } @override - ImageCapture createImageCapture( - int? flashMode, ResolutionInfo? targetResolution) { + ImageCapture createImageCapture(int? flashMode) { return testImageCapture; } @@ -1148,7 +1147,7 @@ class FakeAndroidCameraCameraX extends AndroidCameraCameraX { } @override - ImageAnalysis createImageAnalysis(ResolutionInfo? targetResolution) { + ImageAnalysis createImageAnalysis() { return mockImageAnalysis; } } diff --git a/packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.dart b/packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.dart new file mode 100644 index 000000000000..28ec032a2a9e --- /dev/null +++ b/packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.dart @@ -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. + +import 'package:camera_android_camerax/src/aspect_ratio_strategy.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 'aspect_ratio_strategy_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + TestAspectRatioStrategyHostApi, + TestInstanceManagerHostApi, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AspectRatioStrategy', () { + tearDown(() { + TestAspectRatioStrategyHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test( + 'detached create does not make call to create expected AspectRatioStrategy instance', + () async { + final MockTestAspectRatioStrategyHostApi mockApi = + MockTestAspectRatioStrategyHostApi(); + TestAspectRatioStrategyHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const int preferredAspectRatio = 1; + + const int fallbackRule = 1; + + AspectRatioStrategy.detached( + preferredAspectRatio: preferredAspectRatio, + fallbackRule: fallbackRule, + instanceManager: instanceManager, + ); + + verifyNever(mockApi.create( + argThat(isA()), + preferredAspectRatio, + fallbackRule, + )); + }); + + test( + 'HostApi create makes call to create expected AspectRatioStrategy instance', + () { + final MockTestAspectRatioStrategyHostApi mockApi = + MockTestAspectRatioStrategyHostApi(); + TestAspectRatioStrategyHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const int preferredAspectRatio = 0; + + const int fallbackRule = 0; + + final AspectRatioStrategy instance = AspectRatioStrategy( + preferredAspectRatio: preferredAspectRatio, + fallbackRule: fallbackRule, + instanceManager: instanceManager, + ); + + verify(mockApi.create( + instanceManager.getIdentifier(instance), + preferredAspectRatio, + fallbackRule, + )); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.mocks.dart b/packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.mocks.dart new file mode 100644 index 000000000000..28dda66896e0 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.mocks.dart @@ -0,0 +1,68 @@ +// Mocks generated by Mockito 5.4.1 from annotations +// in camera_android_camerax/test/aspect_ratio_strategy_test.dart. +// Do not manually edit this file. + +// @dart=2.19 + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestAspectRatioStrategyHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestAspectRatioStrategyHostApi extends _i1.Mock + implements _i2.TestAspectRatioStrategyHostApi { + MockTestAspectRatioStrategyHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + int? preferredAspectRatio, + int? fallbackRule, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + preferredAspectRatio, + fallbackRule, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i2.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/image_analysis_test.dart b/packages/camera/camera_android_camerax/test/image_analysis_test.dart index d9c657ea97af..80acd655982e 100644 --- a/packages/camera/camera_android_camerax/test/image_analysis_test.dart +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.dart @@ -3,10 +3,10 @@ // found in the LICENSE file. import 'package:camera_android_camerax/src/analyzer.dart'; -import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/image_analysis.dart'; import 'package:camera_android_camerax/src/image_proxy.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/resolution_selector.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -14,7 +14,11 @@ import 'package:mockito/mockito.dart'; import 'image_analysis_test.mocks.dart'; import 'test_camerax_library.g.dart'; -@GenerateMocks([TestImageAnalysisHostApi, TestInstanceManagerHostApi]) +@GenerateMocks([ + TestImageAnalysisHostApi, + TestInstanceManagerHostApi, + ResolutionSelector, +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -22,13 +26,11 @@ void main() { TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); group('ImageAnalysis', () { - setUp(() {}); - tearDown(() { TestImageAnalysisHostApi.setup(null); }); - test('HostApi create', () { + test('create calls create on the Java side', () { final MockTestImageAnalysisHostApi mockApi = MockTestImageAnalysisHostApi(); TestImageAnalysisHostApi.setup(mockApi); @@ -37,25 +39,28 @@ void main() { onWeakReferenceRemoved: (_) {}, ); - const int targetResolutionWidth = 65; - const int targetResolutionHeight = 99; - final ResolutionInfo targetResolution = - ResolutionInfo(width: 65, height: 99); + final MockResolutionSelector mockResolutionSelector = + MockResolutionSelector(); + const int mockResolutionSelectorId = 24; + + instanceManager.addHostCreatedInstance( + mockResolutionSelector, mockResolutionSelectorId, + onCopy: (ResolutionSelector original) { + return MockResolutionSelector(); + }); + final ImageAnalysis instance = ImageAnalysis( - targetResolution: targetResolution, + resolutionSelector: mockResolutionSelector, instanceManager: instanceManager, ); - final VerificationResult createVerification = verify(mockApi.create( + verify(mockApi.create( argThat(equals(instanceManager.getIdentifier(instance))), - captureAny)); - final ResolutionInfo capturedResolutionInfo = - createVerification.captured.single as ResolutionInfo; - expect(capturedResolutionInfo.width, equals(targetResolutionWidth)); - expect(capturedResolutionInfo.height, equals(targetResolutionHeight)); + argThat(equals(mockResolutionSelectorId)))); }); - test('setAnalyzer', () async { + test('setAnalyzer makes call to set analyzer on ImageAnalysis instance', + () async { final MockTestImageAnalysisHostApi mockApi = MockTestImageAnalysisHostApi(); TestImageAnalysisHostApi.setup(mockApi); @@ -65,7 +70,7 @@ void main() { ); final ImageAnalysis instance = ImageAnalysis.detached( - targetResolution: ResolutionInfo(width: 75, height: 98), + resolutionSelector: MockResolutionSelector(), instanceManager: instanceManager, ); const int instanceIdentifier = 0; @@ -73,7 +78,7 @@ void main() { instance, instanceIdentifier, onCopy: (ImageAnalysis original) => ImageAnalysis.detached( - targetResolution: original.targetResolution, + resolutionSelector: original.resolutionSelector, instanceManager: instanceManager, ), ); @@ -102,7 +107,8 @@ void main() { )); }); - test('clearAnalyzer', () async { + test('clearAnalyzer makes call to clear analyzer on ImageAnalysis instance', + () async { final MockTestImageAnalysisHostApi mockApi = MockTestImageAnalysisHostApi(); TestImageAnalysisHostApi.setup(mockApi); @@ -112,7 +118,7 @@ void main() { ); final ImageAnalysis instance = ImageAnalysis.detached( - targetResolution: ResolutionInfo(width: 75, height: 98), + resolutionSelector: MockResolutionSelector(), instanceManager: instanceManager, ); const int instanceIdentifier = 0; @@ -120,7 +126,7 @@ void main() { instance, instanceIdentifier, onCopy: (ImageAnalysis original) => ImageAnalysis.detached( - targetResolution: original.targetResolution, + resolutionSelector: original.resolutionSelector, instanceManager: instanceManager, ), ); diff --git a/packages/camera/camera_android_camerax/test/image_analysis_test.mocks.dart b/packages/camera/camera_android_camerax/test/image_analysis_test.mocks.dart index cbece720c1c2..b0df5790b2c3 100644 --- a/packages/camera/camera_android_camerax/test/image_analysis_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.mocks.dart @@ -5,7 +5,7 @@ // @dart=2.19 // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i3; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'test_camerax_library.g.dart' as _i2; @@ -33,14 +33,14 @@ class MockTestImageAnalysisHostApi extends _i1.Mock @override void create( int? identifier, - _i3.ResolutionInfo? targetResolutionIdentifier, + int? resolutionSelectorId, ) => super.noSuchMethod( Invocation.method( #create, [ identifier, - targetResolutionIdentifier, + resolutionSelectorId, ], ), returnValueForMissingStub: null, @@ -88,3 +88,14 @@ class MockTestInstanceManagerHostApi extends _i1.Mock returnValueForMissingStub: null, ); } + +/// A class which mocks [ResolutionSelector]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockResolutionSelector extends _i1.Mock + implements _i3.ResolutionSelector { + MockResolutionSelector() { + _i1.throwOnMissingStub(this); + } +} diff --git a/packages/camera/camera_android_camerax/test/image_capture_test.dart b/packages/camera/camera_android_camerax/test/image_capture_test.dart index 21c071965dc4..c50314ed3cb3 100644 --- a/packages/camera/camera_android_camerax/test/image_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/image_capture_test.dart @@ -2,9 +2,9 @@ // 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/image_capture.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/resolution_selector.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -12,7 +12,11 @@ import 'package:mockito/mockito.dart'; import 'image_capture_test.mocks.dart'; import 'test_camerax_library.g.dart'; -@GenerateMocks([TestImageCaptureHostApi, TestInstanceManagerHostApi]) +@GenerateMocks([ + TestImageCaptureHostApi, + TestInstanceManagerHostApi, + ResolutionSelector +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -32,11 +36,11 @@ void main() { ImageCapture.detached( instanceManager: instanceManager, targetFlashMode: ImageCapture.flashModeOn, - targetResolution: ResolutionInfo(width: 50, height: 10), + resolutionSelector: MockResolutionSelector(), ); verifyNever(mockApi.create(argThat(isA()), argThat(isA()), - argThat(isA()))); + argThat(isA()))); }); test('create calls create on the Java side', () async { @@ -47,21 +51,26 @@ void main() { onWeakReferenceRemoved: (_) {}, ); const int targetFlashMode = ImageCapture.flashModeAuto; - const int targetResolutionWidth = 10; - const int targetResolutionHeight = 50; + final MockResolutionSelector mockResolutionSelector = + MockResolutionSelector(); + const int mockResolutionSelectorId = 24; + + instanceManager.addHostCreatedInstance( + mockResolutionSelector, mockResolutionSelectorId, + onCopy: (ResolutionSelector original) { + return MockResolutionSelector(); + }); + ImageCapture( instanceManager: instanceManager, targetFlashMode: targetFlashMode, - targetResolution: ResolutionInfo( - width: targetResolutionWidth, height: targetResolutionHeight), + resolutionSelector: mockResolutionSelector, ); - final VerificationResult createVerification = verify(mockApi.create( - argThat(isA()), argThat(equals(targetFlashMode)), captureAny)); - final ResolutionInfo capturedResolutionInfo = - createVerification.captured.single as ResolutionInfo; - expect(capturedResolutionInfo.width, equals(targetResolutionWidth)); - expect(capturedResolutionInfo.height, equals(targetResolutionHeight)); + verify(mockApi.create( + argThat(isA()), + argThat(equals(targetFlashMode)), + argThat(equals(mockResolutionSelectorId)))); }); test('setFlashMode makes call to set flash mode for ImageCapture instance', diff --git a/packages/camera/camera_android_camerax/test/image_capture_test.mocks.dart b/packages/camera/camera_android_camerax/test/image_capture_test.mocks.dart index 1b9e1e777e29..44331c92cff9 100644 --- a/packages/camera/camera_android_camerax/test/image_capture_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/image_capture_test.mocks.dart @@ -5,9 +5,9 @@ // @dart=2.19 // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; +import 'dart:async' as _i3; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i3; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'test_camerax_library.g.dart' as _i2; @@ -36,7 +36,7 @@ class MockTestImageCaptureHostApi extends _i1.Mock void create( int? identifier, int? flashMode, - _i3.ResolutionInfo? targetResolution, + int? resolutionSelectorId, ) => super.noSuchMethod( Invocation.method( @@ -44,7 +44,7 @@ class MockTestImageCaptureHostApi extends _i1.Mock [ identifier, flashMode, - targetResolution, + resolutionSelectorId, ], ), returnValueForMissingStub: null, @@ -65,13 +65,13 @@ class MockTestImageCaptureHostApi extends _i1.Mock returnValueForMissingStub: null, ); @override - _i4.Future takePicture(int? identifier) => (super.noSuchMethod( + _i3.Future takePicture(int? identifier) => (super.noSuchMethod( Invocation.method( #takePicture, [identifier], ), - returnValue: _i4.Future.value(''), - ) as _i4.Future); + returnValue: _i3.Future.value(''), + ) as _i3.Future); } /// A class which mocks [TestInstanceManagerHostApi]. @@ -92,3 +92,14 @@ class MockTestInstanceManagerHostApi extends _i1.Mock returnValueForMissingStub: null, ); } + +/// A class which mocks [ResolutionSelector]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockResolutionSelector extends _i1.Mock + implements _i4.ResolutionSelector { + MockResolutionSelector() { + _i1.throwOnMissingStub(this); + } +} diff --git a/packages/camera/camera_android_camerax/test/preview_test.dart b/packages/camera/camera_android_camerax/test/preview_test.dart index 6eacd42b5caf..29f862cda4c7 100644 --- a/packages/camera/camera_android_camerax/test/preview_test.dart +++ b/packages/camera/camera_android_camerax/test/preview_test.dart @@ -5,6 +5,7 @@ import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:camera_android_camerax/src/preview.dart'; +import 'package:camera_android_camerax/src/resolution_selector.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -12,7 +13,8 @@ import 'package:mockito/mockito.dart'; import 'preview_test.mocks.dart'; import 'test_camerax_library.g.dart'; -@GenerateMocks([TestInstanceManagerHostApi, TestPreviewHostApi]) +@GenerateMocks( + [TestInstanceManagerHostApi, TestPreviewHostApi, ResolutionSelector]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -32,11 +34,11 @@ void main() { Preview.detached( instanceManager: instanceManager, targetRotation: 90, - targetResolution: ResolutionInfo(width: 50, height: 10), + resolutionSelector: MockResolutionSelector(), ); verifyNever(mockApi.create(argThat(isA()), argThat(isA()), - argThat(isA()))); + argThat(isA()))); }); test('create calls create on the Java side', () async { @@ -47,21 +49,26 @@ void main() { onWeakReferenceRemoved: (_) {}, ); const int targetRotation = 90; - const int targetResolutionWidth = 10; - const int targetResolutionHeight = 50; + final MockResolutionSelector mockResolutionSelector = + MockResolutionSelector(); + const int mockResolutionSelectorId = 24; + + instanceManager.addHostCreatedInstance( + mockResolutionSelector, mockResolutionSelectorId, + onCopy: (ResolutionSelector original) { + return MockResolutionSelector(); + }); + Preview( instanceManager: instanceManager, targetRotation: targetRotation, - targetResolution: ResolutionInfo( - width: targetResolutionWidth, height: targetResolutionHeight), + resolutionSelector: mockResolutionSelector, ); - final VerificationResult createVerification = verify(mockApi.create( - argThat(isA()), argThat(equals(targetRotation)), captureAny)); - final ResolutionInfo capturedResolutionInfo = - createVerification.captured.single as ResolutionInfo; - expect(capturedResolutionInfo.width, equals(targetResolutionWidth)); - expect(capturedResolutionInfo.height, equals(targetResolutionHeight)); + verify(mockApi.create( + argThat(isA()), + argThat(equals(targetRotation)), + argThat(equals(mockResolutionSelectorId)))); }); test( diff --git a/packages/camera/camera_android_camerax/test/preview_test.mocks.dart b/packages/camera/camera_android_camerax/test/preview_test.mocks.dart index ae3783641cec..d56e682ac68e 100644 --- a/packages/camera/camera_android_camerax/test/preview_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/preview_test.mocks.dart @@ -6,6 +6,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i2; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'test_camerax_library.g.dart' as _i3; @@ -64,7 +65,7 @@ class MockTestPreviewHostApi extends _i1.Mock void create( int? identifier, int? rotation, - _i2.ResolutionInfo? targetResolution, + int? resolutionSelectorId, ) => super.noSuchMethod( Invocation.method( @@ -72,7 +73,7 @@ class MockTestPreviewHostApi extends _i1.Mock [ identifier, rotation, - targetResolution, + resolutionSelectorId, ], ), returnValueForMissingStub: null, @@ -108,3 +109,14 @@ class MockTestPreviewHostApi extends _i1.Mock ), ) as _i2.ResolutionInfo); } + +/// A class which mocks [ResolutionSelector]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockResolutionSelector extends _i1.Mock + implements _i4.ResolutionSelector { + MockResolutionSelector() { + _i1.throwOnMissingStub(this); + } +} diff --git a/packages/camera/camera_android_camerax/test/resolution_selector_test.dart b/packages/camera/camera_android_camerax/test/resolution_selector_test.dart new file mode 100644 index 000000000000..45ecef576a3a --- /dev/null +++ b/packages/camera/camera_android_camerax/test/resolution_selector_test.dart @@ -0,0 +1,124 @@ +// 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 'dart:ui'; + +import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/resolution_selector.dart'; +import 'package:camera_android_camerax/src/resolution_strategy.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'resolution_selector_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + AspectRatioStrategy, + ResolutionStrategy, + TestResolutionSelectorHostApi, + TestInstanceManagerHostApi, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ResolutionSelector', () { + tearDown(() { + TestResolutionSelectorHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test( + 'detached constructor does not make call to create expected AspectRatioStrategy instance', + () async { + final MockTestResolutionSelectorHostApi mockApi = + MockTestResolutionSelectorHostApi(); + TestResolutionSelectorHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const int preferredAspectRatio = 1; + + const int fallbackRule = 1; + + AspectRatioStrategy.detached( + preferredAspectRatio: preferredAspectRatio, + fallbackRule: fallbackRule, + instanceManager: instanceManager, + ); + + ResolutionSelector.detached( + resolutionStrategy: MockResolutionStrategy(), + aspectRatioStrategy: MockAspectRatioStrategy(), + instanceManager: instanceManager, + ); + + verifyNever(mockApi.create( + argThat(isA()), + argThat(isA()), + argThat(isA()), + )); + }); + + test('HostApi create creates expected ResolutionSelector instance', () { + final MockTestResolutionSelectorHostApi mockApi = + MockTestResolutionSelectorHostApi(); + TestResolutionSelectorHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final ResolutionStrategy resolutionStrategy = ResolutionStrategy.detached( + boundSize: const Size(50, 30), + fallbackRule: ResolutionStrategy.fallbackRuleClosestLower, + instanceManager: instanceManager, + ); + const int resolutionStrategyIdentifier = 14; + instanceManager.addHostCreatedInstance( + resolutionStrategy, + resolutionStrategyIdentifier, + onCopy: (ResolutionStrategy original) => ResolutionStrategy.detached( + boundSize: original.boundSize, + fallbackRule: original.fallbackRule, + instanceManager: instanceManager, + ), + ); + + final AspectRatioStrategy aspectRatioStrategy = + AspectRatioStrategy.detached( + preferredAspectRatio: AspectRatio.ratio4To3, + fallbackRule: AspectRatioStrategy.fallbackRuleAuto, + instanceManager: instanceManager, + ); + const int aspectRatioStrategyIdentifier = 15; + instanceManager.addHostCreatedInstance( + aspectRatioStrategy, + aspectRatioStrategyIdentifier, + onCopy: (AspectRatioStrategy original) => AspectRatioStrategy.detached( + preferredAspectRatio: original.preferredAspectRatio, + fallbackRule: original.fallbackRule, + instanceManager: instanceManager, + ), + ); + + final ResolutionSelector instance = ResolutionSelector( + resolutionStrategy: resolutionStrategy, + aspectRatioStrategy: aspectRatioStrategy, + instanceManager: instanceManager, + ); + + verify(mockApi.create( + instanceManager.getIdentifier(instance), + resolutionStrategyIdentifier, + aspectRatioStrategyIdentifier, + )); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart new file mode 100644 index 000000000000..6ab8de8316d9 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart @@ -0,0 +1,103 @@ +// Mocks generated by Mockito 5.4.1 from annotations +// in camera_android_camerax/test/resolution_selector_test.dart. +// Do not manually edit this file. + +// @dart=2.19 + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart' as _i2; +import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [AspectRatioStrategy]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockAspectRatioStrategy extends _i1.Mock + implements _i2.AspectRatioStrategy { + MockAspectRatioStrategy() { + _i1.throwOnMissingStub(this); + } + + @override + int get preferredAspectRatio => (super.noSuchMethod( + Invocation.getter(#preferredAspectRatio), + returnValue: 0, + ) as int); + @override + int get fallbackRule => (super.noSuchMethod( + Invocation.getter(#fallbackRule), + returnValue: 0, + ) as int); +} + +/// A class which mocks [ResolutionStrategy]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockResolutionStrategy extends _i1.Mock + implements _i3.ResolutionStrategy { + MockResolutionStrategy() { + _i1.throwOnMissingStub(this); + } +} + +/// A class which mocks [TestResolutionSelectorHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestResolutionSelectorHostApi extends _i1.Mock + implements _i4.TestResolutionSelectorHostApi { + MockTestResolutionSelectorHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + int? resolutionStrategyIdentifier, + int? aspectRatioStrategyIdentifier, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + resolutionStrategyIdentifier, + aspectRatioStrategyIdentifier, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i4.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/resolution_strategy_test.dart b/packages/camera/camera_android_camerax/test/resolution_strategy_test.dart new file mode 100644 index 000000000000..9c098b946075 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/resolution_strategy_test.dart @@ -0,0 +1,109 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/resolution_strategy.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'resolution_strategy_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + TestResolutionStrategyHostApi, + TestInstanceManagerHostApi, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ResolutionStrategy', () { + tearDown(() { + TestResolutionStrategyHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test( + 'detached resolutionStrategy constructors do not make call to Host API create', + () { + final MockTestResolutionStrategyHostApi mockApi = + MockTestResolutionStrategyHostApi(); + TestResolutionStrategyHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const Size boundSize = Size(70, 20); + const int fallbackRule = 1; + + ResolutionStrategy.detached( + boundSize: boundSize, + fallbackRule: fallbackRule, + instanceManager: instanceManager, + ); + + verifyNever(mockApi.create( + argThat(isA()), + argThat(isA() + .having((ResolutionInfo size) => size.width, 'width', 50) + .having((ResolutionInfo size) => size.height, 'height', 30)), + fallbackRule, + )); + + ResolutionStrategy.detachedHighestAvailableStrategy( + instanceManager: instanceManager, + ); + + verifyNever(mockApi.create( + argThat(isA()), + null, + null, + )); + }); + + test('HostApi create creates expected ResolutionStrategies', () { + final MockTestResolutionStrategyHostApi mockApi = + MockTestResolutionStrategyHostApi(); + TestResolutionStrategyHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const Size boundSize = Size(50, 30); + const int fallbackRule = 0; + + final ResolutionStrategy instance = ResolutionStrategy( + boundSize: boundSize, + fallbackRule: fallbackRule, + instanceManager: instanceManager, + ); + + verify(mockApi.create( + instanceManager.getIdentifier(instance), + argThat(isA() + .having((ResolutionInfo size) => size.width, 'width', 50) + .having((ResolutionInfo size) => size.height, 'height', 30)), + fallbackRule, + )); + + final ResolutionStrategy highestAvailableInstance = + ResolutionStrategy.highestAvailableStrategy( + instanceManager: instanceManager, + ); + + verify(mockApi.create( + instanceManager.getIdentifier(highestAvailableInstance), + null, + null, + )); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/resolution_strategy_test.mocks.dart b/packages/camera/camera_android_camerax/test/resolution_strategy_test.mocks.dart new file mode 100644 index 000000000000..1a667389bac0 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/resolution_strategy_test.mocks.dart @@ -0,0 +1,69 @@ +// Mocks generated by Mockito 5.4.1 from annotations +// in camera_android_camerax/test/resolution_strategy_test.dart. +// Do not manually edit this file. + +// @dart=2.19 + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestResolutionStrategyHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestResolutionStrategyHostApi extends _i1.Mock + implements _i2.TestResolutionStrategyHostApi { + MockTestResolutionStrategyHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + _i3.ResolutionInfo? boundSize, + int? fallbackRule, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + boundSize, + fallbackRule, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i2.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 2c4afd19b26b..6a80cf0541fb 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 @@ -597,9 +597,6 @@ class _TestPreviewHostApiCodec extends StandardMessageCodec { if (value is ResolutionInfo) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is ResolutionInfo) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -610,8 +607,6 @@ class _TestPreviewHostApiCodec extends StandardMessageCodec { switch (type) { case 128: return ResolutionInfo.decode(readValue(buffer)!); - case 129: - return ResolutionInfo.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -623,7 +618,7 @@ abstract class TestPreviewHostApi { TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestPreviewHostApiCodec(); - void create(int identifier, int? rotation, ResolutionInfo? targetResolution); + void create(int identifier, int? rotation, int? resolutionSelectorId); int setSurfaceProvider(int identifier); @@ -651,9 +646,8 @@ abstract class TestPreviewHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.create was null, expected non-null int.'); final int? arg_rotation = (args[1] as int?); - final ResolutionInfo? arg_targetResolution = - (args[2] as ResolutionInfo?); - api.create(arg_identifier!, arg_rotation, arg_targetResolution); + final int? arg_resolutionSelectorId = (args[2] as int?); + api.create(arg_identifier!, arg_rotation, arg_resolutionSelectorId); return []; }); } @@ -1033,35 +1027,12 @@ abstract class TestRecordingHostApi { } } -class _TestImageCaptureHostApiCodec extends StandardMessageCodec { - const _TestImageCaptureHostApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is ResolutionInfo) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return ResolutionInfo.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - abstract class TestImageCaptureHostApi { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = _TestImageCaptureHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void create(int identifier, int? flashMode, ResolutionInfo? targetResolution); + void create(int identifier, int? flashMode, int? resolutionSelectorId); void setFlashMode(int identifier, int flashMode); @@ -1087,9 +1058,8 @@ abstract class TestImageCaptureHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.ImageCaptureHostApi.create was null, expected non-null int.'); final int? arg_flashMode = (args[1] as int?); - final ResolutionInfo? arg_targetResolution = - (args[2] as ResolutionInfo?); - api.create(arg_identifier!, arg_flashMode, arg_targetResolution); + final int? arg_resolutionSelectorId = (args[2] as int?); + api.create(arg_identifier!, arg_flashMode, arg_resolutionSelectorId); return []; }); } @@ -1144,8 +1114,8 @@ abstract class TestImageCaptureHostApi { } } -class _TestImageAnalysisHostApiCodec extends StandardMessageCodec { - const _TestImageAnalysisHostApiCodec(); +class _TestResolutionStrategyHostApiCodec extends StandardMessageCodec { + const _TestResolutionStrategyHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is ResolutionInfo) { @@ -1167,12 +1137,128 @@ class _TestImageAnalysisHostApiCodec extends StandardMessageCodec { } } +abstract class TestResolutionStrategyHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = + _TestResolutionStrategyHostApiCodec(); + + void create(int identifier, ResolutionInfo? boundSize, int? fallbackRule); + + static void setup(TestResolutionStrategyHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ResolutionStrategyHostApi.create', 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.ResolutionStrategyHostApi.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.ResolutionStrategyHostApi.create was null, expected non-null int.'); + final ResolutionInfo? arg_boundSize = (args[1] as ResolutionInfo?); + final int? arg_fallbackRule = (args[2] as int?); + api.create(arg_identifier!, arg_boundSize, arg_fallbackRule); + return []; + }); + } + } + } +} + +abstract class TestResolutionSelectorHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier, int? resolutionStrategyIdentifier, + int? aspectRatioStrategyIdentifier); + + static void setup(TestResolutionSelectorHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ResolutionSelectorHostApi.create', 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.ResolutionSelectorHostApi.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.ResolutionSelectorHostApi.create was null, expected non-null int.'); + final int? arg_resolutionStrategyIdentifier = (args[1] as int?); + final int? arg_aspectRatioStrategyIdentifier = (args[2] as int?); + api.create(arg_identifier!, arg_resolutionStrategyIdentifier, + arg_aspectRatioStrategyIdentifier); + return []; + }); + } + } + } +} + +abstract class TestAspectRatioStrategyHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier, int preferredAspectRatio, int fallbackRule); + + static void setup(TestAspectRatioStrategyHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.AspectRatioStrategyHostApi.create', 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.AspectRatioStrategyHostApi.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.AspectRatioStrategyHostApi.create was null, expected non-null int.'); + final int? arg_preferredAspectRatio = (args[1] as int?); + assert(arg_preferredAspectRatio != null, + 'Argument for dev.flutter.pigeon.AspectRatioStrategyHostApi.create was null, expected non-null int.'); + final int? arg_fallbackRule = (args[2] as int?); + assert(arg_fallbackRule != null, + 'Argument for dev.flutter.pigeon.AspectRatioStrategyHostApi.create was null, expected non-null int.'); + api.create( + arg_identifier!, arg_preferredAspectRatio!, arg_fallbackRule!); + return []; + }); + } + } + } +} + abstract class TestImageAnalysisHostApi { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = _TestImageAnalysisHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void create(int identifier, ResolutionInfo? targetResolutionIdentifier); + void create(int identifier, int? resolutionSelectorId); void setAnalyzer(int identifier, int analyzerIdentifier); @@ -1197,9 +1283,8 @@ abstract class TestImageAnalysisHostApi { final int? arg_identifier = (args[0] as int?); assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.ImageAnalysisHostApi.create was null, expected non-null int.'); - final ResolutionInfo? arg_targetResolutionIdentifier = - (args[1] as ResolutionInfo?); - api.create(arg_identifier!, arg_targetResolutionIdentifier); + final int? arg_resolutionSelectorId = (args[1] as int?); + api.create(arg_identifier!, arg_resolutionSelectorId); return []; }); }