diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index a7f96245153e..1f01b049ea61 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -18,3 +18,4 @@ * Implements image capture. * Fixes cast of CameraInfo to fix integration test failure. * Updates internal Java InstanceManager to only stop finalization callbacks when stopped. +* Implements image streaming. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerFlutterApiImpl.java new file mode 100644 index 000000000000..7bdb8626469a --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerFlutterApiImpl.java @@ -0,0 +1,74 @@ +// 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.ImageAnalysis; +import androidx.camera.core.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.AnalyzerFlutterApi; +import java.util.Objects; + +/** + * Flutter API implementation for {@link ImageAnalysis.Analyzer}. + * + *

This class may handle adding native instances that are attached to a Dart instance or passing + * arguments of callbacks methods to a Dart instance. + */ +public class AnalyzerFlutterApiImpl { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private AnalyzerFlutterApi api; + + /** + * Constructs a {@link AnalyzerFlutterApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public AnalyzerFlutterApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + api = new AnalyzerFlutterApi(binaryMessenger); + } + + /** + * Stores the {@link ImageAnalysis.Analyzer} instance and notifies Dart to create and store a new + * {@code Analyzer} instance that is attached to this one. If {@code instance} has already been + * added, this method does nothing. + */ + public void create( + @NonNull ImageAnalysis.Analyzer instance, @NonNull AnalyzerFlutterApi.Reply callback) { + if (!instanceManager.containsInstance(instance)) { + api.create(instanceManager.addHostCreatedInstance(instance), callback); + } + } + + /** + * Sends a message to Dart to call {@code Analyzer.analyze} on the Dart object representing + * `instance`. + */ + public void analyze( + @NonNull ImageAnalysis.Analyzer analyzerInstance, + @NonNull ImageProxy imageProxyInstance, + @NonNull AnalyzerFlutterApi.Reply callback) { + api.analyze( + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(analyzerInstance)), + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(imageProxyInstance)), + callback); + } + + /** + * Sets the Flutter API used to send messages to Dart. + * + *

This is only visible for testing. + */ + @VisibleForTesting + void setApi(@NonNull AnalyzerFlutterApi api) { + this.api = api; + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java new file mode 100644 index 000000000000..6f6f7552ad41 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerHostApiImpl.java @@ -0,0 +1,119 @@ +// 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.ImageAnalysis; +import androidx.camera.core.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.AnalyzerHostApi; + +/** + * Host API implementation for {@link ImageAnalysis.Analyzer}. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class AnalyzerHostApiImpl implements AnalyzerHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private final AnalyzerProxy proxy; + + /** Proxy for constructors and static method of {@link ImageAnalysis.Analyzer}. */ + @VisibleForTesting + public static class AnalyzerProxy { + + /** Creates an instance of {@link AnalyzerImpl}. */ + @NonNull + public AnalyzerImpl create( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + return new AnalyzerImpl(binaryMessenger, instanceManager); + } + } + + /** + * Implementation of {@link ImageAnalysis.Analyzer} that passes arguments of callback methods to + * Dart. + */ + public static class AnalyzerImpl implements ImageAnalysis.Analyzer { + private BinaryMessenger binaryMessenger; + private InstanceManager instanceManager; + private AnalyzerFlutterApiImpl api; + + @VisibleForTesting @NonNull public ImageProxyFlutterApiImpl imageProxyApi; + + /** + * Constructs an instance of {@link ImageAnalysis.Analyzer} that passes arguments of callbacks + * methods to Dart. + */ + public AnalyzerImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + super(); + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + api = new AnalyzerFlutterApiImpl(binaryMessenger, instanceManager); + imageProxyApi = new ImageProxyFlutterApiImpl(binaryMessenger, instanceManager); + } + + @Override + public void analyze(@NonNull ImageProxy imageProxy) { + Long imageFormat = Long.valueOf(imageProxy.getFormat()); + Long imageHeight = Long.valueOf(imageProxy.getHeight()); + Long imageWidth = Long.valueOf(imageProxy.getWidth()); + imageProxyApi.create(imageProxy, imageFormat, imageHeight, imageWidth, reply -> {}); + + api.analyze(this, imageProxy, reply -> {}); + } + + /** + * Flutter API used to send messages back to Dart. + * + *

This is only visible for testing. + */ + @VisibleForTesting + void setApi(@NonNull AnalyzerFlutterApiImpl api) { + this.api = api; + } + } + + /** + * Constructs a {@link AnalyzerHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public AnalyzerHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this(binaryMessenger, instanceManager, new AnalyzerProxy()); + } + + /** + * Constructs a {@link AnalyzerHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructors and static method of {@link ImageAnalysis.Analyzer} + */ + @VisibleForTesting + AnalyzerHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull AnalyzerProxy proxy) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + this.proxy = proxy; + } + + /** + * Creates an {@link AnalyzerProxy} that represents an {@link ImageAnalysis.Analyzer} instance + * with the specified identifier. + */ + @Override + public void create(@NonNull Long identifier) { + instanceManager.addDartCreatedInstance( + proxy.create(binaryMessenger, instanceManager), 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 d0176a1812c8..8d111a003e12 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 @@ -18,6 +18,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity private InstanceManager instanceManager; private FlutterPluginBinding pluginBinding; private ProcessCameraProviderHostApiImpl processCameraProviderHostApi; + private ImageAnalysisHostApiImpl imageAnalysisHostApiImpl; private ImageCaptureHostApiImpl imageCaptureHostApi; public SystemServicesHostApiImpl systemServicesHostApi; @@ -56,6 +57,12 @@ void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry tex binaryMessenger, new PreviewHostApiImpl(binaryMessenger, instanceManager, textureRegistry)); imageCaptureHostApi = new ImageCaptureHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.ImageCaptureHostApi.setup(binaryMessenger, imageCaptureHostApi); + imageAnalysisHostApiImpl = new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager); + GeneratedCameraXLibrary.ImageAnalysisHostApi.setup(binaryMessenger, imageAnalysisHostApiImpl); + GeneratedCameraXLibrary.AnalyzerHostApi.setup( + binaryMessenger, new AnalyzerHostApiImpl(binaryMessenger, instanceManager)); + GeneratedCameraXLibrary.ImageProxyHostApi.setup( + binaryMessenger, new ImageProxyHostApiImpl(binaryMessenger, instanceManager)); } @Override @@ -113,5 +120,8 @@ public void updateContext(Context context) { if (imageCaptureHostApi != null) { processCameraProviderHostApi.setContext(context); } + if (imageAnalysisHostApiImpl != null) { + imageAnalysisHostApiImpl.setContext(context); + } } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java index d6a6a60cc1e1..da025b11dac2 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java @@ -6,16 +6,27 @@ import android.app.Activity; import android.graphics.SurfaceTexture; +import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; import androidx.camera.core.CameraSelector; +import androidx.camera.core.ImageAnalysis; import androidx.camera.core.ImageCapture; import androidx.camera.core.Preview; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; import java.io.File; /** Utility class used to create CameraX-related objects primarily for testing purposes. */ public class CameraXProxy { + /** + * Converts a {@link ResolutionInfo} instance to a {@link Size} for setting the target resolution + * of {@link UseCase}s. + */ + public static Size sizeFromResolution(@NonNull ResolutionInfo resolutionInfo) { + return new Size(resolutionInfo.getWidth().intValue(), resolutionInfo.getHeight().intValue()); + } + public CameraSelector.Builder createCameraSelectorBuilder() { return new CameraSelector.Builder(); } @@ -58,7 +69,20 @@ public ImageCapture.Builder createImageCaptureBuilder() { /** * Creates an {@link ImageCapture.OutputFileOptions} to configure where to save a captured image. */ + @NonNull public ImageCapture.OutputFileOptions createImageCaptureOutputFileOptions(@NonNull File file) { return new ImageCapture.OutputFileOptions.Builder(file).build(); } + + /** Creates an instance of {@link ImageAnalysis.Builder}. */ + @NonNull + public ImageAnalysis.Builder createImageAnalysisBuilder() { + return new ImageAnalysis.Builder(); + } + + /** Creates an array of {@code byte}s with the size provided. */ + @NonNull + public byte[] getBytesFromBuffer(int size) { + return new byte[size]; + } } 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 4d4439ebe23a..31cea4add1f1 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v9.2.4), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.camerax; @@ -41,7 +41,7 @@ public FlutterError(@NonNull String code, @Nullable String message, @Nullable Ob } @NonNull - private static ArrayList wrapError(@NonNull Throwable exception) { + protected static ArrayList wrapError(@NonNull Throwable exception) { ArrayList errorList = new ArrayList(3); if (exception instanceof FlutterError) { FlutterError error = (FlutterError) exception; @@ -85,8 +85,8 @@ public void setHeight(@NonNull Long setterArg) { this.height = setterArg; } - /** Constructor is private to enforce null safety; use Builder. */ - private ResolutionInfo() {} + /** Constructor is non-public to enforce null safety; use Builder. */ + ResolutionInfo() {} public static final class Builder { @@ -162,8 +162,8 @@ public void setDescription(@NonNull String setterArg) { this.description = setterArg; } - /** Constructor is private to enforce null safety; use Builder. */ - private CameraPermissionsErrorData() {} + /** Constructor is non-public to enforce null safety; use Builder. */ + CameraPermissionsErrorData() {} public static final class Builder { @@ -208,9 +208,10 @@ ArrayList toList() { } public interface Result { + @SuppressWarnings("UnknownNullness") void success(T result); - void error(Throwable error); + void error(@NonNull Throwable error); } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface InstanceManagerHostApi { @@ -222,14 +223,15 @@ public interface InstanceManagerHostApi { void clear(); /** The codec used by InstanceManagerHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `InstanceManagerHostApi` to handle messages through the * `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, InstanceManagerHostApi api) { + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable InstanceManagerHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -259,13 +261,13 @@ public interface JavaObjectHostApi { void dispose(@NonNull Long identifier); /** The codec used by JavaObjectHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `JavaObjectHostApi` to handle messages through the `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable JavaObjectHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -293,22 +295,23 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class JavaObjectFlutterApi { - private final BinaryMessenger binaryMessenger; + private final @NonNull BinaryMessenger binaryMessenger; - public JavaObjectFlutterApi(BinaryMessenger argBinaryMessenger) { + public JavaObjectFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") public interface Reply { void reply(T reply); } /** The codec used by JavaObjectFlutterApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } - public void dispose(@NonNull Long identifierArg, Reply callback) { + public void dispose(@NonNull Long identifierArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaObjectFlutterApi.dispose", getCodec()); @@ -324,13 +327,13 @@ public interface CameraInfoHostApi { Long getSensorRotationDegrees(@NonNull Long identifier); /** The codec used by CameraInfoHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `CameraInfoHostApi` to handle messages through the `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, CameraInfoHostApi api) { + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable CameraInfoHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -362,22 +365,23 @@ static void setup(BinaryMessenger binaryMessenger, CameraInfoHostApi api) { } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class CameraInfoFlutterApi { - private final BinaryMessenger binaryMessenger; + private final @NonNull BinaryMessenger binaryMessenger; - public CameraInfoFlutterApi(BinaryMessenger argBinaryMessenger) { + public CameraInfoFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") public interface Reply { void reply(T reply); } /** The codec used by CameraInfoFlutterApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } - public void create(@NonNull Long identifierArg, Reply callback) { + public void create(@NonNull Long identifierArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraInfoFlutterApi.create", getCodec()); @@ -395,14 +399,15 @@ public interface CameraSelectorHostApi { List filter(@NonNull Long identifier, @NonNull List cameraInfoIds); /** The codec used by CameraSelectorHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `CameraSelectorHostApi` to handle messages through the * `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, CameraSelectorHostApi api) { + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable CameraSelectorHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -460,23 +465,24 @@ static void setup(BinaryMessenger binaryMessenger, CameraSelectorHostApi api) { } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class CameraSelectorFlutterApi { - private final BinaryMessenger binaryMessenger; + private final @NonNull BinaryMessenger binaryMessenger; - public CameraSelectorFlutterApi(BinaryMessenger argBinaryMessenger) { + public CameraSelectorFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") public interface Reply { void reply(T reply); } /** The codec used by CameraSelectorFlutterApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } public void create( - @NonNull Long identifierArg, @Nullable Long lensFacingArg, Reply callback) { + @NonNull Long identifierArg, @Nullable Long lensFacingArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraSelectorFlutterApi.create", getCodec()); @@ -488,7 +494,7 @@ public void create( /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface ProcessCameraProviderHostApi { - void getInstance(Result result); + void getInstance(@NonNull Result result); @NonNull List getAvailableCameraInfos(@NonNull Long identifier); @@ -507,14 +513,15 @@ Long bindToLifecycle( void unbindAll(@NonNull Long identifier); /** The codec used by ProcessCameraProviderHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `ProcessCameraProviderHostApi` to handle messages through the * `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, ProcessCameraProviderHostApi api) { + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable ProcessCameraProviderHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -689,22 +696,23 @@ public void error(Throwable error) { } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class ProcessCameraProviderFlutterApi { - private final BinaryMessenger binaryMessenger; + private final @NonNull BinaryMessenger binaryMessenger; - public ProcessCameraProviderFlutterApi(BinaryMessenger argBinaryMessenger) { + public ProcessCameraProviderFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") public interface Reply { void reply(T reply); } /** The codec used by ProcessCameraProviderFlutterApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } - public void create(@NonNull Long identifierArg, Reply callback) { + public void create(@NonNull Long identifierArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, @@ -717,22 +725,23 @@ public void create(@NonNull Long identifierArg, Reply callback) { } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class CameraFlutterApi { - private final BinaryMessenger binaryMessenger; + private final @NonNull BinaryMessenger binaryMessenger; - public CameraFlutterApi(BinaryMessenger argBinaryMessenger) { + public CameraFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") public interface Reply { void reply(T reply); } /** The codec used by CameraFlutterApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } - public void create(@NonNull Long identifierArg, Reply callback) { + public void create(@NonNull Long identifierArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraFlutterApi.create", getCodec()); @@ -772,7 +781,7 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { public interface SystemServicesHostApi { void requestCameraPermissions( - @NonNull Boolean enableAudio, Result result); + @NonNull Boolean enableAudio, @NonNull Result result); void startListeningForDeviceOrientationChange( @NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation); @@ -780,14 +789,15 @@ void startListeningForDeviceOrientationChange( void stopListeningForDeviceOrientationChange(); /** The codec used by SystemServicesHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return SystemServicesHostApiCodec.INSTANCE; } /** * Sets up an instance of `SystemServicesHostApi` to handle messages through the * `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, SystemServicesHostApi api) { + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable SystemServicesHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -874,22 +884,24 @@ public void error(Throwable error) { } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class SystemServicesFlutterApi { - private final BinaryMessenger binaryMessenger; + private final @NonNull BinaryMessenger binaryMessenger; - public SystemServicesFlutterApi(BinaryMessenger argBinaryMessenger) { + public SystemServicesFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") public interface Reply { void reply(T reply); } /** The codec used by SystemServicesFlutterApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); } - public void onDeviceOrientationChanged(@NonNull String orientationArg, Reply callback) { + public void onDeviceOrientationChanged( + @NonNull String orientationArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, @@ -900,7 +912,7 @@ public void onDeviceOrientationChanged(@NonNull String orientationArg, Reply callback.reply(null)); } - public void onCameraError(@NonNull String errorDescriptionArg, Reply callback) { + public void onCameraError(@NonNull String errorDescriptionArg, @NonNull Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, @@ -960,11 +972,11 @@ void create( ResolutionInfo getResolutionInfo(@NonNull Long identifier); /** The codec used by PreviewHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return PreviewHostApiCodec.INSTANCE; } /** Sets up an instance of `PreviewHostApi` to handle messages through the `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, PreviewHostApi api) { + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable PreviewHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -1107,17 +1119,17 @@ void create( void setFlashMode(@NonNull Long identifier, @NonNull Long flashMode); - void takePicture(@NonNull Long identifier, Result result); + void takePicture(@NonNull Long identifier, @NonNull Result result); /** The codec used by ImageCaptureHostApi. */ - static MessageCodec getCodec() { + static @NonNull MessageCodec getCodec() { return ImageCaptureHostApiCodec.INSTANCE; } /** * Sets up an instance of `ImageCaptureHostApi` to handle messages through the * `binaryMessenger`. */ - static void setup(BinaryMessenger binaryMessenger, ImageCaptureHostApi api) { + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ImageCaptureHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -1204,4 +1216,336 @@ public void error(Throwable error) { } } } + + 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 setAnalyzer(@NonNull Long identifier, @NonNull Long analyzerIdentifier); + + void clearAnalyzer(@NonNull Long identifier); + + /** The codec used by ImageAnalysisHostApi. */ + static @NonNull MessageCodec getCodec() { + return ImageAnalysisHostApiCodec.INSTANCE; + } + /** + * Sets up an instance of `ImageAnalysisHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable ImageAnalysisHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ImageAnalysisHostApi.create", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + ResolutionInfo targetResolutionIdentifierArg = (ResolutionInfo) args.get(1); + try { + api.create( + (identifierArg == null) ? null : identifierArg.longValue(), + targetResolutionIdentifierArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ImageAnalysisHostApi.setAnalyzer", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + Number analyzerIdentifierArg = (Number) args.get(1); + try { + api.setAnalyzer( + (identifierArg == null) ? null : identifierArg.longValue(), + (analyzerIdentifierArg == null) ? null : analyzerIdentifierArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ImageAnalysisHostApi.clearAnalyzer", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + api.clearAnalyzer((identifierArg == null) ? null : identifierArg.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 AnalyzerHostApi { + + void create(@NonNull Long identifier); + + /** The codec used by AnalyzerHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `AnalyzerHostApi` to handle messages through the `binaryMessenger`. + */ + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable AnalyzerHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.AnalyzerHostApi.create", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + api.create((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class AnalyzerFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public AnalyzerFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by AnalyzerFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void create(@NonNull Long identifierArg, @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.AnalyzerFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Collections.singletonList(identifierArg)), + channelReply -> callback.reply(null)); + } + + public void analyze( + @NonNull Long identifierArg, + @NonNull Long imageProxyIdentifierArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.AnalyzerFlutterApi.analyze", getCodec()); + channel.send( + new ArrayList(Arrays.asList(identifierArg, imageProxyIdentifierArg)), + channelReply -> callback.reply(null)); + } + } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface ImageProxyHostApi { + + @NonNull + List getPlanes(@NonNull Long identifier); + + void close(@NonNull Long identifier); + + /** The codec used by ImageProxyHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `ImageProxyHostApi` to handle messages through the `binaryMessenger`. + */ + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ImageProxyHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ImageProxyHostApi.getPlanes", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + List output = + api.getPlanes((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ImageProxyHostApi.close", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + api.close((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class ImageProxyFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public ImageProxyFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by ImageProxyFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void create( + @NonNull Long identifierArg, + @NonNull Long formatArg, + @NonNull Long heightArg, + @NonNull Long widthArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ImageProxyFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Arrays.asList(identifierArg, formatArg, heightArg, widthArg)), + channelReply -> callback.reply(null)); + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class PlaneProxyFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public PlaneProxyFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by PlaneProxyFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void create( + @NonNull Long identifierArg, + @NonNull byte[] bufferArg, + @NonNull Long pixelStrideArg, + @NonNull Long rowStrideArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.PlaneProxyFlutterApi.create", getCodec()); + channel.send( + new ArrayList( + Arrays.asList(identifierArg, bufferArg, pixelStrideArg, rowStrideArg)), + channelReply -> callback.reply(null)); + } + } } 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 new file mode 100644 index 000000000000..5e786176d50c --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java @@ -0,0 +1,79 @@ +// 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.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.camera.core.ImageAnalysis; +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 { + + private InstanceManager instanceManager; + private BinaryMessenger binaryMessenger; + private Context context; + + @VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy(); + + public ImageAnalysisHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + } + + /** + * Sets the context that will be used to run an {@link ImageAnalysis.Analyzer} on the main thread. + */ + public void setContext(@NonNull Context context) { + this.context = context; + } + + /** Creates an {@link ImageAnalysis} instance with the target resolution if specified. */ + @Override + public void create(@NonNull Long identifier, @Nullable ResolutionInfo targetResolution) { + ImageAnalysis.Builder imageAnalysisBuilder = cameraXProxy.createImageAnalysisBuilder(); + + if (targetResolution != null) { + imageAnalysisBuilder.setTargetResolution(CameraXProxy.sizeFromResolution(targetResolution)); + } + + ImageAnalysis imageAnalysis = imageAnalysisBuilder.build(); + instanceManager.addDartCreatedInstance(imageAnalysis, identifier); + } + + /** + * Sets {@link ImageAnalysis.Analyzer} instance with specified {@code analyzerIdentifier} on the + * {@link ImageAnalysis} instance with the specified {@code identifier} to receive and analyze + * images. + */ + @Override + public void setAnalyzer(@NonNull Long identifier, @NonNull Long analyzerIdentifier) { + getImageAnalysisInstance(identifier) + .setAnalyzer( + ContextCompat.getMainExecutor(context), + Objects.requireNonNull(instanceManager.getInstance(analyzerIdentifier))); + } + + /** Clears any analyzer previously set on the specified {@link ImageAnalysis} instance. */ + @Override + public void clearAnalyzer(@NonNull Long identifier) { + ImageAnalysis imageAnalysis = + (ImageAnalysis) Objects.requireNonNull(instanceManager.getInstance(identifier)); + imageAnalysis.clearAnalyzer(); + } + + /** + * Retrieives the {@link ImageAnalysis} instance associated with the specified {@code identifier}. + */ + private ImageAnalysis getImageAnalysisInstance(@NonNull Long identifier) { + return Objects.requireNonNull(instanceManager.getInstance(identifier)); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java index e93d81da14e9..0c00ed63f2f8 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 @@ -5,7 +5,6 @@ package io.flutter.plugins.camerax; import android.content.Context; -import android.util.Size; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -62,9 +61,7 @@ public void create( imageCaptureBuilder.setFlashMode(flashMode.intValue()); } if (targetResolution != null) { - imageCaptureBuilder.setTargetResolution( - new Size( - targetResolution.getWidth().intValue(), targetResolution.getHeight().intValue())); + imageCaptureBuilder.setTargetResolution(CameraXProxy.sizeFromResolution(targetResolution)); } ImageCapture imageCapture = imageCaptureBuilder.build(); instanceManager.addDartCreatedInstance(imageCapture, identifier); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyFlutterApiImpl.java new file mode 100644 index 000000000000..0ad569973fd8 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyFlutterApiImpl.java @@ -0,0 +1,67 @@ +// 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.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ImageProxyFlutterApi; + +/** + * Flutter API implementation for {@link ImageProxy}. + * + *

This class may handle adding native instances that are attached to a Dart instance or passing + * arguments of callbacks methods to a Dart instance. + */ +public class ImageProxyFlutterApiImpl { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private ImageProxyFlutterApi api; + + /** + * Constructs a {@link ImageProxyFlutterApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public ImageProxyFlutterApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + api = new ImageProxyFlutterApi(binaryMessenger); + } + + /** + * Stores the {@link ImageProxy} instance and notifies Dart to create and store a new {@link + * ImageProxy} instance that is attached to this one. If {@code instance} has already been added, + * this method does nothing. + */ + public void create( + @NonNull ImageProxy instance, + @NonNull Long imageFormat, + @NonNull Long imageHeight, + @NonNull Long imageWidth, + @NonNull ImageProxyFlutterApi.Reply callback) { + if (!instanceManager.containsInstance(instance)) { + api.create( + instanceManager.addHostCreatedInstance(instance), + imageFormat, + imageHeight, + imageWidth, + callback); + } + } + + /** + * Sets the Flutter API used to send messages to Dart. + * + *

This is only visible for testing. + */ + @VisibleForTesting + void setApi(@NonNull ImageProxyFlutterApi api) { + this.api = api; + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyHostApiImpl.java new file mode 100644 index 000000000000..ad34c6ed8d06 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyHostApiImpl.java @@ -0,0 +1,82 @@ +// 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.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ImageProxyHostApi; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Host API implementation for {@link ImageProxy}. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class ImageProxyHostApiImpl implements ImageProxyHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + + @VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy(); + + @VisibleForTesting @NonNull public PlaneProxyFlutterApiImpl planeProxyFlutterApiImpl; + + /** + * Constructs a {@link ImageProxyHostApiImpl}. + * + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public ImageProxyHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + planeProxyFlutterApiImpl = new PlaneProxyFlutterApiImpl(binaryMessenger, instanceManager); + } + + /** + * Returns the array of identifiers for planes of the {@link ImageProxy} instance with the + * specified identifier. + */ + @Override + @NonNull + public List getPlanes(@NonNull Long identifier) { + ImageProxy.PlaneProxy[] planes = getImageProxyInstance(identifier).getPlanes(); + List planeIdentifiers = new ArrayList(); + + for (ImageProxy.PlaneProxy plane : planes) { + ByteBuffer byteBuffer = plane.getBuffer(); + byte[] bytes = cameraXProxy.getBytesFromBuffer(byteBuffer.remaining()); + byteBuffer.get(bytes, 0, bytes.length); + Long pixelStride = Long.valueOf(plane.getPixelStride()); + Long rowStride = Long.valueOf(plane.getRowStride()); + + planeProxyFlutterApiImpl.create(plane, bytes, pixelStride, rowStride, reply -> {}); + planeIdentifiers.add(instanceManager.getIdentifierForStrongReference(plane)); + } + + return planeIdentifiers; + } + + /** + * Closes the {@link androidx.camera.core.Image} instance associated with the {@link ImageProxy} + * instance with the specified identifier. + */ + @Override + public void close(@NonNull Long identifier) { + getImageProxyInstance(identifier).close(); + } + + /** + * Retrieives the {@link ImageProxy} instance associated with the specified {@code identifier}. + */ + private ImageProxy getImageProxyInstance(@NonNull Long identifier) { + return Objects.requireNonNull(instanceManager.getInstance(identifier)); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PlaneProxyFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PlaneProxyFlutterApiImpl.java new file mode 100644 index 000000000000..713e40321bdc --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PlaneProxyFlutterApiImpl.java @@ -0,0 +1,67 @@ +// 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.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.PlaneProxyFlutterApi; + +/** + * Flutter API implementation for {@link ImageProxy.PlaneProxy}. + * + *

This class may handle adding native instances that are attached to a Dart instance or passing + * arguments of callbacks methods to a Dart instance. + */ +public class PlaneProxyFlutterApiImpl { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private PlaneProxyFlutterApi api; + + /** + * Constructs a {@link PlaneProxyFlutterApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public PlaneProxyFlutterApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + api = new PlaneProxyFlutterApi(binaryMessenger); + } + + /** + * Stores the {@link ImageProxy.PlaneProxy} instance and notifies Dart to create and store a new + * {@link ImageProxy.PlaneProxy} instance that is attached to this one. If {@code instance} has + * already been added, this method does nothing. + */ + public void create( + @NonNull ImageProxy.PlaneProxy instance, + @NonNull byte[] bytes, + @NonNull Long pixelStride, + @NonNull Long rowStride, + @NonNull PlaneProxyFlutterApi.Reply callback) { + if (!instanceManager.containsInstance(instance)) { + api.create( + instanceManager.addHostCreatedInstance(instance), + bytes, + pixelStride, + rowStride, + callback); + } + } + + /** + * Sets the Flutter API used to send messages to Dart. + * + *

This is only visible for testing. + */ + @VisibleForTesting + void setApi(@NonNull PlaneProxyFlutterApi api) { + this.api = api; + } +} 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 838f0b3d656c..8491fa25fd42 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 @@ -46,9 +46,7 @@ public void create( previewBuilder.setTargetRotation(rotation.intValue()); } if (targetResolution != null) { - previewBuilder.setTargetResolution( - new Size( - targetResolution.getWidth().intValue(), targetResolution.getHeight().intValue())); + previewBuilder.setTargetResolution(CameraXProxy.sizeFromResolution(targetResolution)); } Preview preview = previewBuilder.build(); instanceManager.addDartCreatedInstance(preview, identifier); diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AnalyzerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AnalyzerTest.java new file mode 100644 index 000000000000..bcc2648e4eb7 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AnalyzerTest.java @@ -0,0 +1,106 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.camera.core.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.AnalyzerFlutterApi; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class AnalyzerTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public AnalyzerHostApiImpl.AnalyzerImpl mockImageAnalysisAnalyzer; + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public AnalyzerFlutterApi mockFlutterApi; + @Mock public AnalyzerHostApiImpl.AnalyzerProxy mockProxy; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void hostApiCreate_makesCallToCreateAnalyzerInstanceWithExpectedIdentifier() { + final AnalyzerHostApiImpl hostApi = + new AnalyzerHostApiImpl(mockBinaryMessenger, instanceManager, mockProxy); + final long instanceIdentifier = 90; + + when(mockProxy.create(mockBinaryMessenger, instanceManager)) + .thenReturn(mockImageAnalysisAnalyzer); + + hostApi.create(instanceIdentifier); + + assertEquals(instanceManager.getInstance(instanceIdentifier), mockImageAnalysisAnalyzer); + } + + @Test + public void flutterApiCreate_makesCallToDartCreate() { + final AnalyzerFlutterApiImpl flutterApi = + new AnalyzerFlutterApiImpl(mockBinaryMessenger, instanceManager); + + flutterApi.setApi(mockFlutterApi); + + flutterApi.create(mockImageAnalysisAnalyzer, reply -> {}); + final long instanceIdentifier = + Objects.requireNonNull( + instanceManager.getIdentifierForStrongReference(mockImageAnalysisAnalyzer)); + + verify(mockFlutterApi).create(eq(instanceIdentifier), any()); + } + + @Test + public void analyze_makesCallToDartAnalyze() { + final AnalyzerFlutterApiImpl flutterApi = + new AnalyzerFlutterApiImpl(mockBinaryMessenger, instanceManager); + final ImageProxy mockImageProxy = mock(ImageProxy.class); + final long mockImageProxyIdentifier = 97; + final AnalyzerHostApiImpl.AnalyzerImpl instance = + new AnalyzerHostApiImpl.AnalyzerImpl(mockBinaryMessenger, instanceManager); + final ImageProxyFlutterApiImpl mockImageProxyApi = + spy(new ImageProxyFlutterApiImpl(mockBinaryMessenger, instanceManager)); + final long instanceIdentifier = 20; + final long format = 3; + final long height = 2; + final long width = 1; + + flutterApi.setApi(mockFlutterApi); + instance.setApi(flutterApi); + instance.imageProxyApi = mockImageProxyApi; + + instanceManager.addDartCreatedInstance(instance, instanceIdentifier); + instanceManager.addDartCreatedInstance(mockImageProxy, mockImageProxyIdentifier); + + when(mockImageProxy.getFormat()).thenReturn(3); + when(mockImageProxy.getHeight()).thenReturn(2); + when(mockImageProxy.getWidth()).thenReturn(1); + + instance.analyze(mockImageProxy); + + verify(mockFlutterApi).analyze(eq(instanceIdentifier), eq(mockImageProxyIdentifier), any()); + verify(mockImageProxyApi).create(eq(mockImageProxy), eq(format), eq(height), eq(width), any()); + } +} 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 new file mode 100644 index 000000000000..38f77761da99 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java @@ -0,0 +1,112 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +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.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; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ImageAnalysisTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public ImageAnalysis mockImageAnalysis; + @Mock public BinaryMessenger mockBinaryMessenger; + + InstanceManager instanceManager; + private Context context; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + context = ApplicationProvider.getApplicationContext(); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdentifier() { + final ImageAnalysisHostApiImpl hostApi = + 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 long instanceIdentifier = 0; + + hostApi.cameraXProxy = mockCameraXProxy; + + final ArgumentCaptor sizeCaptor = ArgumentCaptor.forClass(Size.class); + + when(mockCameraXProxy.createImageAnalysisBuilder()).thenReturn(mockImageAnalysisBuilder); + when(mockImageAnalysisBuilder.build()).thenReturn(mockImageAnalysis); + + hostApi.create(instanceIdentifier, resolutionInfo); + + verify(mockImageAnalysisBuilder).setTargetResolution(sizeCaptor.capture()); + assertEquals(sizeCaptor.getValue().getWidth(), targetResolutionWidth); + assertEquals(sizeCaptor.getValue().getHeight(), targetResolutionHeight); + assertEquals(instanceManager.getInstance(instanceIdentifier), mockImageAnalysis); + } + + @Test + public void setAnalyzer_makesCallToSetAnalyzerOnExpectedImageAnalysisInstance() { + final ImageAnalysisHostApiImpl hostApi = + new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager); + hostApi.setContext(context); + + final ImageAnalysis.Analyzer mockAnalyzer = mock(ImageAnalysis.Analyzer.class); + final long analyzerIdentifier = 10; + final long instanceIdentifier = 94; + + instanceManager.addDartCreatedInstance(mockAnalyzer, analyzerIdentifier); + instanceManager.addDartCreatedInstance(mockImageAnalysis, instanceIdentifier); + + hostApi.setAnalyzer(instanceIdentifier, analyzerIdentifier); + + verify(mockImageAnalysis).setAnalyzer(any(Executor.class), eq(mockAnalyzer)); + } + + @Test + public void clearAnalyzer_makesCallToClearAnalyzerOnExpectedImageAnalysisInstance() { + final ImageAnalysisHostApiImpl hostApi = + new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager); + final long instanceIdentifier = 22; + + instanceManager.addDartCreatedInstance(mockImageAnalysis, instanceIdentifier); + + hostApi.clearAnalyzer(instanceIdentifier); + + verify(mockImageAnalysis).clearAnalyzer(); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageProxyTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageProxyTest.java new file mode 100644 index 000000000000..99cd06a7beae --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageProxyTest.java @@ -0,0 +1,119 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.camera.core.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ImageProxyFlutterApi; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.annotation.Config; + +public class ImageProxyTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public ImageProxy mockImageProxy; + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public ImageProxyFlutterApi mockFlutterApi; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Config(sdk = 21) + @Test + public void getPlanes_returnsExpectedPlanesFromExpectedImageProxyInstance() { + final ImageProxyHostApiImpl hostApi = + new ImageProxyHostApiImpl(mockBinaryMessenger, instanceManager); + final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); + final PlaneProxyFlutterApiImpl mockPlaneProxyFlutterApiImpl = + mock(PlaneProxyFlutterApiImpl.class); + final long instanceIdentifier = 24; + final long mockPlaneProxyIdentifier = 45; + final ImageProxy.PlaneProxy mockPlaneProxy = mock(ImageProxy.PlaneProxy.class); + final ImageProxy.PlaneProxy[] returnValue = new ImageProxy.PlaneProxy[] {mockPlaneProxy}; + final ByteBuffer mockByteBuffer = mock(ByteBuffer.class); + final int bufferRemaining = 23; + final byte[] buffer = new byte[bufferRemaining]; + final int pixelStride = 2; + final int rowStride = 65; + + instanceManager.addDartCreatedInstance(mockImageProxy, instanceIdentifier); + + hostApi.cameraXProxy = mockCameraXProxy; + hostApi.planeProxyFlutterApiImpl = mockPlaneProxyFlutterApiImpl; + + when(mockImageProxy.getPlanes()).thenReturn(returnValue); + when(mockPlaneProxy.getBuffer()).thenReturn(mockByteBuffer); + when(mockByteBuffer.remaining()).thenReturn(bufferRemaining); + when(mockCameraXProxy.getBytesFromBuffer(bufferRemaining)).thenReturn(buffer); + when(mockPlaneProxy.getPixelStride()).thenReturn(pixelStride); + when(mockPlaneProxy.getRowStride()).thenReturn(rowStride); + + final List result = hostApi.getPlanes(instanceIdentifier); + + verify(mockImageProxy).getPlanes(); + verify(mockPlaneProxyFlutterApiImpl) + .create( + eq(mockPlaneProxy), + eq(buffer), + eq(Long.valueOf(pixelStride)), + eq(Long.valueOf(rowStride)), + any()); + assertEquals(result.size(), 1); + } + + @Test + public void close_makesCallToCloseExpectedImageProxyInstance() { + final ImageProxyHostApiImpl hostApi = + new ImageProxyHostApiImpl(mockBinaryMessenger, instanceManager); + final long instanceIdentifier = 9; + + instanceManager.addDartCreatedInstance(mockImageProxy, instanceIdentifier); + + hostApi.close(instanceIdentifier); + + verify(mockImageProxy).close(); + } + + @Test + public void flutterApiCreate_makesCallToDartCreate() { + final ImageProxyFlutterApiImpl flutterApi = + new ImageProxyFlutterApiImpl(mockBinaryMessenger, instanceManager); + final long format = 3; + final long height = 2; + final long width = 1; + + flutterApi.setApi(mockFlutterApi); + + flutterApi.create(mockImageProxy, format, height, width, reply -> {}); + final long instanceIdentifier = + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(mockImageProxy)); + + verify(mockFlutterApi).create(eq(instanceIdentifier), eq(format), eq(height), eq(width), any()); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PlaneProxyTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PlaneProxyTest.java new file mode 100644 index 000000000000..643b63e3f93f --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PlaneProxyTest.java @@ -0,0 +1,58 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import androidx.camera.core.ImageProxy; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.PlaneProxyFlutterApi; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class PlaneProxyTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public ImageProxy.PlaneProxy mockPlaneProxy; + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public PlaneProxyFlutterApi mockFlutterApi; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void flutterApiCreate_makesCallToCreateInstanceWithExpectedIdentifier() { + final PlaneProxyFlutterApiImpl flutterApi = + new PlaneProxyFlutterApiImpl(mockBinaryMessenger, instanceManager); + final byte[] buffer = new byte[23]; + final long pixelStride = 20; + final long rowStride = 2; + + flutterApi.setApi(mockFlutterApi); + + flutterApi.create(mockPlaneProxy, buffer, pixelStride, rowStride, reply -> {}); + final long instanceIdentifier = + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(mockPlaneProxy)); + + verify(mockFlutterApi) + .create(eq(instanceIdentifier), eq(buffer), eq(pixelStride), eq(rowStride), any()); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/analyzer.dart b/packages/camera/camera_android_camerax/lib/src/analyzer.dart new file mode 100644 index 000000000000..fc312cd1982f --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/analyzer.dart @@ -0,0 +1,136 @@ +// 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:async'; + +import 'package:flutter/services.dart' show BinaryMessenger; +import 'package:meta/meta.dart' show protected; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'image_proxy.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// Wrapper of callback for analyzing images. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ImageAnalysis.Analyzer. +class Analyzer extends JavaObject { + /// Creates an [Analyzer]. + Analyzer( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.analyze}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _AnalyzerHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + _api.createfromInstances(this); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + /// Constructs a [Analyzer] that is not automatically attached to a native object. + Analyzer.detached( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.analyze}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _AnalyzerHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + late final _AnalyzerHostApiImpl _api; + + /// Analyzes an image to produce a result. + final Future Function(ImageProxy imageProxy) analyze; +} + +/// Host API implementation of [Analyzer]. +class _AnalyzerHostApiImpl extends AnalyzerHostApi { + _AnalyzerHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + final BinaryMessenger? binaryMessenger; + + final InstanceManager instanceManager; + + /// Creates an [Analyzer] instance on the native side. + Future createfromInstances( + Analyzer instance, + ) { + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (Analyzer original) => Analyzer.detached( + analyze: original.analyze, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + ); + } +} + +/// Flutter API implementation for [Analyzer]. +/// +/// This class may handle instantiating and adding Dart instances that are +/// attached to a native instance or receiving callback methods from an +/// overridden native class. +@protected +class AnalyzerFlutterApiImpl implements AnalyzerFlutterApi { + /// Constructs a [AnalyzerFlutterApiImpl]. + AnalyzerFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create( + int identifier, + ) { + instanceManager.addHostCreatedInstance( + Analyzer.detached( + analyze: (ImageProxy imageProxy) async {}, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + identifier, + onCopy: (Analyzer original) => Analyzer.detached( + analyze: original.analyze, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ); + } + + @override + void analyze( + int identifier, + int imageProxyIdentifier, + ) { + final Analyzer instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + final ImageProxy imageProxy = + instanceManager.getInstanceWithWeakReference(imageProxyIdentifier)!; + instance.analyze( + imageProxy, + ); + } +} 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 adafd74f33f9..7975c9851074 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 @@ -8,11 +8,15 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; +import 'analyzer.dart'; import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camerax_library.g.dart'; +import 'image_analysis.dart'; import 'image_capture.dart'; +import 'image_proxy.dart'; +import 'plane_proxy.dart'; import 'preview.dart'; import 'process_camera_provider.dart'; import 'surface.dart'; @@ -45,6 +49,10 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting ImageCapture? imageCapture; + /// The [ImageAnalysis] instance that can be configured to analyze individual + /// frames. + ImageAnalysis? imageAnalysis; + /// The [CameraSelector] used to configure the [processCameraProvider] to use /// the desired camera. @visibleForTesting @@ -69,6 +77,25 @@ class AndroidCameraCameraX extends CameraPlatform { cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); + /// The controller we need to stream image data. + @visibleForTesting + StreamController? cameraImageDataStreamController; + + /// Conditional used to create detached instances for testing their + /// callback methods. + @visibleForTesting + bool createDetachedCallbacks = false; + + /// Constant representing the multi-plane Android YUV 420 image format. + /// + /// See https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888. + static const int imageFormatYuv420_888 = 35; + + /// Constant representing the compressed JPEG image format. + /// + /// See https://developer.android.com/reference/android/graphics/ImageFormat#JPEG. + static const int imageFormatJpeg = 256; + /// Returns list of all available cameras and their descriptions. @override Future> availableCameras() async { @@ -174,25 +201,26 @@ class AndroidCameraCameraX extends CameraPlatform { /// the CameraX library, this method just retrieves information about the /// camera and sends a [CameraInitializedEvent]. /// - /// [imageFormatGroup] is used to specify the image formatting used. - /// On Android this defaults to ImageFormat.YUV_420_888 and applies only to - /// the image stream. + /// [imageFormatGroup] is used to specify the image format used for image + /// streaming, but CameraX currently only supports YUV_420_888 (supported by + /// Flutter) and RGBA (not supported by Flutter). CameraX uses YUV_420_888 + /// by default, so [imageFormatGroup] is not used. @override Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) async { - // TODO(camsim99): Use imageFormatGroup to configure ImageAnalysis use case - // for image streaming. - // https://github.com/flutter/flutter/issues/120463 - // Configure CameraInitializedEvent to send as representation of a // configured camera: // Retrieve preview resolution. - assert( - preview != null, - 'Preview instance not found. Please call the "createCamera" method before calling "initializeCamera"', - ); + if (preview == null) { + // No camera has been created; createCamera must be called before initializeCamera. + throw CameraException( + 'cameraNotFound', + "Camera not found. Please call the 'create' method before calling 'initialize'", + ); + } + final ResolutionInfo previewResolutionInfo = await preview!.getResolutionInfo(); @@ -252,7 +280,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// [cameraId] not used. @override Future pausePreview(int cameraId) async { - _unbindPreviewFromLifecycle(); + _unbindUseCaseFromLifecycle(preview!); _previewIsPaused = true; } @@ -288,10 +316,6 @@ class AndroidCameraCameraX extends CameraPlatform { /// [cameraId] is not used. @override Future takePicture(int cameraId) async { - assert(processCameraProvider != null); - assert(cameraSelector != null); - assert(imageCapture != null); - // TODO(camsim99): Add support for flash mode configuration. // https://github.com/flutter/flutter/issues/120715 final String picturePath = await imageCapture!.takePicture(); @@ -299,16 +323,32 @@ class AndroidCameraCameraX extends CameraPlatform { return XFile(picturePath); } + /// A new streamed frame is available. + /// + /// Listening to this stream will start streaming, and canceling will stop. + /// To temporarily stop receiving frames, cancel, then listen again later. + /// Pausing/resuming is not supported, as pausing the stream would cause + /// very high memory usage, and will throw an exception due to the + /// implementation using a broadcast [StreamController], which does not + /// support those operations. + /// + /// [cameraId] and [options] are not used. + @override + Stream onStreamedFrameAvailable(int cameraId, + {CameraImageStreamOptions? options}) { + cameraImageDataStreamController = StreamController( + onListen: _onFrameStreamListen, + onCancel: _onFrameStreamCancel, + ); + return cameraImageDataStreamController!.stream; + } + // Methods for binding UseCases to the lifecycle of the camera controlled // by a ProcessCameraProvider instance: /// Binds [preview] instance to the camera lifecycle controlled by the /// [processCameraProvider]. Future _bindPreviewToLifecycle() async { - assert(processCameraProvider != null); - assert(cameraSelector != null); - assert(preview != null); - final bool previewIsBound = await processCameraProvider!.isBound(preview!); if (previewIsBound || _previewIsPaused) { // Only bind if preview is not already bound or intentionally paused. @@ -319,17 +359,95 @@ class AndroidCameraCameraX extends CameraPlatform { .bindToLifecycle(cameraSelector!, [preview!]); } - /// Unbinds [preview] instance to camera lifecycle controlled by the + /// Configures the [imageAnalysis] instance for image streaming and binds it + /// to camera lifecycle controlled by the [processCameraProvider]. + Future _configureAndBindImageAnalysisToLifecycle() async { + if (imageAnalysis != null && + await processCameraProvider!.isBound(imageAnalysis!)) { + // imageAnalysis already configured and bound to lifecycle. + return; + } + + // Create Analyzer that can read image data for image streaming. + Future analyze(ImageProxy imageProxy) async { + final List planes = await imageProxy.getPlanes(); + final List cameraImagePlanes = []; + for (final PlaneProxy plane in planes) { + cameraImagePlanes.add(CameraImagePlane( + bytes: plane.buffer, + bytesPerRow: plane.rowStride, + bytesPerPixel: plane.pixelStride)); + } + + final int format = imageProxy.format; + final CameraImageFormat cameraImageFormat = CameraImageFormat( + _imageFormatGroupFromPlatformData(format), + raw: format); + + final CameraImageData cameraImageData = CameraImageData( + format: cameraImageFormat, + planes: cameraImagePlanes, + height: imageProxy.height, + width: imageProxy.width); + cameraImageDataStreamController?.add(cameraImageData); + imageProxy.close(); + } + + final Analyzer analyzer = createDetachedCallbacks + ? Analyzer.detached(analyze: analyze) + : Analyzer(analyze: analyze); + + // TODO(camsim99): Support resolution configuration. + // Defaults to YUV_420_888 image format. + imageAnalysis = createImageAnalysis(null); + imageAnalysis!.setAnalyzer(analyzer); + + // TODO(camsim99): Reset live camera state observers here when + // https://github.com/flutter/packages/pull/3419 lands. + camera = await processCameraProvider! + .bindToLifecycle(cameraSelector!, [imageAnalysis!]); + } + + /// Unbinds [useCase] from camera lifecycle controlled by the /// [processCameraProvider]. - Future _unbindPreviewFromLifecycle() async { - final bool previewIsBound = await processCameraProvider!.isBound(preview!); - if (preview == null || !previewIsBound) { + Future _unbindUseCaseFromLifecycle(UseCase useCase) async { + final bool useCaseIsBound = await processCameraProvider!.isBound(useCase); + if (!useCaseIsBound) { return; } - assert(processCameraProvider != null); + processCameraProvider!.unbind([useCase]); + } + + // Methods for configuring image streaming: - processCameraProvider!.unbind([preview!]); + /// The [onListen] callback for the stream controller used for image + /// streaming. + void _onFrameStreamListen() { + _configureAndBindImageAnalysisToLifecycle(); + } + + /// The [onCancel] callback for the stream controller used for image + /// streaming. + /// + /// Removes the previously set analyzer on the [imageAnalysis] instance, since + /// image information should no longer be streamed. + FutureOr _onFrameStreamCancel() async { + imageAnalysis!.clearAnalyzer(); + } + + /// Converts between Android ImageFormat constants and [ImageFormatGroup]s. + /// + /// See https://developer.android.com/reference/android/graphics/ImageFormat. + ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { + switch (data) { + case imageFormatYuv420_888: // android.graphics.ImageFormat.YUV_420_888 + return ImageFormatGroup.yuv420; + case imageFormatJpeg: // android.graphics.ImageFormat.JPEG + return ImageFormatGroup.jpeg; + } + + return ImageFormatGroup.unknown; } // Methods for mapping Flutter camera constants to CameraX constants: @@ -427,4 +545,10 @@ class AndroidCameraCameraX extends CameraPlatform { return ImageCapture( targetFlashMode: flashMode, targetResolution: targetResolution); } + + /// Returns an [ImageAnalysis] configured with specified target resolution. + @visibleForTesting + ImageAnalysis createImageAnalysis(ResolutionInfo? targetResolution) { + return ImageAnalysis(targetResolution: targetResolution); + } } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index 0a1b3ce3b285..493071ec852a 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'analyzer.dart'; import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camerax_library.g.dart'; +import 'image_proxy.dart'; import 'java_object.dart'; +import 'plane_proxy.dart'; import 'process_camera_provider.dart'; import 'system_services.dart'; @@ -20,6 +23,9 @@ class AndroidCameraXCameraFlutterApis { CameraSelectorFlutterApiImpl? cameraSelectorFlutterApi, ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApi, SystemServicesFlutterApiImpl? systemServicesFlutterApi, + AnalyzerFlutterApiImpl? analyzerFlutterApiImpl, + ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl, + PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl, }) { this.javaObjectFlutterApi = javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); @@ -32,6 +38,12 @@ class AndroidCameraXCameraFlutterApis { this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl(); this.systemServicesFlutterApi = systemServicesFlutterApi ?? SystemServicesFlutterApiImpl(); + this.analyzerFlutterApiImpl = + analyzerFlutterApiImpl ?? AnalyzerFlutterApiImpl(); + this.imageProxyFlutterApiImpl = + imageProxyFlutterApiImpl ?? ImageProxyFlutterApiImpl(); + this.planeProxyFlutterApiImpl = + planeProxyFlutterApiImpl ?? PlaneProxyFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -61,6 +73,15 @@ class AndroidCameraXCameraFlutterApis { /// Flutter Api for [SystemServices]. late final SystemServicesFlutterApiImpl systemServicesFlutterApi; + /// Flutter Api implementation for [Analyzer]. + late final AnalyzerFlutterApiImpl analyzerFlutterApiImpl; + + /// Flutter Api implementation for [ImageProxy]. + late final ImageProxyFlutterApiImpl imageProxyFlutterApiImpl; + + /// Flutter Api implementation for [PlaneProxy]. + late final PlaneProxyFlutterApiImpl planeProxyFlutterApiImpl; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -70,6 +91,9 @@ class AndroidCameraXCameraFlutterApis { ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi); CameraFlutterApi.setup(cameraFlutterApi); SystemServicesFlutterApi.setup(systemServicesFlutterApi); + AnalyzerFlutterApi.setup(analyzerFlutterApiImpl); + ImageProxyFlutterApi.setup(imageProxyFlutterApiImpl); + PlaneProxyFlutterApi.setup(planeProxyFlutterApiImpl); _haveBeenSetUp = true; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera.dart b/packages/camera/camera_android_camerax/lib/src/camera.dart index 24ff30540b28..51e0813cdd32 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera.dart @@ -23,7 +23,10 @@ class Camera extends JavaObject { /// Flutter API implementation of [Camera]. class CameraFlutterApiImpl implements CameraFlutterApi { - /// Constructs a [CameraSelectorFlutterApiImpl]. + /// Constructs a [CameraFlutterApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. CameraFlutterApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, diff --git a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart index aa7ee88023c1..8300063204c0 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart @@ -99,6 +99,9 @@ class CameraSelector extends JavaObject { /// Host API implementation of [CameraSelector]. class CameraSelectorHostApiImpl extends CameraSelectorHostApi { /// Constructs a [CameraSelectorHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. CameraSelectorHostApiImpl( {this.binaryMessenger, InstanceManager? instanceManager}) : super(binaryMessenger: binaryMessenger) { 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 473839cac796..5e8e95548d38 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v9.2.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import @@ -944,3 +944,330 @@ class ImageCaptureHostApi { } } } + +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 + /// BinaryMessenger will be used which routes to the host platform. + ImageAnalysisHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _ImageAnalysisHostApiCodec(); + + Future create(int arg_identifier, + ResolutionInfo? arg_targetResolutionIdentifier) 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?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future setAnalyzer( + int arg_identifier, int arg_analyzerIdentifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageAnalysisHostApi.setAnalyzer', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier, arg_analyzerIdentifier]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future clearAnalyzer(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageAnalysisHostApi.clearAnalyzer', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +class AnalyzerHostApi { + /// Constructor for [AnalyzerHostApi]. 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. + AnalyzerHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future create(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.AnalyzerHostApi.create', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +abstract class AnalyzerFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier); + + void analyze(int identifier, int imageProxyIdentifier); + + static void setup(AnalyzerFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.AnalyzerFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.AnalyzerFlutterApi.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.AnalyzerFlutterApi.create was null, expected non-null int.'); + api.create(arg_identifier!); + return; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.AnalyzerFlutterApi.analyze', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.AnalyzerFlutterApi.analyze 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.AnalyzerFlutterApi.analyze was null, expected non-null int.'); + final int? arg_imageProxyIdentifier = (args[1] as int?); + assert(arg_imageProxyIdentifier != null, + 'Argument for dev.flutter.pigeon.AnalyzerFlutterApi.analyze was null, expected non-null int.'); + api.analyze(arg_identifier!, arg_imageProxyIdentifier!); + return; + }); + } + } + } +} + +class ImageProxyHostApi { + /// Constructor for [ImageProxyHostApi]. 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. + ImageProxyHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future> getPlanes(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageProxyHostApi.getPlanes', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as List?)!.cast(); + } + } + + Future close(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageProxyHostApi.close', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +abstract class ImageProxyFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier, int format, int height, int width); + + static void setup(ImageProxyFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageProxyFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ImageProxyFlutterApi.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.ImageProxyFlutterApi.create was null, expected non-null int.'); + final int? arg_format = (args[1] as int?); + assert(arg_format != null, + 'Argument for dev.flutter.pigeon.ImageProxyFlutterApi.create was null, expected non-null int.'); + final int? arg_height = (args[2] as int?); + assert(arg_height != null, + 'Argument for dev.flutter.pigeon.ImageProxyFlutterApi.create was null, expected non-null int.'); + final int? arg_width = (args[3] as int?); + assert(arg_width != null, + 'Argument for dev.flutter.pigeon.ImageProxyFlutterApi.create was null, expected non-null int.'); + api.create(arg_identifier!, arg_format!, arg_height!, arg_width!); + return; + }); + } + } + } +} + +abstract class PlaneProxyFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier, Uint8List buffer, int pixelStride, int rowStride); + + static void setup(PlaneProxyFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PlaneProxyFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.PlaneProxyFlutterApi.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.PlaneProxyFlutterApi.create was null, expected non-null int.'); + final Uint8List? arg_buffer = (args[1] as Uint8List?); + assert(arg_buffer != null, + 'Argument for dev.flutter.pigeon.PlaneProxyFlutterApi.create was null, expected non-null Uint8List.'); + final int? arg_pixelStride = (args[2] as int?); + assert(arg_pixelStride != null, + 'Argument for dev.flutter.pigeon.PlaneProxyFlutterApi.create was null, expected non-null int.'); + final int? arg_rowStride = (args[3] as int?); + assert(arg_rowStride != null, + 'Argument for dev.flutter.pigeon.PlaneProxyFlutterApi.create was null, expected non-null int.'); + api.create( + arg_identifier!, arg_buffer!, arg_pixelStride!, arg_rowStride!); + return; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/image_analysis.dart b/packages/camera/camera_android_camerax/lib/src/image_analysis.dart new file mode 100644 index 000000000000..69cc7829c7a3 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/image_analysis.dart @@ -0,0 +1,114 @@ +// 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:async'; + +import 'package:flutter/services.dart' show BinaryMessenger; + +import 'analyzer.dart'; +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; +import 'use_case.dart'; + +/// Use case for providing CPU accessible images for performing image analysis. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ImageAnalysis. +class ImageAnalysis extends UseCase { + /// Creates an [ImageAnalysis]. + ImageAnalysis( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + this.targetResolution}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _ImageAnalysisHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + _api.createfromInstances(this, targetResolution); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + /// Constructs an [ImageAnalysis] that is not automatically attached to a native object. + ImageAnalysis.detached( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + this.targetResolution}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _ImageAnalysisHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + late final _ImageAnalysisHostApiImpl _api; + + /// Target resolution of the camera preview stream. + final ResolutionInfo? targetResolution; + + /// Sets an [Analyzer] to receive and analyze images. + Future setAnalyzer(Analyzer analyzer) => + _api.setAnalyzerfromInstances(this, analyzer); + + /// Removes a previously set [Analyzer]. + Future clearAnalyzer() => _api.clearAnalyzerfromInstances(this); +} + +/// Host API implementation of [ImageAnalysis]. +class _ImageAnalysisHostApiImpl extends ImageAnalysisHostApi { + /// Constructor for [_ImageAnalysisHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. + _ImageAnalysisHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + final BinaryMessenger? binaryMessenger; + + final InstanceManager instanceManager; + + /// Creates an [ImageAnalysis] instance with the specified target resolution + /// on the native side. + Future createfromInstances( + ImageAnalysis instance, + ResolutionInfo? targetResolution, + ) { + return create( + instanceManager.addDartCreatedInstance( + instance, + onCopy: (ImageAnalysis original) => ImageAnalysis.detached( + targetResolution: original.targetResolution, + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + ), + targetResolution, + ); + } + + /// Sets the [analyzer] to receive and analyze images on the [instance]. + Future setAnalyzerfromInstances( + ImageAnalysis instance, + Analyzer analyzer, + ) { + return setAnalyzer( + instanceManager.getIdentifier(instance)!, + instanceManager.getIdentifier(analyzer)!, + ); + } + + /// Removes a previously set analyzer from the [instance]. + Future clearAnalyzerfromInstances( + ImageAnalysis instance, + ) { + return clearAnalyzer( + instanceManager.getIdentifier(instance)!, + ); + } +} 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 6545908cb215..9b80c8706214 100644 --- a/packages/camera/camera_android_camerax/lib/src/image_capture.dart +++ b/packages/camera/camera_android_camerax/lib/src/image_capture.dart @@ -95,6 +95,9 @@ class ImageCapture extends UseCase { /// Host API implementation of [ImageCapture]. class ImageCaptureHostApiImpl extends ImageCaptureHostApi { /// Constructs a [ImageCaptureHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. ImageCaptureHostApiImpl( {this.binaryMessenger, InstanceManager? instanceManager}) { this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager; @@ -109,7 +112,7 @@ class ImageCaptureHostApiImpl extends ImageCaptureHostApi { /// Maintains instances stored to communicate with native language objects. late final InstanceManager instanceManager; - /// Creates a [ImageCapture] instance with the flash mode and target resolution + /// Creates an [ImageCapture] instance with the flash mode and target resolution /// if specified. void createFromInstance(ImageCapture instance, int? targetFlashMode, ResolutionInfo? targetResolution) { diff --git a/packages/camera/camera_android_camerax/lib/src/image_proxy.dart b/packages/camera/camera_android_camerax/lib/src/image_proxy.dart new file mode 100644 index 000000000000..a1f929c5960d --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/image_proxy.dart @@ -0,0 +1,140 @@ +// 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:async'; + +import 'package:flutter/services.dart' show BinaryMessenger; +import 'package:meta/meta.dart' show protected; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; +import 'plane_proxy.dart'; + +/// Representation of a single complete image buffer. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ImageProxy. +class ImageProxy extends JavaObject { + /// Constructs a [ImageProxy] that is not automatically attached to a native object. + ImageProxy.detached( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + required this.format, + required this.height, + required this.width}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _ImageProxyHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + /// The image format. + final int format; + + /// The image height. + final int height; + + /// The image width. + final int width; + + late final _ImageProxyHostApiImpl _api; + + /// Returns the list of color planes of image data. + Future> getPlanes() => _api.getPlanesFromInstances(this); + + /// Closes the underlying image. + Future close() => _api.closeFromInstances(this); +} + +/// Host API implementation of [ImageProxy]. +class _ImageProxyHostApiImpl extends ImageProxyHostApi { + /// Constructor for [_ImageProxyHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. + _ImageProxyHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + final BinaryMessenger? binaryMessenger; + + final InstanceManager instanceManager; + + /// Returns the list of color planes of the image data represnted by the + /// [instance]. + Future> getPlanesFromInstances( + ImageProxy instance, + ) async { + final List planesAsObjects = await getPlanes( + instanceManager.getIdentifier(instance)!, + ); + + return planesAsObjects.map((int? planeIdentifier) { + return instanceManager + .getInstanceWithWeakReference(planeIdentifier!)!; + }).toList(); + } + + /// Closes the underlying image of the [instance]. + Future closeFromInstances( + ImageProxy instance, + ) { + return close( + instanceManager.getIdentifier(instance)!, + ); + } +} + +/// Flutter API implementation for [ImageProxy]. +/// +/// This class may handle instantiating and adding Dart instances that are +/// attached to a native instance or receiving callback methods from an +/// overridden native class. +@protected +class ImageProxyFlutterApiImpl implements ImageProxyFlutterApi { + /// Constructs a [ImageProxyFlutterApiImpl]. + ImageProxyFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create( + int identifier, + int format, + int height, + int width, + ) { + instanceManager.addHostCreatedInstance( + ImageProxy.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + format: format, + height: height, + width: width, + ), + identifier, + onCopy: (ImageProxy original) => ImageProxy.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + format: original.format, + height: original.height, + width: original.width), + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/plane_proxy.dart b/packages/camera/camera_android_camerax/lib/src/plane_proxy.dart new file mode 100644 index 000000000000..c057876d1ede --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/plane_proxy.dart @@ -0,0 +1,91 @@ +// 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:typed_data'; + +import 'package:flutter/services.dart' show BinaryMessenger; +import 'package:meta/meta.dart' show protected; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// A single color plane of image data. +/// +/// See https://developer.android.com/reference/androidx/camera/core/ImageProxy.PlaneProxy. +class PlaneProxy extends JavaObject { + /// Constructs a [PlaneProxy] that is not automatically attached to a native object. + PlaneProxy.detached( + {super.binaryMessenger, + super.instanceManager, + required this.buffer, + required this.pixelStride, + required this.rowStride}) + : super.detached() { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + /// Returns the pixels buffer containing frame data. + final Uint8List buffer; + + /// Returns the pixel stride, the distance between adjacent pixel samples, in + /// bytes. + final int pixelStride; + + /// Returns the row stride, the distance between the start of two consecutive + /// rows of pixels in the image, in bytes. + final int rowStride; +} + +/// Flutter API implementation for [PlaneProxy]. +/// +/// This class may handle instantiating and adding Dart instances that are +/// attached to a native instance or receiving callback methods from an +/// overridden native class. +@protected +class PlaneProxyFlutterApiImpl implements PlaneProxyFlutterApi { + /// Constructs a [PlaneProxyFlutterApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. + PlaneProxyFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create( + int identifier, + Uint8List buffer, + int pixelStride, + int rowStride, + ) { + instanceManager.addHostCreatedInstance( + PlaneProxy.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + buffer: buffer, + pixelStride: pixelStride, + rowStride: rowStride, + ), + identifier, + onCopy: (PlaneProxy original) => PlaneProxy.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + buffer: buffer, + pixelStride: pixelStride, + rowStride: rowStride), + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/preview.dart b/packages/camera/camera_android_camerax/lib/src/preview.dart index d62ab77f20f2..02a540a3b507 100644 --- a/packages/camera/camera_android_camerax/lib/src/preview.dart +++ b/packages/camera/camera_android_camerax/lib/src/preview.dart @@ -71,6 +71,9 @@ class Preview extends UseCase { /// Host API implementation of [Preview]. class PreviewHostApiImpl extends PreviewHostApi { /// Constructs a [PreviewHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. PreviewHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager}) { this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager; } diff --git a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart index d722cf524fae..d8cccdb2aa3e 100644 --- a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart +++ b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart @@ -75,6 +75,9 @@ class ProcessCameraProvider extends JavaObject { /// Host API implementation of [ProcessCameraProvider]. class ProcessCameraProviderHostApiImpl extends ProcessCameraProviderHostApi { /// Creates a [ProcessCameraProviderHostApiImpl]. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an `InstanceManager` is being created. ProcessCameraProviderHostApiImpl( {this.binaryMessenger, InstanceManager? instanceManager}) : super(binaryMessenger: binaryMessenger) { diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index cf580aa396a7..3d492709c900 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -151,3 +151,41 @@ abstract class ImageCaptureHostApi { @async String takePicture(int identifier); } + +@HostApi(dartHostTestHandler: 'TestImageAnalysisHostApi') +abstract class ImageAnalysisHostApi { + void create(int identifier, ResolutionInfo? targetResolutionIdentifier); + + void setAnalyzer(int identifier, int analyzerIdentifier); + + void clearAnalyzer(int identifier); +} + +@HostApi(dartHostTestHandler: 'TestAnalyzerHostApi') +abstract class AnalyzerHostApi { + void create(int identifier); +} + +@FlutterApi() +abstract class AnalyzerFlutterApi { + void create(int identifier); + + void analyze(int identifier, int imageProxyIdentifier); +} + +@HostApi(dartHostTestHandler: 'TestImageProxyHostApi') +abstract class ImageProxyHostApi { + List getPlanes(int identifier); + + void close(int identifier); +} + +@FlutterApi() +abstract class ImageProxyFlutterApi { + void create(int identifier, int format, int height, int width); +} + +@FlutterApi() +abstract class PlaneProxyFlutterApi { + void create(int identifier, Uint8List buffer, int pixelStride, int rowStride); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 35399a356ab3..f4630a7ab26f 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -23,11 +23,12 @@ dependencies: sdk: flutter integration_test: sdk: flutter + meta: ^1.7.0 stream_transform: ^2.1.0 dev_dependencies: async: ^2.5.0 - build_runner: ^2.1.4 + build_runner: ^2.2.0 flutter_test: sdk: flutter mockito: 5.4.0 diff --git a/packages/camera/camera_android_camerax/test/analyzer_test.dart b/packages/camera/camera_android_camerax/test/analyzer_test.dart new file mode 100644 index 000000000000..1888e4c9b8f8 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/analyzer_test.dart @@ -0,0 +1,115 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/analyzer.dart'; +import 'package:camera_android_camerax/src/image_proxy.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 'analyzer_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestAnalyzerHostApi, TestInstanceManagerHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Analyzer', () { + setUp(() {}); + + tearDown(() { + TestAnalyzerHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test('HostApi create', () { + final MockTestAnalyzerHostApi mockApi = MockTestAnalyzerHostApi(); + TestAnalyzerHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final Analyzer instance = Analyzer( + analyze: (ImageProxy imageProxy) async {}, + instanceManager: instanceManager, + ); + + verify(mockApi.create( + instanceManager.getIdentifier(instance), + )); + }); + + test('FlutterAPI create', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final AnalyzerFlutterApiImpl api = AnalyzerFlutterApiImpl( + instanceManager: instanceManager, + ); + + const int instanceIdentifier = 0; + + api.create( + instanceIdentifier, + ); + + expect( + instanceManager.getInstanceWithWeakReference(instanceIdentifier), + isA(), + ); + }); + + test('analyze', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const int instanceIdentifier = 0; + const int imageProxyIdentifier = 44; + late final Object callbackParameter; + final Analyzer instance = Analyzer.detached( + analyze: ( + ImageProxy imageProxy, + ) async { + callbackParameter = imageProxy; + }, + instanceManager: instanceManager, + ); + instanceManager.addHostCreatedInstance( + instance, + instanceIdentifier, + onCopy: (Analyzer original) => Analyzer.detached( + analyze: original.analyze, + instanceManager: instanceManager, + ), + ); + final ImageProxy imageProxy = ImageProxy.detached( + instanceManager: instanceManager, format: 3, height: 4, width: 5); + instanceManager.addHostCreatedInstance(imageProxy, imageProxyIdentifier, + onCopy: (ImageProxy original) => ImageProxy.detached( + instanceManager: instanceManager, + format: original.format, + height: original.height, + width: original.width)); + + final AnalyzerFlutterApiImpl flutterApi = AnalyzerFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.analyze( + instanceIdentifier, + imageProxyIdentifier, + ); + + expect( + callbackParameter, + imageProxy, + ); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/analyzer_test.mocks.dart b/packages/camera/camera_android_camerax/test/analyzer_test.mocks.dart new file mode 100644 index 000000000000..a08d2796cff7 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/analyzer_test.mocks.dart @@ -0,0 +1,57 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in camera_android_camerax/test/analyzer_test.dart. +// Do not manually edit this file. + +// 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 [TestAnalyzerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestAnalyzerHostApi extends _i1.Mock + implements _i2.TestAnalyzerHostApi { + MockTestAnalyzerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? identifier) => super.noSuchMethod( + Invocation.method( + #create, + [identifier], + ), + 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/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 640c1ed2d248..2db12ebde63b 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 @@ -6,40 +6,53 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:camera_android_camerax/camera_android_camerax.dart'; +import 'package:camera_android_camerax/src/analyzer.dart'; import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/image_analysis.dart'; import 'package:camera_android_camerax/src/image_capture.dart'; +import 'package:camera_android_camerax/src/image_proxy.dart'; +import 'package:camera_android_camerax/src/plane_proxy.dart'; import 'package:camera_android_camerax/src/preview.dart'; import 'package:camera_android_camerax/src/process_camera_provider.dart'; import 'package:camera_android_camerax/src/system_services.dart'; import 'package:camera_android_camerax/src/use_case.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/services.dart' show DeviceOrientation; +import 'package:flutter/services.dart' show DeviceOrientation, Uint8List; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'android_camera_camerax_test.mocks.dart'; +import 'test_camerax_library.g.dart'; @GenerateNiceMocks(>[ MockSpec(), MockSpec(), + MockSpec(), MockSpec(), + MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) @GenerateMocks([BuildContext]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + test('Should fetch CameraDescription instances for available cameras', () async { // Arrange - final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); final List returnData = [ { @@ -99,7 +112,7 @@ void main() { test( 'createCamera requests permissions, starts listening for device orientation changes, and returns flutter surface texture ID', () async { - final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; @@ -139,7 +152,7 @@ void main() { test( 'createCamera binds Preview and ImageCapture use cases to ProcessCameraProvider instance', () async { - final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; @@ -158,14 +171,14 @@ void main() { }); test( - 'initializeCamera throws AssertionError when createCamera has not been called before initializedCamera', + 'initializeCamera throws a CameraException when createCamera has not been called before initializedCamera', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); - expect(() => camera.initializeCamera(3), throwsAssertionError); + expect(() => camera.initializeCamera(3), throwsA(isA())); }); test('initializeCamera sends expected CameraInitializedEvent', () async { - final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); const int cameraId = 10; const CameraLensDirection testLensDirection = CameraLensDirection.back; @@ -405,17 +418,131 @@ void main() { expect(imageFile.path, equals(testPicturePath)); }); + + test( + 'onStreamedFrameAvailable emits CameraImageData when picked up from CameraImageData stream controller', + () async { + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); + const int cameraId = 22; + camera.processCameraProvider = MockProcessCameraProvider(); + camera.cameraSelector = MockCameraSelector(); + camera.createDetachedCallbacks = true; + + final CameraImageData mockCameraImageData = MockCameraImageData(); + final Stream imageStream = + camera.onStreamedFrameAvailable(cameraId); + final StreamQueue streamQueue = + StreamQueue(imageStream); + + camera.cameraImageDataStreamController!.add(mockCameraImageData); + + expect(await streamQueue.next, equals(mockCameraImageData)); + await streamQueue.cancel(); + }); + + test( + 'onStreamedFrameAvaiable returns stream that responds expectedly to being listened to', + () async { + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); + const int cameraId = 33; + final ProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final CameraSelector mockCameraSelector = MockCameraSelector(); + final Camera mockCamera = MockCamera(); + final MockImageProxy mockImageProxy = MockImageProxy(); + final MockPlaneProxy mockPlane = MockPlaneProxy(); + final List mockPlanes = [mockPlane]; + final Uint8List buffer = Uint8List(0); + const int pixelStride = 27; + const int rowStride = 58; + const int imageFormat = 582; + const int imageHeight = 100; + const int imageWidth = 200; + + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = mockCameraSelector; + camera.createDetachedCallbacks = true; + + when(mockProcessCameraProvider.bindToLifecycle( + mockCameraSelector, [camera.mockImageAnalysis])) + .thenAnswer((_) async => mockCamera); + when(mockImageProxy.getPlanes()) + .thenAnswer((_) => Future>.value(mockPlanes)); + when(mockPlane.buffer).thenReturn(buffer); + when(mockPlane.rowStride).thenReturn(rowStride); + when(mockPlane.pixelStride).thenReturn(pixelStride); + when(mockImageProxy.format).thenReturn(imageFormat); + when(mockImageProxy.height).thenReturn(imageHeight); + when(mockImageProxy.width).thenReturn(imageWidth); + + final StreamSubscription + onStreamedFrameAvailableSubscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData imageData) { + // Test Analyzer correctly process ImageProxy instances. + expect(imageData.planes.length, equals(0)); + expect(imageData.planes[0].bytes, equals(buffer)); + expect(imageData.planes[0].bytesPerRow, equals(rowStride)); + expect(imageData.planes[0].bytesPerPixel, equals(pixelStride)); + expect(imageData.format.raw, equals(imageFormat)); + expect(imageData.height, equals(imageHeight)); + expect(imageData.width, equals(imageWidth)); + }); + + // Test ImageAnalysis use case is bound to ProcessCameraProvider. + final Analyzer capturedAnalyzer = + verify(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single + as Analyzer; + verify(mockProcessCameraProvider.bindToLifecycle( + mockCameraSelector, [camera.mockImageAnalysis])); + + await capturedAnalyzer.analyze(mockImageProxy); + onStreamedFrameAvailableSubscription.cancel(); + }); + + test( + 'onStreamedFrameAvaiable returns stream that responds expectedly to being canceled', + () async { + final MockAndroidCameraCameraX camera = MockAndroidCameraCameraX(); + const int cameraId = 32; + final ProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final CameraSelector mockCameraSelector = MockCameraSelector(); + final Camera mockCamera = MockCamera(); + + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = mockCameraSelector; + camera.createDetachedCallbacks = true; + + when(mockProcessCameraProvider.bindToLifecycle( + mockCameraSelector, [camera.mockImageAnalysis])) + .thenAnswer((_) async => mockCamera); + + final StreamSubscription imageStreamSubscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData data) {}); + + when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis)) + .thenAnswer((_) async => Future.value(true)); + + await imageStreamSubscription.cancel(); + + verify(camera.mockImageAnalysis.clearAnalyzer()); + }); } /// Mock of [AndroidCameraCameraX] that stubs behavior of some methods for /// testing. -class MockAndroidCameraCamerax extends AndroidCameraCameraX { +class MockAndroidCameraCameraX extends AndroidCameraCameraX { bool cameraPermissionsRequested = false; bool startedListeningForDeviceOrientationChanges = false; + + // Mocks available for use throughout testing. final MockPreview testPreview = MockPreview(); final MockImageCapture testImageCapture = MockImageCapture(); final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); + final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); @override Future requestCameraPermissions(bool enableAudio) async { @@ -450,4 +577,9 @@ class MockAndroidCameraCamerax extends AndroidCameraCameraX { int? flashMode, ResolutionInfo? targetResolution) { return testImageCapture; } + + @override + ImageAnalysis createImageAnalysis(ResolutionInfo? targetResolution) { + return mockImageAnalysis; + } } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 59be6656d5e1..56673fc45926 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -3,22 +3,31 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; +import 'dart:async' as _i9; +import 'dart:typed_data' as _i16; -import 'package:camera_android_camerax/src/camera.dart' as _i3; -import 'package:camera_android_camerax/src/camera_info.dart' as _i7; -import 'package:camera_android_camerax/src/camera_selector.dart' as _i9; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i2; -import 'package:camera_android_camerax/src/image_capture.dart' as _i10; -import 'package:camera_android_camerax/src/preview.dart' as _i11; +import 'package:camera_android_camerax/src/analyzer.dart' as _i12; +import 'package:camera_android_camerax/src/camera.dart' as _i4; +import 'package:camera_android_camerax/src/camera_info.dart' as _i8; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i10; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i3; +import 'package:camera_android_camerax/src/image_analysis.dart' as _i11; +import 'package:camera_android_camerax/src/image_capture.dart' as _i13; +import 'package:camera_android_camerax/src/image_proxy.dart' as _i14; +import 'package:camera_android_camerax/src/plane_proxy.dart' as _i15; +import 'package:camera_android_camerax/src/preview.dart' as _i17; import 'package:camera_android_camerax/src/process_camera_provider.dart' - as _i12; -import 'package:camera_android_camerax/src/use_case.dart' as _i13; -import 'package:flutter/foundation.dart' as _i6; -import 'package:flutter/services.dart' as _i5; -import 'package:flutter/widgets.dart' as _i4; + as _i18; +import 'package:camera_android_camerax/src/use_case.dart' as _i19; +import 'package:camera_platform_interface/camera_platform_interface.dart' + as _i2; +import 'package:flutter/foundation.dart' as _i7; +import 'package:flutter/services.dart' as _i6; +import 'package:flutter/widgets.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'test_camerax_library.g.dart' as _i20; + // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters @@ -30,9 +39,20 @@ import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeResolutionInfo_0 extends _i1.SmartFake - implements _i2.ResolutionInfo { - _FakeResolutionInfo_0( +class _FakeCameraImageFormat_0 extends _i1.SmartFake + implements _i2.CameraImageFormat { + _FakeCameraImageFormat_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResolutionInfo_1 extends _i1.SmartFake + implements _i3.ResolutionInfo { + _FakeResolutionInfo_1( Object parent, Invocation parentInvocation, ) : super( @@ -41,8 +61,8 @@ class _FakeResolutionInfo_0 extends _i1.SmartFake ); } -class _FakeCamera_1 extends _i1.SmartFake implements _i3.Camera { - _FakeCamera_1( +class _FakeCamera_2 extends _i1.SmartFake implements _i4.Camera { + _FakeCamera_2( Object parent, Invocation parentInvocation, ) : super( @@ -51,8 +71,8 @@ class _FakeCamera_1 extends _i1.SmartFake implements _i3.Camera { ); } -class _FakeWidget_2 extends _i1.SmartFake implements _i4.Widget { - _FakeWidget_2( +class _FakeWidget_3 extends _i1.SmartFake implements _i5.Widget { + _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( @@ -61,13 +81,13 @@ class _FakeWidget_2 extends _i1.SmartFake implements _i4.Widget { ); @override - String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + String toString({_i6.DiagnosticLevel? minLevel = _i6.DiagnosticLevel.info}) => super.toString(); } -class _FakeInheritedWidget_3 extends _i1.SmartFake - implements _i4.InheritedWidget { - _FakeInheritedWidget_3( +class _FakeInheritedWidget_4 extends _i1.SmartFake + implements _i5.InheritedWidget { + _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( @@ -76,13 +96,13 @@ class _FakeInheritedWidget_3 extends _i1.SmartFake ); @override - String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + String toString({_i6.DiagnosticLevel? minLevel = _i6.DiagnosticLevel.info}) => super.toString(); } -class _FakeDiagnosticsNode_4 extends _i1.SmartFake - implements _i6.DiagnosticsNode { - _FakeDiagnosticsNode_4( +class _FakeDiagnosticsNode_5 extends _i1.SmartFake + implements _i7.DiagnosticsNode { + _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( @@ -92,8 +112,8 @@ class _FakeDiagnosticsNode_4 extends _i1.SmartFake @override String toString({ - _i6.TextTreeConfiguration? parentConfiguration, - _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, + _i7.TextTreeConfiguration? parentConfiguration, + _i6.DiagnosticLevel? minLevel = _i6.DiagnosticLevel.info, }) => super.toString(); } @@ -101,77 +121,206 @@ class _FakeDiagnosticsNode_4 extends _i1.SmartFake /// A class which mocks [Camera]. /// /// See the documentation for Mockito's code generation for more information. -class MockCamera extends _i1.Mock implements _i3.Camera {} +class MockCamera extends _i1.Mock implements _i4.Camera {} /// A class which mocks [CameraInfo]. /// /// See the documentation for Mockito's code generation for more information. -class MockCameraInfo extends _i1.Mock implements _i7.CameraInfo { +class MockCameraInfo extends _i1.Mock implements _i8.CameraInfo { @override - _i8.Future getSensorRotationDegrees() => (super.noSuchMethod( + _i9.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( #getSensorRotationDegrees, [], ), - returnValue: _i8.Future.value(0), - returnValueForMissingStub: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + returnValueForMissingStub: _i9.Future.value(0), + ) as _i9.Future); +} + +/// A class which mocks [CameraImageData]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCameraImageData extends _i1.Mock implements _i2.CameraImageData { + @override + _i2.CameraImageFormat get format => (super.noSuchMethod( + Invocation.getter(#format), + returnValue: _FakeCameraImageFormat_0( + this, + Invocation.getter(#format), + ), + returnValueForMissingStub: _FakeCameraImageFormat_0( + this, + Invocation.getter(#format), + ), + ) as _i2.CameraImageFormat); + @override + int get height => (super.noSuchMethod( + Invocation.getter(#height), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + int get width => (super.noSuchMethod( + Invocation.getter(#width), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + List<_i2.CameraImagePlane> get planes => (super.noSuchMethod( + Invocation.getter(#planes), + returnValue: <_i2.CameraImagePlane>[], + returnValueForMissingStub: <_i2.CameraImagePlane>[], + ) as List<_i2.CameraImagePlane>); } /// A class which mocks [CameraSelector]. /// /// See the documentation for Mockito's code generation for more information. -class MockCameraSelector extends _i1.Mock implements _i9.CameraSelector { +class MockCameraSelector extends _i1.Mock implements _i10.CameraSelector { @override - _i8.Future> filter(List<_i7.CameraInfo>? cameraInfos) => + _i9.Future> filter(List<_i8.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), - returnValue: _i8.Future>.value(<_i7.CameraInfo>[]), + returnValue: _i9.Future>.value(<_i8.CameraInfo>[]), returnValueForMissingStub: - _i8.Future>.value(<_i7.CameraInfo>[]), - ) as _i8.Future>); + _i9.Future>.value(<_i8.CameraInfo>[]), + ) as _i9.Future>); +} + +/// A class which mocks [ImageAnalysis]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockImageAnalysis extends _i1.Mock implements _i11.ImageAnalysis { + @override + _i9.Future setAnalyzer(_i12.Analyzer? analyzer) => (super.noSuchMethod( + Invocation.method( + #setAnalyzer, + [analyzer], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future clearAnalyzer() => (super.noSuchMethod( + Invocation.method( + #clearAnalyzer, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); } /// A class which mocks [ImageCapture]. /// /// See the documentation for Mockito's code generation for more information. -class MockImageCapture extends _i1.Mock implements _i10.ImageCapture { +class MockImageCapture extends _i1.Mock implements _i13.ImageCapture { @override - _i8.Future setFlashMode(int? newFlashMode) => (super.noSuchMethod( + _i9.Future setFlashMode(int? newFlashMode) => (super.noSuchMethod( Invocation.method( #setFlashMode, [newFlashMode], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future takePicture() => (super.noSuchMethod( + _i9.Future takePicture() => (super.noSuchMethod( Invocation.method( #takePicture, [], ), - returnValue: _i8.Future.value(''), - returnValueForMissingStub: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + returnValueForMissingStub: _i9.Future.value(''), + ) as _i9.Future); +} + +/// A class which mocks [ImageProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockImageProxy extends _i1.Mock implements _i14.ImageProxy { + @override + int get format => (super.noSuchMethod( + Invocation.getter(#format), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + int get height => (super.noSuchMethod( + Invocation.getter(#height), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + int get width => (super.noSuchMethod( + Invocation.getter(#width), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + _i9.Future> getPlanes() => (super.noSuchMethod( + Invocation.method( + #getPlanes, + [], + ), + returnValue: + _i9.Future>.value(<_i15.PlaneProxy>[]), + returnValueForMissingStub: + _i9.Future>.value(<_i15.PlaneProxy>[]), + ) as _i9.Future>); + @override + _i9.Future close() => (super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); +} + +/// A class which mocks [PlaneProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlaneProxy extends _i1.Mock implements _i15.PlaneProxy { + @override + _i16.Uint8List get buffer => (super.noSuchMethod( + Invocation.getter(#buffer), + returnValue: _i16.Uint8List(0), + returnValueForMissingStub: _i16.Uint8List(0), + ) as _i16.Uint8List); + @override + int get pixelStride => (super.noSuchMethod( + Invocation.getter(#pixelStride), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + int get rowStride => (super.noSuchMethod( + Invocation.getter(#rowStride), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); } /// A class which mocks [Preview]. /// /// See the documentation for Mockito's code generation for more information. -class MockPreview extends _i1.Mock implements _i11.Preview { +class MockPreview extends _i1.Mock implements _i17.Preview { @override - _i8.Future setSurfaceProvider() => (super.noSuchMethod( + _i9.Future setSurfaceProvider() => (super.noSuchMethod( Invocation.method( #setSurfaceProvider, [], ), - returnValue: _i8.Future.value(0), - returnValueForMissingStub: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + returnValueForMissingStub: _i9.Future.value(0), + ) as _i9.Future); @override void releaseFlutterSurfaceTexture() => super.noSuchMethod( Invocation.method( @@ -181,12 +330,12 @@ class MockPreview extends _i1.Mock implements _i11.Preview { returnValueForMissingStub: null, ); @override - _i8.Future<_i2.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( + _i9.Future<_i3.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [], ), - returnValue: _i8.Future<_i2.ResolutionInfo>.value(_FakeResolutionInfo_0( + returnValue: _i9.Future<_i3.ResolutionInfo>.value(_FakeResolutionInfo_1( this, Invocation.method( #getResolutionInfo, @@ -194,36 +343,36 @@ class MockPreview extends _i1.Mock implements _i11.Preview { ), )), returnValueForMissingStub: - _i8.Future<_i2.ResolutionInfo>.value(_FakeResolutionInfo_0( + _i9.Future<_i3.ResolutionInfo>.value(_FakeResolutionInfo_1( this, Invocation.method( #getResolutionInfo, [], ), )), - ) as _i8.Future<_i2.ResolutionInfo>); + ) as _i9.Future<_i3.ResolutionInfo>); } /// A class which mocks [ProcessCameraProvider]. /// /// See the documentation for Mockito's code generation for more information. class MockProcessCameraProvider extends _i1.Mock - implements _i12.ProcessCameraProvider { + implements _i18.ProcessCameraProvider { @override - _i8.Future> getAvailableCameraInfos() => + _i9.Future> getAvailableCameraInfos() => (super.noSuchMethod( Invocation.method( #getAvailableCameraInfos, [], ), - returnValue: _i8.Future>.value(<_i7.CameraInfo>[]), + returnValue: _i9.Future>.value(<_i8.CameraInfo>[]), returnValueForMissingStub: - _i8.Future>.value(<_i7.CameraInfo>[]), - ) as _i8.Future>); + _i9.Future>.value(<_i8.CameraInfo>[]), + ) as _i9.Future>); @override - _i8.Future<_i3.Camera> bindToLifecycle( - _i9.CameraSelector? cameraSelector, - List<_i13.UseCase>? useCases, + _i9.Future<_i4.Camera> bindToLifecycle( + _i10.CameraSelector? cameraSelector, + List<_i19.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( @@ -233,7 +382,7 @@ class MockProcessCameraProvider extends _i1.Mock useCases, ], ), - returnValue: _i8.Future<_i3.Camera>.value(_FakeCamera_1( + returnValue: _i9.Future<_i4.Camera>.value(_FakeCamera_2( this, Invocation.method( #bindToLifecycle, @@ -243,7 +392,7 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - returnValueForMissingStub: _i8.Future<_i3.Camera>.value(_FakeCamera_1( + returnValueForMissingStub: _i9.Future<_i4.Camera>.value(_FakeCamera_2( this, Invocation.method( #bindToLifecycle, @@ -253,18 +402,18 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - ) as _i8.Future<_i3.Camera>); + ) as _i9.Future<_i4.Camera>); @override - _i8.Future isBound(_i13.UseCase? useCase) => (super.noSuchMethod( + _i9.Future isBound(_i19.UseCase? useCase) => (super.noSuchMethod( Invocation.method( #isBound, [useCase], ), - returnValue: _i8.Future.value(false), - returnValueForMissingStub: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + returnValueForMissingStub: _i9.Future.value(false), + ) as _i9.Future); @override - void unbind(List<_i13.UseCase>? useCases) => super.noSuchMethod( + void unbind(List<_i19.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], @@ -281,22 +430,37 @@ class MockProcessCameraProvider extends _i1.Mock ); } +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i20.TestInstanceManagerHostApi { + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} + /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. -class MockBuildContext extends _i1.Mock implements _i4.BuildContext { +class MockBuildContext extends _i1.Mock implements _i5.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override - _i4.Widget get widget => (super.noSuchMethod( + _i5.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), - returnValue: _FakeWidget_2( + returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), - ) as _i4.Widget); + ) as _i5.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), @@ -308,8 +472,8 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { returnValue: false, ) as bool); @override - _i4.InheritedWidget dependOnInheritedElement( - _i4.InheritedElement? ancestor, { + _i5.InheritedWidget dependOnInheritedElement( + _i5.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( @@ -318,7 +482,7 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { [ancestor], {#aspect: aspect}, ), - returnValue: _FakeInheritedWidget_3( + returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, @@ -326,9 +490,9 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { {#aspect: aspect}, ), ), - ) as _i4.InheritedWidget); + ) as _i5.InheritedWidget); @override - void visitAncestorElements(bool Function(_i4.Element)? visitor) => + void visitAncestorElements(bool Function(_i5.Element)? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, @@ -337,7 +501,7 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { returnValueForMissingStub: null, ); @override - void visitChildElements(_i4.ElementVisitor? visitor) => super.noSuchMethod( + void visitChildElements(_i5.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], @@ -345,7 +509,7 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { returnValueForMissingStub: null, ); @override - void dispatchNotification(_i4.Notification? notification) => + void dispatchNotification(_i5.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, @@ -354,9 +518,9 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { returnValueForMissingStub: null, ); @override - _i6.DiagnosticsNode describeElement( + _i7.DiagnosticsNode describeElement( String? name, { - _i6.DiagnosticsTreeStyle? style = _i6.DiagnosticsTreeStyle.errorProperty, + _i7.DiagnosticsTreeStyle? style = _i7.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( @@ -364,7 +528,7 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { [name], {#style: style}, ), - returnValue: _FakeDiagnosticsNode_4( + returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, @@ -372,11 +536,11 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { {#style: style}, ), ), - ) as _i6.DiagnosticsNode); + ) as _i7.DiagnosticsNode); @override - _i6.DiagnosticsNode describeWidget( + _i7.DiagnosticsNode describeWidget( String? name, { - _i6.DiagnosticsTreeStyle? style = _i6.DiagnosticsTreeStyle.errorProperty, + _i7.DiagnosticsTreeStyle? style = _i7.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( @@ -384,7 +548,7 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { [name], {#style: style}, ), - returnValue: _FakeDiagnosticsNode_4( + returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, @@ -392,9 +556,9 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { {#style: style}, ), ), - ) as _i6.DiagnosticsNode); + ) as _i7.DiagnosticsNode); @override - List<_i6.DiagnosticsNode> describeMissingAncestor( + List<_i7.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( @@ -402,21 +566,21 @@ class MockBuildContext extends _i1.Mock implements _i4.BuildContext { [], {#expectedAncestorType: expectedAncestorType}, ), - returnValue: <_i6.DiagnosticsNode>[], - ) as List<_i6.DiagnosticsNode>); + returnValue: <_i7.DiagnosticsNode>[], + ) as List<_i7.DiagnosticsNode>); @override - _i6.DiagnosticsNode describeOwnershipChain(String? name) => + _i7.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), - returnValue: _FakeDiagnosticsNode_4( + returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), - ) as _i6.DiagnosticsNode); + ) as _i7.DiagnosticsNode); } diff --git a/packages/camera/camera_android_camerax/test/camera_test.dart b/packages/camera/camera_android_camerax/test/camera_test.dart index c2948282dcf1..3d6ea51b4b04 100644 --- a/packages/camera/camera_android_camerax/test/camera_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_test.dart @@ -5,10 +5,18 @@ import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'camera_info_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestInstanceManagerHostApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + group('Camera', () { test('flutterApiCreateTest', () { final InstanceManager instanceManager = InstanceManager( diff --git a/packages/camera/camera_android_camerax/test/camera_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_test.mocks.dart new file mode 100644 index 000000000000..915ba5584b4d --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera_test.mocks.dart @@ -0,0 +1,38 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in camera_android_camerax/test/camera_test.dart. +// Do not manually edit this file. + +// 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 [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 new file mode 100644 index 000000000000..0e581094f2b6 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.dart @@ -0,0 +1,137 @@ +// 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/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:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'image_analysis_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestImageAnalysisHostApi, TestInstanceManagerHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('ImageAnalysis', () { + setUp(() {}); + + tearDown(() { + TestImageAnalysisHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test('HostApi create', () { + final MockTestImageAnalysisHostApi mockApi = + MockTestImageAnalysisHostApi(); + TestImageAnalysisHostApi.setup(mockApi); + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + const int targetResolutionWidth = 65; + const int targetResolutionHeight = 99; + final ResolutionInfo targetResolution = + ResolutionInfo(width: 65, height: 99); + final ImageAnalysis instance = ImageAnalysis( + targetResolution: targetResolution, + instanceManager: instanceManager, + ); + + final VerificationResult createVerification = 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)); + }); + + test('setAnalyzer', () async { + final MockTestImageAnalysisHostApi mockApi = + MockTestImageAnalysisHostApi(); + TestImageAnalysisHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final ImageAnalysis instance = ImageAnalysis.detached( + targetResolution: ResolutionInfo(width: 75, height: 98), + instanceManager: instanceManager, + ); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance( + instance, + instanceIdentifier, + onCopy: (ImageAnalysis original) => ImageAnalysis.detached( + targetResolution: original.targetResolution, + instanceManager: instanceManager, + ), + ); + + final Analyzer analyzer = Analyzer.detached( + analyze: (ImageProxy imageProxy) async {}, + instanceManager: instanceManager, + ); + const int analyzerIdentifier = 10; + instanceManager.addHostCreatedInstance( + analyzer, + analyzerIdentifier, + onCopy: (_) => Analyzer.detached( + analyze: (ImageProxy imageProxy) async {}, + instanceManager: instanceManager, + ), + ); + + await instance.setAnalyzer( + analyzer, + ); + + verify(mockApi.setAnalyzer( + instanceIdentifier, + analyzerIdentifier, + )); + }); + + test('clearAnalyzer', () async { + final MockTestImageAnalysisHostApi mockApi = + MockTestImageAnalysisHostApi(); + TestImageAnalysisHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final ImageAnalysis instance = ImageAnalysis.detached( + targetResolution: ResolutionInfo(width: 75, height: 98), + instanceManager: instanceManager, + ); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance( + instance, + instanceIdentifier, + onCopy: (ImageAnalysis original) => ImageAnalysis.detached( + targetResolution: original.targetResolution, + instanceManager: instanceManager, + ), + ); + + await instance.clearAnalyzer(); + + verify(mockApi.clearAnalyzer( + instanceIdentifier, + )); + }); + }); +} 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 new file mode 100644 index 000000000000..421d9908c3a8 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.mocks.dart @@ -0,0 +1,88 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in camera_android_camerax/test/image_analysis_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _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 [TestImageAnalysisHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestImageAnalysisHostApi extends _i1.Mock + implements _i2.TestImageAnalysisHostApi { + MockTestImageAnalysisHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + _i3.ResolutionInfo? targetResolutionIdentifier, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + targetResolutionIdentifier, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setAnalyzer( + int? identifier, + int? analyzerIdentifier, + ) => + super.noSuchMethod( + Invocation.method( + #setAnalyzer, + [ + identifier, + analyzerIdentifier, + ], + ), + returnValueForMissingStub: null, + ); + @override + void clearAnalyzer(int? identifier) => super.noSuchMethod( + Invocation.method( + #clearAnalyzer, + [identifier], + ), + 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_capture_test.dart b/packages/camera/camera_android_camerax/test/image_capture_test.dart index 1d507c370bb0..bfcc66c0175f 100644 --- a/packages/camera/camera_android_camerax/test/image_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/image_capture_test.dart @@ -79,7 +79,7 @@ void main() { onCopy: (_) => ImageCapture.detached(instanceManager: instanceManager), ); - imageCapture.setFlashMode(flashMode); + await imageCapture.setFlashMode(flashMode); verify(mockApi.setFlashMode( instanceManager.getIdentifier(imageCapture), flashMode)); diff --git a/packages/camera/camera_android_camerax/test/image_proxy_test.dart b/packages/camera/camera_android_camerax/test/image_proxy_test.dart new file mode 100644 index 000000000000..15f9fdabd738 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/image_proxy_test.dart @@ -0,0 +1,131 @@ +// 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:typed_data'; + +import 'package:camera_android_camerax/src/image_proxy.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/plane_proxy.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'image_proxy_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestImageProxyHostApi, TestInstanceManagerHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('ImageProxy', () { + setUp(() {}); + + tearDown(() { + TestImageProxyHostApi.setup(null); + TestInstanceManagerHostApi.setup(null); + }); + + test('getPlanes', () async { + final MockTestImageProxyHostApi mockApi = MockTestImageProxyHostApi(); + TestImageProxyHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final ImageProxy instance = ImageProxy.detached( + instanceManager: instanceManager, format: 2, height: 7, width: 10); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance(instance, instanceIdentifier, + onCopy: (ImageProxy original) => ImageProxy.detached( + instanceManager: instanceManager, + format: original.format, + height: original.height, + width: original.width)); + final PlaneProxy planeProxy = PlaneProxy.detached( + instanceManager: instanceManager, + buffer: Uint8List(3), + pixelStride: 3, + rowStride: 20); + const int planeProxyIdentifier = 48; + instanceManager.addHostCreatedInstance(planeProxy, planeProxyIdentifier, + onCopy: (PlaneProxy original) => PlaneProxy.detached( + instanceManager: instanceManager, + buffer: original.buffer, + pixelStride: original.pixelStride, + rowStride: original.rowStride)); + + final List result = [planeProxyIdentifier]; + when(mockApi.getPlanes( + instanceIdentifier, + )).thenAnswer((_) { + return result; + }); + + final List planes = await instance.getPlanes(); + expect(planes[0], equals(planeProxy)); + + verify(mockApi.getPlanes( + instanceIdentifier, + )); + }); + + test('close', () async { + final MockTestImageProxyHostApi mockApi = MockTestImageProxyHostApi(); + TestImageProxyHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final ImageProxy instance = ImageProxy.detached( + instanceManager: instanceManager, format: 2, height: 7, width: 10); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance(instance, instanceIdentifier, + onCopy: (ImageProxy original) => ImageProxy.detached( + instanceManager: instanceManager, + format: original.format, + height: original.height, + width: original.width)); + + await instance.close(); + + verify(mockApi.close( + instanceIdentifier, + )); + }); + + test('FlutterAPI create', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final ImageProxyFlutterApiImpl api = ImageProxyFlutterApiImpl( + instanceManager: instanceManager, + ); + + const int instanceIdentifier = 0; + const int format = 9; + const int height = 55; + const int width = 11; + + api.create( + instanceIdentifier, + format, + height, + width, + ); + + final ImageProxy imageProxy = + instanceManager.getInstanceWithWeakReference(instanceIdentifier)!; + + expect(imageProxy.format, equals(format)); + expect(imageProxy.height, equals(height)); + expect(imageProxy.width, equals(width)); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/image_proxy_test.mocks.dart b/packages/camera/camera_android_camerax/test/image_proxy_test.mocks.dart new file mode 100644 index 000000000000..175d917e68d3 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/image_proxy_test.mocks.dart @@ -0,0 +1,65 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in camera_android_camerax/test/image_proxy_test.dart. +// Do not manually edit this file. + +// 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 [TestImageProxyHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestImageProxyHostApi extends _i1.Mock + implements _i2.TestImageProxyHostApi { + MockTestImageProxyHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + List getPlanes(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getPlanes, + [identifier], + ), + returnValue: [], + ) as List); + @override + void close(int? identifier) => super.noSuchMethod( + Invocation.method( + #close, + [identifier], + ), + 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/plane_proxy_test.dart b/packages/camera/camera_android_camerax/test/plane_proxy_test.dart new file mode 100644 index 000000000000..cbf886b44e30 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/plane_proxy_test.dart @@ -0,0 +1,52 @@ +// 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:typed_data'; + +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/plane_proxy.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; + +import 'plane_proxy_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestInstanceManagerHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('PlaneProxy', () { + setUp(() {}); + + tearDown(() { + TestInstanceManagerHostApi.setup(null); + }); + + test('FlutterAPI create', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final PlaneProxyFlutterApiImpl api = PlaneProxyFlutterApiImpl( + instanceManager: instanceManager, + ); + + const int instanceIdentifier = 0; + final Uint8List buffer = Uint8List(1); + const int pixelStride = 3; + const int rowStride = 6; + + api.create(instanceIdentifier, buffer, pixelStride, rowStride); + + final PlaneProxy planeProxy = + instanceManager.getInstanceWithWeakReference(instanceIdentifier)!; + + expect(planeProxy.buffer, equals(buffer)); + expect(planeProxy.pixelStride, equals(pixelStride)); + expect(planeProxy.rowStride, equals(rowStride)); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/plane_proxy_test.mocks.dart b/packages/camera/camera_android_camerax/test/plane_proxy_test.mocks.dart new file mode 100644 index 000000000000..cd378700d83c --- /dev/null +++ b/packages/camera/camera_android_camerax/test/plane_proxy_test.mocks.dart @@ -0,0 +1,38 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in camera_android_camerax/test/plane_proxy_test.dart. +// Do not manually edit this file. + +// 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 [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 2bb655ffb630..0af4cd0029f8 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v9.2.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -14,6 +14,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; abstract class TestInstanceManagerHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = StandardMessageCodec(); /// Clear the native `InstanceManager`. @@ -28,9 +30,12 @@ abstract class TestInstanceManagerHostApi { 'dev.flutter.pigeon.InstanceManagerHostApi.clear', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { // ignore message api.clear(); return []; @@ -41,6 +46,8 @@ abstract class TestInstanceManagerHostApi { } abstract class TestJavaObjectHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); @@ -52,9 +59,12 @@ abstract class TestJavaObjectHostApi { 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose was null.'); final List args = (message as List?)!; @@ -70,6 +80,8 @@ abstract class TestJavaObjectHostApi { } abstract class TestCameraInfoHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = StandardMessageCodec(); int getSensorRotationDegrees(int identifier); @@ -82,9 +94,12 @@ abstract class TestCameraInfoHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraInfoHostApi.getSensorRotationDegrees was null.'); final List args = (message as List?)!; @@ -100,6 +115,8 @@ abstract class TestCameraInfoHostApi { } abstract class TestCameraSelectorHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = StandardMessageCodec(); void create(int identifier, int? lensFacing); @@ -113,9 +130,12 @@ abstract class TestCameraSelectorHostApi { 'dev.flutter.pigeon.CameraSelectorHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraSelectorHostApi.create was null.'); final List args = (message as List?)!; @@ -133,9 +153,12 @@ abstract class TestCameraSelectorHostApi { 'dev.flutter.pigeon.CameraSelectorHostApi.filter', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraSelectorHostApi.filter was null.'); final List args = (message as List?)!; @@ -156,6 +179,8 @@ abstract class TestCameraSelectorHostApi { } abstract class TestProcessCameraProviderHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = StandardMessageCodec(); Future getInstance(); @@ -178,9 +203,12 @@ abstract class TestProcessCameraProviderHostApi { 'dev.flutter.pigeon.ProcessCameraProviderHostApi.getInstance', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { // ignore message final int output = await api.getInstance(); return [output]; @@ -193,9 +221,12 @@ abstract class TestProcessCameraProviderHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.getAvailableCameraInfos was null.'); final List args = (message as List?)!; @@ -214,9 +245,12 @@ abstract class TestProcessCameraProviderHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null.'); final List args = (message as List?)!; @@ -241,9 +275,12 @@ abstract class TestProcessCameraProviderHostApi { 'dev.flutter.pigeon.ProcessCameraProviderHostApi.isBound', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.isBound was null.'); final List args = (message as List?)!; @@ -264,9 +301,12 @@ abstract class TestProcessCameraProviderHostApi { 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null.'); final List args = (message as List?)!; @@ -287,9 +327,12 @@ abstract class TestProcessCameraProviderHostApi { 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null.'); final List args = (message as List?)!; @@ -328,6 +371,8 @@ class _TestSystemServicesHostApiCodec extends StandardMessageCodec { } abstract class TestSystemServicesHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestSystemServicesHostApiCodec(); Future requestCameraPermissions( @@ -346,9 +391,12 @@ abstract class TestSystemServicesHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions was null.'); final List args = (message as List?)!; @@ -367,9 +415,12 @@ abstract class TestSystemServicesHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null.'); final List args = (message as List?)!; @@ -391,9 +442,12 @@ abstract class TestSystemServicesHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { // ignore message api.stopListeningForDeviceOrientationChange(); return []; @@ -432,6 +486,8 @@ class _TestPreviewHostApiCodec extends StandardMessageCodec { } abstract class TestPreviewHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestPreviewHostApiCodec(); void create(int identifier, int? rotation, ResolutionInfo? targetResolution); @@ -449,9 +505,12 @@ abstract class TestPreviewHostApi { 'dev.flutter.pigeon.PreviewHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.create was null.'); final List args = (message as List?)!; @@ -471,9 +530,12 @@ abstract class TestPreviewHostApi { 'dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider was null.'); final List args = (message as List?)!; @@ -491,9 +553,12 @@ abstract class TestPreviewHostApi { codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { // ignore message api.releaseFlutterSurfaceTexture(); return []; @@ -505,9 +570,12 @@ abstract class TestPreviewHostApi { 'dev.flutter.pigeon.PreviewHostApi.getResolutionInfo', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.getResolutionInfo was null.'); final List args = (message as List?)!; @@ -546,6 +614,8 @@ class _TestImageCaptureHostApiCodec extends StandardMessageCodec { } abstract class TestImageCaptureHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestImageCaptureHostApiCodec(); void create(int identifier, int? flashMode, ResolutionInfo? targetResolution); @@ -561,9 +631,12 @@ abstract class TestImageCaptureHostApi { 'dev.flutter.pigeon.ImageCaptureHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ImageCaptureHostApi.create was null.'); final List args = (message as List?)!; @@ -583,9 +656,12 @@ abstract class TestImageCaptureHostApi { 'dev.flutter.pigeon.ImageCaptureHostApi.setFlashMode', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ImageCaptureHostApi.setFlashMode was null.'); final List args = (message as List?)!; @@ -605,9 +681,12 @@ abstract class TestImageCaptureHostApi { 'dev.flutter.pigeon.ImageCaptureHostApi.takePicture', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ImageCaptureHostApi.takePicture was null.'); final List args = (message as List?)!; @@ -621,3 +700,205 @@ abstract class TestImageCaptureHostApi { } } } + +class _TestImageAnalysisHostApiCodec extends StandardMessageCodec { + const _TestImageAnalysisHostApiCodec(); + @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 TestImageAnalysisHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = _TestImageAnalysisHostApiCodec(); + + void create(int identifier, ResolutionInfo? targetResolutionIdentifier); + + void setAnalyzer(int identifier, int analyzerIdentifier); + + void clearAnalyzer(int identifier); + + static void setup(TestImageAnalysisHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageAnalysisHostApi.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.ImageAnalysisHostApi.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.ImageAnalysisHostApi.create was null, expected non-null int.'); + final ResolutionInfo? arg_targetResolutionIdentifier = + (args[1] as ResolutionInfo?); + api.create(arg_identifier!, arg_targetResolutionIdentifier); + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageAnalysisHostApi.setAnalyzer', 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.ImageAnalysisHostApi.setAnalyzer 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.ImageAnalysisHostApi.setAnalyzer was null, expected non-null int.'); + final int? arg_analyzerIdentifier = (args[1] as int?); + assert(arg_analyzerIdentifier != null, + 'Argument for dev.flutter.pigeon.ImageAnalysisHostApi.setAnalyzer was null, expected non-null int.'); + api.setAnalyzer(arg_identifier!, arg_analyzerIdentifier!); + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageAnalysisHostApi.clearAnalyzer', 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.ImageAnalysisHostApi.clearAnalyzer 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.ImageAnalysisHostApi.clearAnalyzer was null, expected non-null int.'); + api.clearAnalyzer(arg_identifier!); + return []; + }); + } + } + } +} + +abstract class TestAnalyzerHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier); + + static void setup(TestAnalyzerHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.AnalyzerHostApi.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.AnalyzerHostApi.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.AnalyzerHostApi.create was null, expected non-null int.'); + api.create(arg_identifier!); + return []; + }); + } + } + } +} + +abstract class TestImageProxyHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + List getPlanes(int identifier); + + void close(int identifier); + + static void setup(TestImageProxyHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageProxyHostApi.getPlanes', 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.ImageProxyHostApi.getPlanes 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.ImageProxyHostApi.getPlanes was null, expected non-null int.'); + final List output = api.getPlanes(arg_identifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ImageProxyHostApi.close', 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.ImageProxyHostApi.close 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.ImageProxyHostApi.close was null, expected non-null int.'); + api.close(arg_identifier!); + return []; + }); + } + } + } +}