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