[camerax] Adds functionality to bind UseCases to a lifecycle (#6939)
* Copy over code from proof of concept
* Add dart tests
* Fix dart tests
* Add java tests
* Add me as owner and changelog change
* Fix analyzer
* Add instance manager fix
* Update comment
* Undo instance manager changes
* Formatting
* Fix analyze
* Address review
* Fix analyze
* Add import
* Fix assertion error
* Remove unecessary this keywrod
* Update packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java
Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com>
Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com>
diff --git a/CODEOWNERS b/CODEOWNERS
index f128098..8a46d52 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -28,6 +28,7 @@
# - Android
packages/camera/camera_android/** @camsim99
+packages/camera/camera_android_camerax/** @camsim99
packages/espresso/** @GaryQian
packages/flutter_plugin_android_lifecycle/** @GaryQian
packages/google_maps_flutter/google_maps_flutter_android/** @GaryQian
diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index f94a860..389fc31 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -5,3 +5,4 @@
* Adds CameraSelector class.
* Adds ProcessCameraProvider class.
* Bump CameraX version to 1.3.0-alpha02.
+* Adds Camera and UseCase classes, along with methods for binding UseCases to a lifecycle with the ProcessCameraProvider.
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 b8fbaf5..7ee7263 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
@@ -6,6 +6,7 @@
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
@@ -15,7 +16,7 @@
public final class CameraAndroidCameraxPlugin implements FlutterPlugin, ActivityAware {
private InstanceManager instanceManager;
private FlutterPluginBinding pluginBinding;
- private ProcessCameraProviderHostApiImpl processCameraProviderHostApi;
+ public ProcessCameraProviderHostApiImpl processCameraProviderHostApi;
/**
* Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment.
@@ -36,10 +37,10 @@
// Set up Host APIs.
GeneratedCameraXLibrary.CameraInfoHostApi.setup(
binaryMessenger, new CameraInfoHostApiImpl(instanceManager));
- GeneratedCameraXLibrary.JavaObjectHostApi.setup(
- binaryMessenger, new JavaObjectHostApiImpl(instanceManager));
GeneratedCameraXLibrary.CameraSelectorHostApi.setup(
binaryMessenger, new CameraSelectorHostApiImpl(binaryMessenger, instanceManager));
+ GeneratedCameraXLibrary.JavaObjectHostApi.setup(
+ binaryMessenger, new JavaObjectHostApiImpl(instanceManager));
processCameraProviderHostApi =
new ProcessCameraProviderHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.ProcessCameraProviderHostApi.setup(
@@ -49,10 +50,6 @@
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
pluginBinding = flutterPluginBinding;
- (new CameraAndroidCameraxPlugin())
- .setUp(
- flutterPluginBinding.getBinaryMessenger(),
- flutterPluginBinding.getApplicationContext());
}
@Override
@@ -66,7 +63,10 @@
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
- updateContext(activityPluginBinding.getActivity());
+ setUp(pluginBinding.getBinaryMessenger(), pluginBinding.getApplicationContext());
+ updateContext(pluginBinding.getApplicationContext());
+ processCameraProviderHostApi.setLifecycleOwner(
+ (LifecycleOwner) activityPluginBinding.getActivity());
}
@Override
@@ -89,7 +89,7 @@
* Updates context that is used to fetch the corresponding instance of a {@code
* ProcessCameraProvider}.
*/
- private void updateContext(Context context) {
+ public void updateContext(Context context) {
if (processCameraProviderHostApi != null) {
processCameraProviderHostApi.setContext(context);
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java
new file mode 100644
index 0000000..a035483
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java
@@ -0,0 +1,22 @@
+// 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.camera.core.Camera;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraFlutterApi;
+
+public class CameraFlutterApiImpl extends CameraFlutterApi {
+ private final InstanceManager instanceManager;
+
+ public CameraFlutterApiImpl(BinaryMessenger binaryMessenger, InstanceManager instanceManager) {
+ super(binaryMessenger);
+ this.instanceManager = instanceManager;
+ }
+
+ void create(Camera camera, Reply<Void> reply) {
+ create(instanceManager.addHostCreatedInstance(camera), reply);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java
index 7daba0d..d960b7f 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java
@@ -7,6 +7,7 @@
import androidx.annotation.NonNull;
import androidx.camera.core.CameraInfo;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoHostApi;
+import java.util.Objects;
public class CameraInfoHostApiImpl implements CameraInfoHostApi {
private final InstanceManager instanceManager;
@@ -17,7 +18,8 @@
@Override
public Long getSensorRotationDegrees(@NonNull Long identifier) {
- CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(identifier);
+ CameraInfo cameraInfo =
+ (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier));
return Long.valueOf(cameraInfo.getSensorRotationDegrees());
}
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java
index 9c559a7..87c69de 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java
@@ -12,6 +12,7 @@
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraSelectorHostApi;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
public class CameraSelectorHostApiImpl implements CameraSelectorHostApi {
private final BinaryMessenger binaryMessenger;
@@ -41,13 +42,15 @@
@Override
public List<Long> filter(@NonNull Long identifier, @NonNull List<Long> cameraInfoIds) {
- CameraSelector cameraSelector = (CameraSelector) instanceManager.getInstance(identifier);
+ CameraSelector cameraSelector =
+ (CameraSelector) Objects.requireNonNull(instanceManager.getInstance(identifier));
List<CameraInfo> cameraInfosForFilter = new ArrayList<CameraInfo>();
for (Number cameraInfoAsNumber : cameraInfoIds) {
Long cameraInfoId = cameraInfoAsNumber.longValue();
- CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(cameraInfoId);
+ CameraInfo cameraInfo =
+ (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId));
cameraInfosForFilter.add(cameraInfo);
}
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 041564c..8c42a79 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
@@ -332,6 +332,16 @@
@NonNull
List<Long> getAvailableCameraInfos(@NonNull Long identifier);
+ @NonNull
+ Long bindToLifecycle(
+ @NonNull Long identifier,
+ @NonNull Long cameraSelectorIdentifier,
+ @NonNull List<Long> useCaseIds);
+
+ void unbind(@NonNull Long identifier, @NonNull List<Long> useCaseIds);
+
+ void unbindAll(@NonNull Long identifier);
+
/** The codec used by ProcessCameraProviderHostApi. */
static MessageCodec<Object> getCodec() {
return ProcessCameraProviderHostApiCodec.INSTANCE;
@@ -405,6 +415,107 @@
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map<String, Object> wrapped = new HashMap<>();
+ try {
+ ArrayList<Object> args = (ArrayList<Object>) message;
+ Number identifierArg = (Number) args.get(0);
+ if (identifierArg == null) {
+ throw new NullPointerException("identifierArg unexpectedly null.");
+ }
+ Number cameraSelectorIdentifierArg = (Number) args.get(1);
+ if (cameraSelectorIdentifierArg == null) {
+ throw new NullPointerException(
+ "cameraSelectorIdentifierArg unexpectedly null.");
+ }
+ List<Long> useCaseIdsArg = (List<Long>) args.get(2);
+ if (useCaseIdsArg == null) {
+ throw new NullPointerException("useCaseIdsArg unexpectedly null.");
+ }
+ Long output =
+ api.bindToLifecycle(
+ (identifierArg == null) ? null : identifierArg.longValue(),
+ (cameraSelectorIdentifierArg == null)
+ ? null
+ : cameraSelectorIdentifierArg.longValue(),
+ useCaseIdsArg);
+ wrapped.put("result", output);
+ } catch (Error | RuntimeException exception) {
+ wrapped.put("error", wrapError(exception));
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map<String, Object> wrapped = new HashMap<>();
+ try {
+ ArrayList<Object> args = (ArrayList<Object>) message;
+ Number identifierArg = (Number) args.get(0);
+ if (identifierArg == null) {
+ throw new NullPointerException("identifierArg unexpectedly null.");
+ }
+ List<Long> useCaseIdsArg = (List<Long>) args.get(1);
+ if (useCaseIdsArg == null) {
+ throw new NullPointerException("useCaseIdsArg unexpectedly null.");
+ }
+ api.unbind(
+ (identifierArg == null) ? null : identifierArg.longValue(), useCaseIdsArg);
+ wrapped.put("result", null);
+ } catch (Error | RuntimeException exception) {
+ wrapped.put("error", wrapError(exception));
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ Map<String, Object> wrapped = new HashMap<>();
+ try {
+ ArrayList<Object> args = (ArrayList<Object>) message;
+ Number identifierArg = (Number) args.get(0);
+ if (identifierArg == null) {
+ throw new NullPointerException("identifierArg unexpectedly null.");
+ }
+ api.unbindAll((identifierArg == null) ? null : identifierArg.longValue());
+ wrapped.put("result", null);
+ } catch (Error | RuntimeException exception) {
+ wrapped.put("error", wrapError(exception));
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
}
}
@@ -445,6 +556,40 @@
}
}
+ private static class CameraFlutterApiCodec extends StandardMessageCodec {
+ public static final CameraFlutterApiCodec INSTANCE = new CameraFlutterApiCodec();
+
+ private CameraFlutterApiCodec() {}
+ }
+
+ /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
+ public static class CameraFlutterApi {
+ private final BinaryMessenger binaryMessenger;
+
+ public CameraFlutterApi(BinaryMessenger argBinaryMessenger) {
+ this.binaryMessenger = argBinaryMessenger;
+ }
+
+ public interface Reply<T> {
+ void reply(T reply);
+ }
+
+ static MessageCodec<Object> getCodec() {
+ return CameraFlutterApiCodec.INSTANCE;
+ }
+
+ public void create(@NonNull Long identifierArg, Reply<Void> callback) {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger, "dev.flutter.pigeon.CameraFlutterApi.create", getCodec());
+ channel.send(
+ new ArrayList<Object>(Arrays.asList(identifierArg)),
+ channelReply -> {
+ callback.reply(null);
+ });
+ }
+ }
+
private static Map<String, Object> wrapError(Throwable exception) {
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("message", exception.toString());
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java
index 19c5eb5..f82f18f 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java
@@ -6,20 +6,26 @@
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.UseCase;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
+import androidx.lifecycle.LifecycleOwner;
import com.google.common.util.concurrent.ListenableFuture;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ProcessCameraProviderHostApi;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
public class ProcessCameraProviderHostApiImpl implements ProcessCameraProviderHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;
private Context context;
+ private LifecycleOwner lifecycleOwner;
public ProcessCameraProviderHostApiImpl(
BinaryMessenger binaryMessenger, InstanceManager instanceManager, Context context) {
@@ -28,6 +34,10 @@
this.context = context;
}
+ public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
+ this.lifecycleOwner = lifecycleOwner;
+ }
+
/**
* Sets the context that the {@code ProcessCameraProvider} will use to attach the lifecycle of the
* camera to.
@@ -40,8 +50,8 @@
}
/**
- * Returns the instance of the ProcessCameraProvider to manage the lifecycle of the camera for the
- * current {@code Context}.
+ * Returns the instance of the {@code ProcessCameraProvider} to manage the lifecycle of the camera
+ * for the current {@code Context}.
*/
@Override
public void getInstance(GeneratedCameraXLibrary.Result<Long> result) {
@@ -54,11 +64,9 @@
// Camera provider is now guaranteed to be available.
ProcessCameraProvider processCameraProvider = processCameraProviderFuture.get();
- if (!instanceManager.containsInstance(processCameraProvider)) {
- final ProcessCameraProviderFlutterApiImpl flutterApi =
- new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager);
- flutterApi.create(processCameraProvider, reply -> {});
- }
+ final ProcessCameraProviderFlutterApiImpl flutterApi =
+ new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager);
+ flutterApi.create(processCameraProvider, reply -> {});
result.success(instanceManager.getIdentifierForStrongReference(processCameraProvider));
} catch (Exception e) {
result.error(e);
@@ -67,11 +75,11 @@
ContextCompat.getMainExecutor(context));
}
- /** Returns cameras available to the ProcessCameraProvider. */
+ /** Returns cameras available to the {@code ProcessCameraProvider}. */
@Override
public List<Long> getAvailableCameraInfos(@NonNull Long identifier) {
ProcessCameraProvider processCameraProvider =
- (ProcessCameraProvider) instanceManager.getInstance(identifier);
+ (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier));
List<CameraInfo> availableCameras = processCameraProvider.getAvailableCameraInfos();
List<Long> availableCamerasIds = new ArrayList<Long>();
@@ -84,4 +92,59 @@
}
return availableCamerasIds;
}
+
+ /**
+ * Binds specified {@code UseCase}s to the lifecycle of the {@code LifecycleOwner} that
+ * corresponds to this instance and returns the instance of the {@code Camera} whose lifecycle
+ * that {@code LifecycleOwner} reflects.
+ */
+ @Override
+ public Long bindToLifecycle(
+ @NonNull Long identifier,
+ @NonNull Long cameraSelectorIdentifier,
+ @NonNull List<Long> useCaseIds) {
+ ProcessCameraProvider processCameraProvider =
+ (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier));
+ CameraSelector cameraSelector =
+ (CameraSelector)
+ Objects.requireNonNull(instanceManager.getInstance(cameraSelectorIdentifier));
+ UseCase[] useCases = new UseCase[useCaseIds.size()];
+ for (int i = 0; i < useCaseIds.size(); i++) {
+ useCases[i] =
+ (UseCase)
+ Objects.requireNonNull(
+ instanceManager.getInstance(((Number) useCaseIds.get(i)).longValue()));
+ }
+
+ Camera camera =
+ processCameraProvider.bindToLifecycle(
+ (LifecycleOwner) lifecycleOwner, cameraSelector, useCases);
+
+ final CameraFlutterApiImpl cameraFlutterApi =
+ new CameraFlutterApiImpl(binaryMessenger, instanceManager);
+ cameraFlutterApi.create(camera, result -> {});
+
+ return instanceManager.getIdentifierForStrongReference(camera);
+ }
+
+ @Override
+ public void unbind(@NonNull Long identifier, @NonNull List<Long> useCaseIds) {
+ ProcessCameraProvider processCameraProvider =
+ (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier));
+ UseCase[] useCases = new UseCase[useCaseIds.size()];
+ for (int i = 0; i < useCaseIds.size(); i++) {
+ useCases[i] =
+ (UseCase)
+ Objects.requireNonNull(
+ instanceManager.getInstance(((Number) useCaseIds.get(i)).longValue()));
+ }
+ processCameraProvider.unbind(useCases);
+ }
+
+ @Override
+ public void unbindAll(@NonNull Long identifier) {
+ ProcessCameraProvider processCameraProvider =
+ (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier));
+ processCameraProvider.unbindAll();
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java
new file mode 100644
index 0000000..e2135b3
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java
@@ -0,0 +1,52 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camerax;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import androidx.camera.core.Camera;
+import io.flutter.plugin.common.BinaryMessenger;
+import java.util.Objects;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class CameraTest {
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock public BinaryMessenger mockBinaryMessenger;
+ @Mock public Camera camera;
+
+ InstanceManager testInstanceManager;
+
+ @Before
+ public void setUp() {
+ testInstanceManager = InstanceManager.open(identifier -> {});
+ }
+
+ @After
+ public void tearDown() {
+ testInstanceManager.close();
+ }
+
+ @Test
+ public void flutterApiCreateTest() {
+ final CameraFlutterApiImpl spyFlutterApi =
+ spy(new CameraFlutterApiImpl(mockBinaryMessenger, testInstanceManager));
+
+ spyFlutterApi.create(camera, reply -> {});
+
+ final long identifier =
+ Objects.requireNonNull(testInstanceManager.getIdentifierForStrongReference(camera));
+ verify(spyFlutterApi).create(eq(identifier), any());
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java
index 5008e4e..47b4ed6 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java
@@ -13,8 +13,12 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.UseCase;
import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.lifecycle.LifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -100,6 +104,58 @@
}
@Test
+ public void bindToLifecycleTest() {
+ final ProcessCameraProviderHostApiImpl processCameraProviderHostApi =
+ new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context);
+ final Camera mockCamera = mock(Camera.class);
+ final CameraSelector mockCameraSelector = mock(CameraSelector.class);
+ final UseCase mockUseCase = mock(UseCase.class);
+ UseCase[] mockUseCases = new UseCase[] {mockUseCase};
+
+ LifecycleOwner mockLifecycleOwner = mock(LifecycleOwner.class);
+ processCameraProviderHostApi.setLifecycleOwner(mockLifecycleOwner);
+
+ testInstanceManager.addDartCreatedInstance(processCameraProvider, 0);
+ testInstanceManager.addDartCreatedInstance(mockCameraSelector, 1);
+ testInstanceManager.addDartCreatedInstance(mockUseCase, 2);
+ testInstanceManager.addDartCreatedInstance(mockCamera, 3);
+
+ when(processCameraProvider.bindToLifecycle(
+ mockLifecycleOwner, mockCameraSelector, mockUseCases))
+ .thenReturn(mockCamera);
+
+ assertEquals(
+ processCameraProviderHostApi.bindToLifecycle(0L, 1L, Arrays.asList(2L)), Long.valueOf(3));
+ verify(processCameraProvider)
+ .bindToLifecycle(mockLifecycleOwner, mockCameraSelector, mockUseCases);
+ }
+
+ @Test
+ public void unbindTest() {
+ final ProcessCameraProviderHostApiImpl processCameraProviderHostApi =
+ new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context);
+ final UseCase mockUseCase = mock(UseCase.class);
+ UseCase[] mockUseCases = new UseCase[] {mockUseCase};
+
+ testInstanceManager.addDartCreatedInstance(processCameraProvider, 0);
+ testInstanceManager.addDartCreatedInstance(mockUseCase, 1);
+
+ processCameraProviderHostApi.unbind(0L, Arrays.asList(1L));
+ verify(processCameraProvider).unbind(mockUseCases);
+ }
+
+ @Test
+ public void unbindAllTest() {
+ final ProcessCameraProviderHostApiImpl processCameraProviderHostApi =
+ new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context);
+
+ testInstanceManager.addDartCreatedInstance(processCameraProvider, 0);
+
+ processCameraProviderHostApi.unbindAll(0L);
+ verify(processCameraProvider).unbindAll();
+ }
+
+ @Test
public void flutterApiCreateTest() {
final ProcessCameraProviderFlutterApiImpl spyFlutterApi =
spy(new ProcessCameraProviderFlutterApiImpl(mockBinaryMessenger, testInstanceManager));
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 9c6564a..620831b 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,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'camera.dart';
import 'camera_info.dart';
import 'camera_selector.dart';
import 'camerax_library.pigeon.dart';
@@ -13,6 +14,7 @@
/// Creates a [AndroidCameraXCameraFlutterApis].
AndroidCameraXCameraFlutterApis({
JavaObjectFlutterApiImpl? javaObjectFlutterApi,
+ CameraFlutterApiImpl? cameraFlutterApi,
CameraInfoFlutterApiImpl? cameraInfoFlutterApi,
CameraSelectorFlutterApiImpl? cameraSelectorFlutterApi,
ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApi,
@@ -25,6 +27,7 @@
cameraSelectorFlutterApi ?? CameraSelectorFlutterApiImpl();
this.processCameraProviderFlutterApi = processCameraProviderFlutterApi ??
ProcessCameraProviderFlutterApiImpl();
+ this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl();
}
static bool _haveBeenSetUp = false;
@@ -48,6 +51,9 @@
late final ProcessCameraProviderFlutterApiImpl
processCameraProviderFlutterApi;
+ /// Flutter Api for [Camera].
+ late final CameraFlutterApiImpl cameraFlutterApi;
+
/// Ensures all the Flutter APIs have been setup to receive calls from native code.
void ensureSetUp() {
if (!_haveBeenSetUp) {
@@ -55,6 +61,7 @@
CameraInfoFlutterApi.setup(cameraInfoFlutterApi);
CameraSelectorFlutterApi.setup(cameraSelectorFlutterApi);
ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi);
+ CameraFlutterApi.setup(cameraFlutterApi);
_haveBeenSetUp = true;
}
}
diff --git a/packages/camera/camera_android_camerax/lib/src/camera.dart b/packages/camera/camera_android_camerax/lib/src/camera.dart
new file mode 100644
index 0000000..0a3820c
--- /dev/null
+++ b/packages/camera/camera_android_camerax/lib/src/camera.dart
@@ -0,0 +1,53 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart' show BinaryMessenger;
+
+import 'android_camera_camerax_flutter_api_impls.dart';
+import 'camerax_library.pigeon.dart';
+import 'instance_manager.dart';
+import 'java_object.dart';
+
+/// The interface used to control the flow of data of use cases, control the
+/// camera, and publich the state of the camera.
+///
+/// See https://developer.android.com/reference/androidx/camera/core/Camera.
+class Camera extends JavaObject {
+ /// Constructs a [Camera] that is not automatically attached to a native object.
+ Camera.detached({super.binaryMessenger, super.instanceManager})
+ : super.detached() {
+ AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
+ }
+}
+
+/// Flutter API implementation of [Camera].
+class CameraFlutterApiImpl implements CameraFlutterApi {
+ /// Constructs a [CameraSelectorFlutterApiImpl].
+ CameraFlutterApiImpl({
+ 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(
+ Camera.detached(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager),
+ identifier,
+ onCopy: (Camera original) {
+ return Camera.detached(
+ binaryMessenger: binaryMessenger, instanceManager: instanceManager);
+ },
+ );
+ }
+}
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart
index c0b0523..636a375 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart
@@ -338,6 +338,89 @@
return (replyMap['result'] as List<Object?>?)!.cast<int?>();
}
}
+
+ Future<int> bindToLifecycle(int arg_identifier,
+ int arg_cameraSelectorIdentifier, List<int?> arg_useCaseIds) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap = await channel.send(<Object?>[
+ arg_identifier,
+ arg_cameraSelectorIdentifier,
+ arg_useCaseIds
+ ]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else if (replyMap['result'] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyMap['result'] as int?)!;
+ }
+ }
+
+ Future<void> unbind(int arg_identifier, List<int?> arg_useCaseIds) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_identifier, arg_useCaseIds])
+ as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return;
+ }
+ }
+
+ Future<void> unbindAll(int arg_identifier) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_identifier]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return;
+ }
+ }
}
class _ProcessCameraProviderFlutterApiCodec extends StandardMessageCodec {
@@ -372,3 +455,34 @@
}
}
}
+
+class _CameraFlutterApiCodec extends StandardMessageCodec {
+ const _CameraFlutterApiCodec();
+}
+
+abstract class CameraFlutterApi {
+ static const MessageCodec<Object?> codec = _CameraFlutterApiCodec();
+
+ void create(int identifier);
+ static void setup(CameraFlutterApi? api, {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.CameraFlutterApi.create', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.CameraFlutterApi.create was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_identifier = (args[0] as int?);
+ assert(arg_identifier != null,
+ 'Argument for dev.flutter.pigeon.CameraFlutterApi.create was null, expected non-null int.');
+ api.create(arg_identifier!);
+ return;
+ });
+ }
+ }
+ }
+}
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 5a67fa7..30c8162 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
@@ -5,10 +5,13 @@
import 'package:flutter/services.dart';
import 'android_camera_camerax_flutter_api_impls.dart';
+import 'camera.dart';
import 'camera_info.dart';
+import 'camera_selector.dart';
import 'camerax_library.pigeon.dart';
import 'instance_manager.dart';
import 'java_object.dart';
+import 'use_case.dart';
/// Provides an object to manage the camera.
///
@@ -42,6 +45,25 @@
Future<List<CameraInfo>> getAvailableCameraInfos() {
return _api.getAvailableCameraInfosFromInstances(this);
}
+
+ /// Binds the specified [UseCase]s to the lifecycle of the camera that it
+ /// returns.
+ Future<Camera> bindToLifecycle(
+ CameraSelector cameraSelector, List<UseCase> useCases) {
+ return _api.bindToLifecycleFromInstances(this, cameraSelector, useCases);
+ }
+
+ /// Unbinds specified [UseCase]s from the lifecycle of the camera that this
+ /// instance tracks.
+ void unbind(List<UseCase> useCases) {
+ _api.unbindFromInstances(this, useCases);
+ }
+
+ /// Unbinds all previously bound [UseCase]s from the lifecycle of the camera
+ /// that this tracks.
+ void unbindAll() {
+ _api.unbindAllFromInstances(this);
+ }
}
/// Host API implementation of [ProcessCameraProvider].
@@ -69,22 +91,71 @@
as ProcessCameraProvider;
}
+ /// Gets identifier that the [instanceManager] has set for
+ /// the [ProcessCameraProvider] instance.
+ int getProcessCameraProviderIdentifier(ProcessCameraProvider instance) {
+ final int? identifier = instanceManager.getIdentifier(instance);
+
+ assert(identifier != null,
+ 'No ProcessCameraProvider has the identifer of that which was requested.');
+ return identifier!;
+ }
+
/// Retrives the list of CameraInfos corresponding to the available cameras.
Future<List<CameraInfo>> getAvailableCameraInfosFromInstances(
ProcessCameraProvider instance) async {
- int? identifier = instanceManager.getIdentifier(instance);
- identifier ??= instanceManager.addDartCreatedInstance(instance,
- onCopy: (ProcessCameraProvider original) {
- return ProcessCameraProvider.detached(
- binaryMessenger: binaryMessenger, instanceManager: instanceManager);
- });
-
+ final int identifier = getProcessCameraProviderIdentifier(instance);
final List<int?> cameraInfos = await getAvailableCameraInfos(identifier);
return cameraInfos
.map<CameraInfo>((int? id) =>
instanceManager.getInstanceWithWeakReference(id!)! as CameraInfo)
.toList();
}
+
+ /// Binds the specified [UseCase]s to the lifecycle of the camera which
+ /// the provided [ProcessCameraProvider] instance tracks.
+ ///
+ /// The instance of the camera whose lifecycle the [UseCase]s are bound to
+ /// is returned.
+ Future<Camera> bindToLifecycleFromInstances(
+ ProcessCameraProvider instance,
+ CameraSelector cameraSelector,
+ List<UseCase> useCases,
+ ) async {
+ final int identifier = getProcessCameraProviderIdentifier(instance);
+ final List<int> useCaseIds = useCases
+ .map<int>((UseCase useCase) => instanceManager.getIdentifier(useCase)!)
+ .toList();
+
+ final int cameraIdentifier = await bindToLifecycle(
+ identifier,
+ instanceManager.getIdentifier(cameraSelector)!,
+ useCaseIds,
+ );
+ return instanceManager.getInstanceWithWeakReference(cameraIdentifier)!
+ as Camera;
+ }
+
+ /// Unbinds specified [UseCase]s from the lifecycle of the camera which the
+ /// provided [ProcessCameraProvider] instance tracks.
+ void unbindFromInstances(
+ ProcessCameraProvider instance,
+ List<UseCase> useCases,
+ ) {
+ final int identifier = getProcessCameraProviderIdentifier(instance);
+ final List<int> useCaseIds = useCases
+ .map<int>((UseCase useCase) => instanceManager.getIdentifier(useCase)!)
+ .toList();
+
+ unbind(identifier, useCaseIds);
+ }
+
+ /// Unbinds all previously bound [UseCase]s from the lifecycle of the camera
+ /// which the provided [ProcessCameraProvider] instance tracks.
+ void unbindAllFromInstances(ProcessCameraProvider instance) {
+ final int identifier = getProcessCameraProviderIdentifier(instance);
+ unbindAll(identifier);
+ }
}
/// Flutter API Implementation of [ProcessCameraProvider].
diff --git a/packages/camera/camera_android_camerax/lib/src/use_case.dart b/packages/camera/camera_android_camerax/lib/src/use_case.dart
new file mode 100644
index 0000000..f8910d9
--- /dev/null
+++ b/packages/camera/camera_android_camerax/lib/src/use_case.dart
@@ -0,0 +1,14 @@
+// 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 'java_object.dart';
+
+/// An object representing the different functionalitites of the camera.
+///
+/// See https://developer.android.com/reference/androidx/camera/core/UseCase.
+class UseCase extends JavaObject {
+ /// Creates a detached [UseCase].
+ UseCase.detached({super.binaryMessenger, super.instanceManager})
+ : super.detached();
+}
diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
index 4d7d969..edd2059 100644
--- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
+++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
@@ -64,9 +64,21 @@
int getInstance();
List<int> getAvailableCameraInfos(int identifier);
+
+ int bindToLifecycle(
+ int identifier, int cameraSelectorIdentifier, List<int> useCaseIds);
+
+ void unbind(int identifier, List<int> useCaseIds);
+
+ void unbindAll(int identifier);
}
@FlutterApi()
abstract class ProcessCameraProviderFlutterApi {
void create(int identifier);
}
+
+@FlutterApi()
+abstract class CameraFlutterApi {
+ void create(int identifier);
+}
diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart
index e1f1e3c..63ec03c 100644
--- a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart
@@ -1,4 +1,4 @@
-// Mocks generated by Mockito 5.3.0 from annotations
+// Mocks generated by Mockito 5.3.2 from annotations
// in camera_android_camerax/test/camera_info_test.dart.
// Do not manually edit this file.
@@ -29,6 +29,10 @@
@override
int getSensorRotationDegrees(int? identifier) => (super.noSuchMethod(
- Invocation.method(#getSensorRotationDegrees, [identifier]),
- returnValue: 0) as int);
+ Invocation.method(
+ #getSensorRotationDegrees,
+ [identifier],
+ ),
+ returnValue: 0,
+ ) as int);
}
diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart
index 456db1e..bb08c82 100644
--- a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart
@@ -1,4 +1,4 @@
-// Mocks generated by Mockito 5.3.0 from annotations
+// Mocks generated by Mockito 5.3.2 from annotations
// in camera_android_camerax/test/camera_selector_test.dart.
// Do not manually edit this file.
@@ -28,11 +28,33 @@
}
@override
- void create(int? identifier, int? lensFacing) =>
- super.noSuchMethod(Invocation.method(#create, [identifier, lensFacing]),
- returnValueForMissingStub: null);
+ void create(
+ int? identifier,
+ int? lensFacing,
+ ) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #create,
+ [
+ identifier,
+ lensFacing,
+ ],
+ ),
+ returnValueForMissingStub: null,
+ );
@override
- List<int?> filter(int? identifier, List<int?>? cameraInfoIds) => (super
- .noSuchMethod(Invocation.method(#filter, [identifier, cameraInfoIds]),
- returnValue: <int?>[]) as List<int?>);
+ List<int?> filter(
+ int? identifier,
+ List<int?>? cameraInfoIds,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #filter,
+ [
+ identifier,
+ cameraInfoIds,
+ ],
+ ),
+ returnValue: <int?>[],
+ ) as List<int?>);
}
diff --git a/packages/camera/camera_android_camerax/test/camera_test.dart b/packages/camera/camera_android_camerax/test/camera_test.dart
new file mode 100644
index 0000000..c294828
--- /dev/null
+++ b/packages/camera/camera_android_camerax/test/camera_test.dart
@@ -0,0 +1,26 @@
+// 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/camera.dart';
+import 'package:camera_android_camerax/src/instance_manager.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ group('Camera', () {
+ test('flutterApiCreateTest', () {
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+ final CameraFlutterApiImpl flutterApi = CameraFlutterApiImpl(
+ instanceManager: instanceManager,
+ );
+
+ flutterApi.create(0);
+
+ expect(instanceManager.getInstanceWithWeakReference(0), isA<Camera>());
+ });
+ });
+}
diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart
index 65e7d00..c4f56f6 100644
--- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart
+++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart
@@ -2,9 +2,12 @@
// 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/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/instance_manager.dart';
import 'package:camera_android_camerax/src/process_camera_provider.dart';
+import 'package:camera_android_camerax/src/use_case.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@@ -78,6 +81,114 @@
verify(mockApi.getAvailableCameraInfos(0));
});
+ test('bindToLifecycleTest', () async {
+ final MockTestProcessCameraProviderHostApi mockApi =
+ MockTestProcessCameraProviderHostApi();
+ TestProcessCameraProviderHostApi.setup(mockApi);
+
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+ final ProcessCameraProvider processCameraProvider =
+ ProcessCameraProvider.detached(
+ instanceManager: instanceManager,
+ );
+ final CameraSelector fakeCameraSelector =
+ CameraSelector.detached(instanceManager: instanceManager);
+ final UseCase fakeUseCase =
+ UseCase.detached(instanceManager: instanceManager);
+ final Camera fakeCamera =
+ Camera.detached(instanceManager: instanceManager);
+
+ instanceManager.addHostCreatedInstance(
+ processCameraProvider,
+ 0,
+ onCopy: (_) => ProcessCameraProvider.detached(),
+ );
+ instanceManager.addHostCreatedInstance(
+ fakeCameraSelector,
+ 1,
+ onCopy: (_) => CameraSelector.detached(),
+ );
+ instanceManager.addHostCreatedInstance(
+ fakeUseCase,
+ 2,
+ onCopy: (_) => UseCase.detached(),
+ );
+ instanceManager.addHostCreatedInstance(
+ fakeCamera,
+ 3,
+ onCopy: (_) => Camera.detached(),
+ );
+
+ when(mockApi.bindToLifecycle(0, 1, <int>[2])).thenReturn(3);
+ expect(
+ await processCameraProvider
+ .bindToLifecycle(fakeCameraSelector, <UseCase>[fakeUseCase]),
+ equals(fakeCamera));
+ verify(mockApi.bindToLifecycle(0, 1, <int>[2]));
+ });
+
+ test('unbindTest', () async {
+ final MockTestProcessCameraProviderHostApi mockApi =
+ MockTestProcessCameraProviderHostApi();
+ TestProcessCameraProviderHostApi.setup(mockApi);
+
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+ final ProcessCameraProvider processCameraProvider =
+ ProcessCameraProvider.detached(
+ instanceManager: instanceManager,
+ );
+ final UseCase fakeUseCase =
+ UseCase.detached(instanceManager: instanceManager);
+
+ instanceManager.addHostCreatedInstance(
+ processCameraProvider,
+ 0,
+ onCopy: (_) => ProcessCameraProvider.detached(),
+ );
+ instanceManager.addHostCreatedInstance(
+ fakeUseCase,
+ 1,
+ onCopy: (_) => UseCase.detached(),
+ );
+
+ processCameraProvider.unbind(<UseCase>[fakeUseCase]);
+ verify(mockApi.unbind(0, <int>[1]));
+ });
+
+ test('unbindAllTest', () async {
+ final MockTestProcessCameraProviderHostApi mockApi =
+ MockTestProcessCameraProviderHostApi();
+ TestProcessCameraProviderHostApi.setup(mockApi);
+
+ final InstanceManager instanceManager = InstanceManager(
+ onWeakReferenceRemoved: (_) {},
+ );
+ final ProcessCameraProvider processCameraProvider =
+ ProcessCameraProvider.detached(
+ instanceManager: instanceManager,
+ );
+ final UseCase fakeUseCase =
+ UseCase.detached(instanceManager: instanceManager);
+
+ instanceManager.addHostCreatedInstance(
+ processCameraProvider,
+ 0,
+ onCopy: (_) => ProcessCameraProvider.detached(),
+ );
+ instanceManager.addHostCreatedInstance(
+ fakeUseCase,
+ 1,
+ onCopy: (_) => UseCase.detached(),
+ );
+
+ processCameraProvider.unbind(<UseCase>[fakeUseCase]);
+ verify(mockApi.unbind(0, <int>[1]));
+ });
+
test('flutterApiCreateTest', () {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart
index 9fcfe69..7b0ca76 100644
--- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart
@@ -1,4 +1,4 @@
-// Mocks generated by Mockito 5.3.0 from annotations
+// Mocks generated by Mockito 5.3.2 from annotations
// in camera_android_camerax/test/process_camera_provider_test.dart.
// Do not manually edit this file.
@@ -30,11 +30,59 @@
}
@override
- _i3.Future<int> getInstance() =>
- (super.noSuchMethod(Invocation.method(#getInstance, []),
- returnValue: _i3.Future<int>.value(0)) as _i3.Future<int>);
+ _i3.Future<int> getInstance() => (super.noSuchMethod(
+ Invocation.method(
+ #getInstance,
+ [],
+ ),
+ returnValue: _i3.Future<int>.value(0),
+ ) as _i3.Future<int>);
@override
List<int?> getAvailableCameraInfos(int? identifier) => (super.noSuchMethod(
- Invocation.method(#getAvailableCameraInfos, [identifier]),
- returnValue: <int?>[]) as List<int?>);
+ Invocation.method(
+ #getAvailableCameraInfos,
+ [identifier],
+ ),
+ returnValue: <int?>[],
+ ) as List<int?>);
+ @override
+ int bindToLifecycle(
+ int? identifier,
+ int? cameraSelectorIdentifier,
+ List<int?>? useCaseIds,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #bindToLifecycle,
+ [
+ identifier,
+ cameraSelectorIdentifier,
+ useCaseIds,
+ ],
+ ),
+ returnValue: 0,
+ ) as int);
+ @override
+ void unbind(
+ int? identifier,
+ List<int?>? useCaseIds,
+ ) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #unbind,
+ [
+ identifier,
+ useCaseIds,
+ ],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void unbindAll(int? identifier) => super.noSuchMethod(
+ Invocation.method(
+ #unbindAll,
+ [identifier],
+ ),
+ returnValueForMissingStub: null,
+ );
}
diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart
index 2196b73..c6afe06 100644
--- a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart
+++ b/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart
@@ -146,6 +146,10 @@
Future<int> getInstance();
List<int?> getAvailableCameraInfos(int identifier);
+ int bindToLifecycle(
+ int identifier, int cameraSelectorIdentifier, List<int?> useCaseIds);
+ void unbind(int identifier, List<int?> useCaseIds);
+ void unbindAll(int identifier);
static void setup(TestProcessCameraProviderHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@@ -183,5 +187,75 @@
});
}
}
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle',
+ codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_identifier = (args[0] as int?);
+ assert(arg_identifier != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null int.');
+ final int? arg_cameraSelectorIdentifier = (args[1] as int?);
+ assert(arg_cameraSelectorIdentifier != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null int.');
+ final List<int?>? arg_useCaseIds =
+ (args[2] as List<Object?>?)?.cast<int?>();
+ assert(arg_useCaseIds != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null List<int?>.');
+ final int output = api.bindToLifecycle(
+ arg_identifier!, arg_cameraSelectorIdentifier!, arg_useCaseIds!);
+ return <Object?, Object?>{'result': output};
+ });
+ }
+ }
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_identifier = (args[0] as int?);
+ assert(arg_identifier != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null, expected non-null int.');
+ final List<int?>? arg_useCaseIds =
+ (args[1] as List<Object?>?)?.cast<int?>();
+ assert(arg_useCaseIds != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null, expected non-null List<int?>.');
+ api.unbind(arg_identifier!, arg_useCaseIds!);
+ return <Object?, Object?>{};
+ });
+ }
+ }
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_identifier = (args[0] as int?);
+ assert(arg_identifier != null,
+ 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null, expected non-null int.');
+ api.unbindAll(arg_identifier!);
+ return <Object?, Object?>{};
+ });
+ }
+ }
}
}