From 897f6951d9678ab2bb5b45b9ed0191af2b10e0fc Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:30:49 -0700 Subject: [PATCH] [camerax] Wrap classes to implement resolution configuration for image capture, image analysis, and preview (#4523) Wraps classes to implement resolution configuration for image capture, image analysis, and preview. Also bumps CameraX version to latest and removes the deprecated classes used previously. No functionality changes. Also thanks to @bparrishMines who did majority of the work here! Part of https://github.com/flutter/flutter/issues/120462 --- .../camera_android_camerax/CHANGELOG.md | 6 + .../android/build.gradle | 2 +- .../AspectRatioStrategyHostApiImpl.java | 65 ++++ .../camerax/CameraAndroidCameraxPlugin.java | 6 + .../camerax/GeneratedCameraXLibrary.java | 284 +++++++++++++----- .../camerax/ImageAnalysisHostApiImpl.java | 10 +- .../camerax/ImageCaptureHostApiImpl.java | 13 +- .../plugins/camerax/PreviewHostApiImpl.java | 13 +- .../ResolutionSelectorHostApiImpl.java | 87 ++++++ .../ResolutionStrategyHostApiImpl.java | 81 +++++ .../camerax/AspectRatioStrategyTest.java | 51 ++++ .../plugins/camerax/ImageAnalysisTest.java | 22 +- .../plugins/camerax/ImageCaptureTest.java | 22 +- .../flutter/plugins/camerax/PreviewTest.java | 20 +- .../camerax/ResolutionSelectorTest.java | 59 ++++ .../camerax/ResolutionStrategyTest.java | 71 +++++ .../lib/src/android_camera_camerax.dart | 50 +-- .../lib/src/aspect_ratio_strategy.dart | 138 +++++++++ .../lib/src/camerax_library.g.dart | 222 ++++++++++---- .../lib/src/image_analysis.dart | 20 +- .../lib/src/image_capture.dart | 23 +- .../lib/src/preview.dart | 25 +- .../lib/src/resolution_selector.dart | 108 +++++++ .../lib/src/resolution_strategy.dart | 189 ++++++++++++ .../pigeons/camerax_library.dart | 25 +- .../camera_android_camerax/pubspec.yaml | 2 +- .../test/android_camera_camerax_test.dart | 7 +- .../test/aspect_ratio_strategy_test.dart | 84 ++++++ .../aspect_ratio_strategy_test.mocks.dart | 68 +++++ .../test/image_analysis_test.dart | 50 +-- .../test/image_analysis_test.mocks.dart | 17 +- .../test/image_capture_test.dart | 37 ++- .../test/image_capture_test.mocks.dart | 25 +- .../test/preview_test.dart | 33 +- .../test/preview_test.mocks.dart | 16 +- .../test/resolution_selector_test.dart | 124 ++++++++ .../test/resolution_selector_test.mocks.dart | 103 +++++++ .../test/resolution_strategy_test.dart | 109 +++++++ .../test/resolution_strategy_test.mocks.dart | 69 +++++ .../test/test_camerax_library.g.dart | 173 ++++++++--- 40 files changed, 2159 insertions(+), 370 deletions(-) create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AspectRatioStrategyTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionStrategyTest.java create mode 100644 packages/camera/camera_android_camerax/lib/src/aspect_ratio_strategy.dart create mode 100644 packages/camera/camera_android_camerax/lib/src/resolution_selector.dart create mode 100644 packages/camera/camera_android_camerax/lib/src/resolution_strategy.dart create mode 100644 packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.dart create mode 100644 packages/camera/camera_android_camerax/test/aspect_ratio_strategy_test.mocks.dart create mode 100644 packages/camera/camera_android_camerax/test/resolution_selector_test.dart create mode 100644 packages/camera/camera_android_camerax/test/resolution_selector_test.mocks.dart create mode 100644 packages/camera/camera_android_camerax/test/resolution_strategy_test.dart create mode 100644 packages/camera/camera_android_camerax/test/resolution_strategy_test.mocks.dart diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ab60e8aba8..1cddca1cbb 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 f10f133360..5530a3bb10 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 0000000000..91e0def66e --- /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 dd8ab51465..dbae2a4680 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 final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity 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 c6f6279937..680ecb8052 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 @@ public class GeneratedCameraXLibrary { } } + /** + * 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 @@ public class GeneratedCameraXLibrary { 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 @@ public class GeneratedCameraXLibrary { 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 @@ public class GeneratedCameraXLibrary { 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 @@ public class GeneratedCameraXLibrary { 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 class GeneratedCameraXLibrary { 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 @@ public class GeneratedCameraXLibrary { /** 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 @@ public class GeneratedCameraXLibrary { 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 class GeneratedCameraXLibrary { } } + 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 class GeneratedCameraXLibrary { 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 class GeneratedCameraXLibrary { /** 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 @@ public class GeneratedCameraXLibrary { 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 5e786176d5..58491477ae 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.NonNull; 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 class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi { /** 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 f2e71aa3ee..88ec2debb7 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.Nullable; 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 class ImageCaptureHostApiImpl implements ImageCaptureHostApi { */ @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 6f93fd0f87..07b581ebf9 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.Nullable; 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 class PreviewHostApiImpl implements PreviewHostApi { /** 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 0000000000..be05e1bb8b --- /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 0000000000..c110c40446 --- /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 0000000000..02e757dc39 --- /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 38f77761da..b9a6d299ae 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.verify; 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 class ImageAnalysisTest { 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 881b6bea5a..df6c8ee74b 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.verify; 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.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.MockedStatic; import org.mockito.junit.MockitoJUnit; @@ -66,26 +64,18 @@ public class ImageCaptureTest { 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 39b73abd73..37a7c77041 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.util.Size; 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 class PreviewTest { 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 0000000000..f323e4706c --- /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 0000000000..7bbc6152da --- /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 9a58e6f4df..7c5bf6b37e 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 0000000000..621f4ddb2e --- /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 2a1b2813f1..2d1dccf971 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,29 +1516,6 @@ abstract class RecordingFlutterApi { } } -class _ImageCaptureHostApiCodec extends StandardMessageCodec { - const _ImageCaptureHostApiCodec(); - @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 ImageCaptureHostApi { /// Constructor for [ImageCaptureHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -1536,15 +1524,15 @@ class ImageCaptureHostApi { : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _ImageCaptureHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier, int? arg_flashMode, - ResolutionInfo? arg_targetResolution) async { + 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_targetResolution]) + [arg_identifier, arg_flashMode, arg_resolutionSelectorId]) as List?; if (replyList == null) { throw PlatformException( @@ -1612,6 +1600,138 @@ class ImageCaptureHostApi { } } +class _ResolutionStrategyHostApiCodec extends StandardMessageCodec { + const _ResolutionStrategyHostApiCodec(); + @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 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. + ResolutionStrategyHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _ResolutionStrategyHostApiCodec(); + + Future create(int arg_identifier, ResolutionInfo? arg_boundSize, + int? arg_fallbackRule) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ResolutionStrategyHostApi.create', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_identifier, arg_boundSize, arg_fallbackRule]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +class 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.ResolutionSelectorHostApi.create', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send([ + arg_identifier, + arg_resolutionStrategyIdentifier, + arg_aspectRatioStrategyIdentifier + ]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +class 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.AspectRatioStrategyHostApi.create', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send([ + arg_identifier, + arg_preferredAspectRatio, + arg_fallbackRule + ]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + class _CameraStateFlutterApiCodec extends StandardMessageCodec { const _CameraStateFlutterApiCodec(); @override @@ -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 4d2a335a3c..e28457c6d1 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 f7f45d15ff..76fd9c4ae5 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 a5307463a5..f0568078be 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 0000000000..74191724e5 --- /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 0000000000..80669f3f8d --- /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 601601dbf3..a22d7f6e35 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 0fea9fc682..f8927d0311 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 bdebdce095..a0e60b60be 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 0000000000..28ec032a2a --- /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 0000000000..28dda66896 --- /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 d9c657ea97..80acd65598 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 cbece720c1..b0df5790b2 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 21c071965d..c50314ed3c 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 1b9e1e777e..44331c92cf 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 6eacd42b5c..29f862cda4 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 ae3783641c..d56e682ac6 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 0000000000..45ecef576a --- /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 0000000000..6ab8de8316 --- /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 0000000000..9c098b9460 --- /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 0000000000..1a667389ba --- /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 2c4afd19b2..6a80cf0541 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 []; }); }