mirror of
https://github.com/flutter/packages.git
synced 2025-07-03 09:08:54 +08:00
[camerax] Implements torch mode (#4903)
Implements the torch flash mode. Also wraps classes necessary for the implementation (a method in `Camera`, `CameraControl`). Fixes https://github.com/flutter/flutter/issues/120715. Fixes https://github.com/flutter/flutter/issues/115846. Part of https://github.com/flutter/flutter/issues/115847.
This commit is contained in:
@ -1,3 +1,7 @@
|
|||||||
|
## 0.5.0+19
|
||||||
|
|
||||||
|
* Implements torch flash mode.
|
||||||
|
|
||||||
## 0.5.0+18
|
## 0.5.0+18
|
||||||
|
|
||||||
* Implements `startVideoCapturing`.
|
* Implements `startVideoCapturing`.
|
||||||
|
@ -35,10 +35,6 @@ and thus, the plugin will fall back to 480p if configured with a
|
|||||||
|
|
||||||
`lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
|
`lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
|
||||||
|
|
||||||
### Torch mode \[[Issue #120715][120715]\]
|
|
||||||
|
|
||||||
Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing.
|
|
||||||
|
|
||||||
### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\]
|
### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\]
|
||||||
|
|
||||||
`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.
|
`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.
|
||||||
|
@ -25,6 +25,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
|||||||
private VideoCaptureHostApiImpl videoCaptureHostApiImpl;
|
private VideoCaptureHostApiImpl videoCaptureHostApiImpl;
|
||||||
private ImageAnalysisHostApiImpl imageAnalysisHostApiImpl;
|
private ImageAnalysisHostApiImpl imageAnalysisHostApiImpl;
|
||||||
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
|
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
|
||||||
|
private CameraControlHostApiImpl cameraControlHostApiImpl;
|
||||||
public @Nullable SystemServicesHostApiImpl systemServicesHostApiImpl;
|
public @Nullable SystemServicesHostApiImpl systemServicesHostApiImpl;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@ -81,7 +82,8 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
|||||||
GeneratedCameraXLibrary.LiveDataHostApi.setup(binaryMessenger, liveDataHostApiImpl);
|
GeneratedCameraXLibrary.LiveDataHostApi.setup(binaryMessenger, liveDataHostApiImpl);
|
||||||
GeneratedCameraXLibrary.ObserverHostApi.setup(
|
GeneratedCameraXLibrary.ObserverHostApi.setup(
|
||||||
binaryMessenger, new ObserverHostApiImpl(binaryMessenger, instanceManager));
|
binaryMessenger, new ObserverHostApiImpl(binaryMessenger, instanceManager));
|
||||||
imageAnalysisHostApiImpl = new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager);
|
imageAnalysisHostApiImpl =
|
||||||
|
new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager, context);
|
||||||
GeneratedCameraXLibrary.ImageAnalysisHostApi.setup(binaryMessenger, imageAnalysisHostApiImpl);
|
GeneratedCameraXLibrary.ImageAnalysisHostApi.setup(binaryMessenger, imageAnalysisHostApiImpl);
|
||||||
GeneratedCameraXLibrary.AnalyzerHostApi.setup(
|
GeneratedCameraXLibrary.AnalyzerHostApi.setup(
|
||||||
binaryMessenger, new AnalyzerHostApiImpl(binaryMessenger, instanceManager));
|
binaryMessenger, new AnalyzerHostApiImpl(binaryMessenger, instanceManager));
|
||||||
@ -107,6 +109,8 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
|||||||
binaryMessenger, new FallbackStrategyHostApiImpl(instanceManager));
|
binaryMessenger, new FallbackStrategyHostApiImpl(instanceManager));
|
||||||
GeneratedCameraXLibrary.QualitySelectorHostApi.setup(
|
GeneratedCameraXLibrary.QualitySelectorHostApi.setup(
|
||||||
binaryMessenger, new QualitySelectorHostApiImpl(instanceManager));
|
binaryMessenger, new QualitySelectorHostApiImpl(instanceManager));
|
||||||
|
cameraControlHostApiImpl = new CameraControlHostApiImpl(instanceManager, context);
|
||||||
|
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -128,7 +132,6 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
|||||||
Activity activity = activityPluginBinding.getActivity();
|
Activity activity = activityPluginBinding.getActivity();
|
||||||
|
|
||||||
setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());
|
setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());
|
||||||
updateContext(activity);
|
|
||||||
|
|
||||||
if (activity instanceof LifecycleOwner) {
|
if (activity instanceof LifecycleOwner) {
|
||||||
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
|
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
|
||||||
@ -183,5 +186,8 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
|||||||
if (imageAnalysisHostApiImpl != null) {
|
if (imageAnalysisHostApiImpl != null) {
|
||||||
imageAnalysisHostApiImpl.setContext(context);
|
imageAnalysisHostApiImpl.setContext(context);
|
||||||
}
|
}
|
||||||
|
if (cameraControlHostApiImpl != null) {
|
||||||
|
cameraControlHostApiImpl.setContext(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
// 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.camera.core.CameraControl;
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger;
|
||||||
|
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraControlFlutterApi;
|
||||||
|
|
||||||
|
public class CameraControlFlutterApiImpl extends CameraControlFlutterApi {
|
||||||
|
private final @NonNull InstanceManager instanceManager;
|
||||||
|
|
||||||
|
public CameraControlFlutterApiImpl(
|
||||||
|
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
|
||||||
|
super(binaryMessenger);
|
||||||
|
this.instanceManager = instanceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link CameraControl} instance in Dart. {@code reply} is not used so it can be empty.
|
||||||
|
*/
|
||||||
|
void create(CameraControl cameraControl, Reply<Void> reply) {
|
||||||
|
if (!instanceManager.containsInstance(cameraControl)) {
|
||||||
|
create(instanceManager.addHostCreatedInstance(cameraControl), reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
// 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.VisibleForTesting;
|
||||||
|
import androidx.camera.core.CameraControl;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraControlHostApi;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host API implementation for {@link CameraControl}.
|
||||||
|
*
|
||||||
|
* <p>This class handles instantiating and adding native object instances that are attached to a
|
||||||
|
* Dart instance or handle method calls on the associated native class or an instance of the class.
|
||||||
|
*/
|
||||||
|
public class CameraControlHostApiImpl implements CameraControlHostApi {
|
||||||
|
private final InstanceManager instanceManager;
|
||||||
|
private final CameraControlProxy proxy;
|
||||||
|
|
||||||
|
/** Proxy for constructors and static method of {@link CameraControl}. */
|
||||||
|
@VisibleForTesting
|
||||||
|
public static class CameraControlProxy {
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
/** Enables or disables the torch of the specified {@link CameraControl} instance. */
|
||||||
|
@NonNull
|
||||||
|
public void enableTorch(
|
||||||
|
@NonNull CameraControl cameraControl,
|
||||||
|
@NonNull Boolean torch,
|
||||||
|
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
|
||||||
|
ListenableFuture<Void> enableTorchFuture = cameraControl.enableTorch(torch);
|
||||||
|
|
||||||
|
Futures.addCallback(
|
||||||
|
enableTorchFuture,
|
||||||
|
new FutureCallback<Void>() {
|
||||||
|
public void onSuccess(Void voidResult) {
|
||||||
|
result.success(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
result.error(t);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ContextCompat.getMainExecutor(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@link CameraControlHostApiImpl}.
|
||||||
|
*
|
||||||
|
* @param instanceManager maintains instances stored to communicate with attached Dart objects
|
||||||
|
*/
|
||||||
|
public CameraControlHostApiImpl(
|
||||||
|
@NonNull InstanceManager instanceManager, @NonNull Context context) {
|
||||||
|
this(instanceManager, new CameraControlProxy(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@link CameraControlHostApiImpl}.
|
||||||
|
*
|
||||||
|
* @param instanceManager maintains instances stored to communicate with attached Dart objects
|
||||||
|
* @param proxy proxy for constructors and static method of {@link CameraControl}
|
||||||
|
* @param context {@link Context} used to retrieve {@code Executor} used to enable torch mode
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
CameraControlHostApiImpl(
|
||||||
|
@NonNull InstanceManager instanceManager,
|
||||||
|
@NonNull CameraControlProxy proxy,
|
||||||
|
@NonNull Context context) {
|
||||||
|
this.instanceManager = instanceManager;
|
||||||
|
this.proxy = proxy;
|
||||||
|
proxy.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode.
|
||||||
|
*
|
||||||
|
* <p>If using the camera plugin in an add-to-app context, ensure that a new instance of the
|
||||||
|
* {@code CameraControl} is fetched via {@code #enableTorch} anytime the context changes.
|
||||||
|
*/
|
||||||
|
public void setContext(@NonNull Context context) {
|
||||||
|
this.proxy.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableTorch(
|
||||||
|
@NonNull Long identifier,
|
||||||
|
@NonNull Boolean torch,
|
||||||
|
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
|
||||||
|
proxy.enableTorch(
|
||||||
|
Objects.requireNonNull(instanceManager.getInstance(identifier)), torch, result);
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ package io.flutter.plugins.camerax;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
|
import androidx.camera.core.CameraControl;
|
||||||
import androidx.camera.core.CameraInfo;
|
import androidx.camera.core.CameraInfo;
|
||||||
import io.flutter.plugin.common.BinaryMessenger;
|
import io.flutter.plugin.common.BinaryMessenger;
|
||||||
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraHostApi;
|
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraHostApi;
|
||||||
@ -28,14 +29,33 @@ public class CameraHostApiImpl implements CameraHostApi {
|
|||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public Long getCameraInfo(@NonNull Long identifier) {
|
public Long getCameraInfo(@NonNull Long identifier) {
|
||||||
Camera camera = (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier));
|
Camera camera = getCameraInstance(identifier);
|
||||||
CameraInfo cameraInfo = camera.getCameraInfo();
|
CameraInfo cameraInfo = camera.getCameraInfo();
|
||||||
|
|
||||||
if (!instanceManager.containsInstance(cameraInfo)) {
|
|
||||||
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
|
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
|
||||||
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
|
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
|
||||||
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
|
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
|
||||||
}
|
|
||||||
return instanceManager.getIdentifierForStrongReference(cameraInfo);
|
return instanceManager.getIdentifierForStrongReference(cameraInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the {@link CameraControl} instance that provides access to asynchronous operations
|
||||||
|
* like zoom and focus & metering on the {@link Camera} instance with the specified identifier.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Long getCameraControl(@NonNull Long identifier) {
|
||||||
|
Camera camera = getCameraInstance(identifier);
|
||||||
|
CameraControl cameraControl = camera.getCameraControl();
|
||||||
|
|
||||||
|
CameraControlFlutterApiImpl cameraControlFlutterApiImpl =
|
||||||
|
new CameraControlFlutterApiImpl(binaryMessenger, instanceManager);
|
||||||
|
cameraControlFlutterApiImpl.create(cameraControl, reply -> {});
|
||||||
|
return instanceManager.getIdentifierForStrongReference(cameraControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieives the {@link Camera} instance associated with the specified {@code identifier}. */
|
||||||
|
private Camera getCameraInstance(@NonNull Long identifier) {
|
||||||
|
return (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1136,6 +1136,9 @@ public class GeneratedCameraXLibrary {
|
|||||||
@NonNull
|
@NonNull
|
||||||
Long getCameraInfo(@NonNull Long identifier);
|
Long getCameraInfo(@NonNull Long identifier);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Long getCameraControl(@NonNull Long identifier);
|
||||||
|
|
||||||
/** The codec used by CameraHostApi. */
|
/** The codec used by CameraHostApi. */
|
||||||
static @NonNull MessageCodec<Object> getCodec() {
|
static @NonNull MessageCodec<Object> getCodec() {
|
||||||
return new StandardMessageCodec();
|
return new StandardMessageCodec();
|
||||||
@ -1166,6 +1169,31 @@ public class GeneratedCameraXLibrary {
|
|||||||
channel.setMessageHandler(null);
|
channel.setMessageHandler(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(
|
||||||
|
binaryMessenger, "dev.flutter.pigeon.CameraHostApi.getCameraControl", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler(
|
||||||
|
(message, reply) -> {
|
||||||
|
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||||
|
Number identifierArg = (Number) args.get(0);
|
||||||
|
try {
|
||||||
|
Long output =
|
||||||
|
api.getCameraControl(
|
||||||
|
(identifierArg == null) ? null : identifierArg.longValue());
|
||||||
|
wrapped.add(0, output);
|
||||||
|
} catch (Throwable exception) {
|
||||||
|
ArrayList<Object> 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. */
|
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
|
||||||
@ -3203,4 +3231,82 @@ public class GeneratedCameraXLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
public interface CameraControlHostApi {
|
||||||
|
|
||||||
|
void enableTorch(
|
||||||
|
@NonNull Long identifier, @NonNull Boolean torch, @NonNull Result<Void> result);
|
||||||
|
|
||||||
|
/** The codec used by CameraControlHostApi. */
|
||||||
|
static @NonNull MessageCodec<Object> getCodec() {
|
||||||
|
return new StandardMessageCodec();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets up an instance of `CameraControlHostApi` to handle messages through the
|
||||||
|
* `binaryMessenger`.
|
||||||
|
*/
|
||||||
|
static void setup(
|
||||||
|
@NonNull BinaryMessenger binaryMessenger, @Nullable CameraControlHostApi api) {
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(
|
||||||
|
binaryMessenger, "dev.flutter.pigeon.CameraControlHostApi.enableTorch", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler(
|
||||||
|
(message, reply) -> {
|
||||||
|
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||||
|
Number identifierArg = (Number) args.get(0);
|
||||||
|
Boolean torchArg = (Boolean) args.get(1);
|
||||||
|
Result<Void> resultCallback =
|
||||||
|
new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.add(0, null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void error(Throwable error) {
|
||||||
|
ArrayList<Object> wrappedError = wrapError(error);
|
||||||
|
reply.reply(wrappedError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.enableTorch(
|
||||||
|
(identifierArg == null) ? null : identifierArg.longValue(),
|
||||||
|
torchArg,
|
||||||
|
resultCallback);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
|
||||||
|
public static class CameraControlFlutterApi {
|
||||||
|
private final @NonNull BinaryMessenger binaryMessenger;
|
||||||
|
|
||||||
|
public CameraControlFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
|
||||||
|
this.binaryMessenger = argBinaryMessenger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Public interface for sending reply. */
|
||||||
|
@SuppressWarnings("UnknownNullness")
|
||||||
|
public interface Reply<T> {
|
||||||
|
void reply(T reply);
|
||||||
|
}
|
||||||
|
/** The codec used by CameraControlFlutterApi. */
|
||||||
|
static @NonNull MessageCodec<Object> getCodec() {
|
||||||
|
return new StandardMessageCodec();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(
|
||||||
|
binaryMessenger, "dev.flutter.pigeon.CameraControlFlutterApi.create", getCodec());
|
||||||
|
channel.send(
|
||||||
|
new ArrayList<Object>(Collections.singletonList(identifierArg)),
|
||||||
|
channelReply -> callback.reply(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,12 @@ public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi {
|
|||||||
@VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy();
|
@VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy();
|
||||||
|
|
||||||
public ImageAnalysisHostApiImpl(
|
public ImageAnalysisHostApiImpl(
|
||||||
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
|
@NonNull BinaryMessenger binaryMessenger,
|
||||||
|
@NonNull InstanceManager instanceManager,
|
||||||
|
@NonNull Context context) {
|
||||||
this.binaryMessenger = binaryMessenger;
|
this.binaryMessenger = binaryMessenger;
|
||||||
this.instanceManager = instanceManager;
|
this.instanceManager = instanceManager;
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
// 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.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.camera.core.CameraControl;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
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.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
|
public class CameraControlTest {
|
||||||
|
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
@Mock public BinaryMessenger mockBinaryMessenger;
|
||||||
|
@Mock public CameraControl cameraControl;
|
||||||
|
|
||||||
|
InstanceManager testInstanceManager;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
testInstanceManager = InstanceManager.create(identifier -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
testInstanceManager.stopFinalizationListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enableTorch_turnsTorchModeOnAndOffAsExpected() {
|
||||||
|
try (MockedStatic<Futures> mockedFutures = Mockito.mockStatic(Futures.class)) {
|
||||||
|
final CameraControlHostApiImpl cameraControlHostApiImpl =
|
||||||
|
new CameraControlHostApiImpl(testInstanceManager, mock(Context.class));
|
||||||
|
final Long cameraControlIdentifier = 88L;
|
||||||
|
final boolean enableTorch = true;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final ListenableFuture<Void> enableTorchFuture = mock(ListenableFuture.class);
|
||||||
|
|
||||||
|
testInstanceManager.addDartCreatedInstance(cameraControl, cameraControlIdentifier);
|
||||||
|
|
||||||
|
when(cameraControl.enableTorch(true)).thenReturn(enableTorchFuture);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final ArgumentCaptor<FutureCallback<Void>> futureCallbackCaptor =
|
||||||
|
ArgumentCaptor.forClass(FutureCallback.class);
|
||||||
|
|
||||||
|
// Test turning on torch mode.
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final GeneratedCameraXLibrary.Result<Void> successfulMockResult =
|
||||||
|
mock(GeneratedCameraXLibrary.Result.class);
|
||||||
|
cameraControlHostApiImpl.enableTorch(
|
||||||
|
cameraControlIdentifier, enableTorch, successfulMockResult);
|
||||||
|
mockedFutures.verify(
|
||||||
|
() -> Futures.addCallback(eq(enableTorchFuture), futureCallbackCaptor.capture(), any()));
|
||||||
|
mockedFutures.clearInvocations();
|
||||||
|
|
||||||
|
FutureCallback<Void> successfulEnableTorchCallback = futureCallbackCaptor.getValue();
|
||||||
|
|
||||||
|
successfulEnableTorchCallback.onSuccess(mock(Void.class));
|
||||||
|
verify(successfulMockResult).success(null);
|
||||||
|
|
||||||
|
// Test turning off torch mode.
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final GeneratedCameraXLibrary.Result<Void> failedMockResult =
|
||||||
|
mock(GeneratedCameraXLibrary.Result.class);
|
||||||
|
final Throwable testThrowable = new Throwable();
|
||||||
|
cameraControlHostApiImpl.enableTorch(cameraControlIdentifier, enableTorch, failedMockResult);
|
||||||
|
mockedFutures.verify(
|
||||||
|
() -> Futures.addCallback(eq(enableTorchFuture), futureCallbackCaptor.capture(), any()));
|
||||||
|
|
||||||
|
FutureCallback<Void> failedEnableTorchCallback = futureCallbackCaptor.getValue();
|
||||||
|
|
||||||
|
failedEnableTorchCallback.onFailure(testThrowable);
|
||||||
|
verify(failedMockResult).error(testThrowable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
|
||||||
|
final CameraControlFlutterApiImpl spyFlutterApi =
|
||||||
|
spy(new CameraControlFlutterApiImpl(mockBinaryMessenger, testInstanceManager));
|
||||||
|
|
||||||
|
spyFlutterApi.create(cameraControl, reply -> {});
|
||||||
|
|
||||||
|
final long cameraControlIdentifier =
|
||||||
|
Objects.requireNonNull(testInstanceManager.getIdentifierForStrongReference(cameraControl));
|
||||||
|
verify(spyFlutterApi).create(eq(cameraControlIdentifier), any());
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
|
import androidx.camera.core.CameraControl;
|
||||||
import androidx.camera.core.CameraInfo;
|
import androidx.camera.core.CameraInfo;
|
||||||
import io.flutter.plugin.common.BinaryMessenger;
|
import io.flutter.plugin.common.BinaryMessenger;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -59,6 +60,23 @@ public class CameraTest {
|
|||||||
verify(camera).getCameraInfo();
|
verify(camera).getCameraInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCameraControl_retrievesExpectedCameraControlInstance() {
|
||||||
|
final CameraHostApiImpl cameraHostApiImpl =
|
||||||
|
new CameraHostApiImpl(mockBinaryMessenger, testInstanceManager);
|
||||||
|
final CameraControl mockCameraControl = mock(CameraControl.class);
|
||||||
|
final Long cameraIdentifier = 43L;
|
||||||
|
final Long mockCameraControlIdentifier = 79L;
|
||||||
|
|
||||||
|
testInstanceManager.addDartCreatedInstance(camera, cameraIdentifier);
|
||||||
|
testInstanceManager.addDartCreatedInstance(mockCameraControl, mockCameraControlIdentifier);
|
||||||
|
|
||||||
|
when(camera.getCameraControl()).thenReturn(mockCameraControl);
|
||||||
|
|
||||||
|
assertEquals(cameraHostApiImpl.getCameraControl(cameraIdentifier), mockCameraControlIdentifier);
|
||||||
|
verify(camera).getCameraControl();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
|
public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
|
||||||
final CameraFlutterApiImpl spyFlutterApi =
|
final CameraFlutterApiImpl spyFlutterApi =
|
||||||
|
@ -50,7 +50,7 @@ public class ImageAnalysisTest {
|
|||||||
@Test
|
@Test
|
||||||
public void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdentifier() {
|
public void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdentifier() {
|
||||||
final ImageAnalysisHostApiImpl hostApi =
|
final ImageAnalysisHostApiImpl hostApi =
|
||||||
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager);
|
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
|
||||||
final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class);
|
final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class);
|
||||||
final ImageAnalysis.Builder mockImageAnalysisBuilder = mock(ImageAnalysis.Builder.class);
|
final ImageAnalysis.Builder mockImageAnalysisBuilder = mock(ImageAnalysis.Builder.class);
|
||||||
final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class);
|
final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class);
|
||||||
@ -72,8 +72,7 @@ public class ImageAnalysisTest {
|
|||||||
@Test
|
@Test
|
||||||
public void setAnalyzer_makesCallToSetAnalyzerOnExpectedImageAnalysisInstance() {
|
public void setAnalyzer_makesCallToSetAnalyzerOnExpectedImageAnalysisInstance() {
|
||||||
final ImageAnalysisHostApiImpl hostApi =
|
final ImageAnalysisHostApiImpl hostApi =
|
||||||
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager);
|
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
|
||||||
hostApi.setContext(context);
|
|
||||||
|
|
||||||
final ImageAnalysis.Analyzer mockAnalyzer = mock(ImageAnalysis.Analyzer.class);
|
final ImageAnalysis.Analyzer mockAnalyzer = mock(ImageAnalysis.Analyzer.class);
|
||||||
final long analyzerIdentifier = 10;
|
final long analyzerIdentifier = 10;
|
||||||
@ -90,7 +89,7 @@ public class ImageAnalysisTest {
|
|||||||
@Test
|
@Test
|
||||||
public void clearAnalyzer_makesCallToClearAnalyzerOnExpectedImageAnalysisInstance() {
|
public void clearAnalyzer_makesCallToClearAnalyzerOnExpectedImageAnalysisInstance() {
|
||||||
final ImageAnalysisHostApiImpl hostApi =
|
final ImageAnalysisHostApiImpl hostApi =
|
||||||
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager);
|
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
|
||||||
final long instanceIdentifier = 22;
|
final long instanceIdentifier = 22;
|
||||||
|
|
||||||
instanceManager.addDartCreatedInstance(mockImageAnalysis, instanceIdentifier);
|
instanceManager.addDartCreatedInstance(mockImageAnalysis, instanceIdentifier);
|
||||||
|
@ -274,7 +274,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.flash_on),
|
icon: const Icon(Icons.flash_on),
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
onPressed: () {}, // TODO(camsim99): Add functionality back here.
|
onPressed: controller != null ? onFlashModeButtonPressed : null,
|
||||||
),
|
),
|
||||||
// The exposure and focus mode are currently not supported on the web.
|
// The exposure and focus mode are currently not supported on the web.
|
||||||
...!kIsWeb
|
...!kIsWeb
|
||||||
@ -326,28 +326,36 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
|
|||||||
color: controller?.value.flashMode == FlashMode.off
|
color: controller?.value.flashMode == FlashMode.off
|
||||||
? Colors.orange
|
? Colors.orange
|
||||||
: Colors.blue,
|
: Colors.blue,
|
||||||
onPressed: () {}, // TODO(camsim99): Add functionality back here.
|
onPressed: controller != null
|
||||||
|
? () => onSetFlashModeButtonPressed(FlashMode.off)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.flash_auto),
|
icon: const Icon(Icons.flash_auto),
|
||||||
color: controller?.value.flashMode == FlashMode.auto
|
color: controller?.value.flashMode == FlashMode.auto
|
||||||
? Colors.orange
|
? Colors.orange
|
||||||
: Colors.blue,
|
: Colors.blue,
|
||||||
onPressed: () {}, // TODO(camsim99): Add functionality back here.
|
onPressed: controller != null
|
||||||
|
? () => onSetFlashModeButtonPressed(FlashMode.auto)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.flash_on),
|
icon: const Icon(Icons.flash_on),
|
||||||
color: controller?.value.flashMode == FlashMode.always
|
color: controller?.value.flashMode == FlashMode.always
|
||||||
? Colors.orange
|
? Colors.orange
|
||||||
: Colors.blue,
|
: Colors.blue,
|
||||||
onPressed: () {}, // TODO(camsim99): Add functionality back here.
|
onPressed: controller != null
|
||||||
|
? () => onSetFlashModeButtonPressed(FlashMode.always)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.highlight),
|
icon: const Icon(Icons.highlight),
|
||||||
color: controller?.value.flashMode == FlashMode.torch
|
color: controller?.value.flashMode == FlashMode.torch
|
||||||
? Colors.orange
|
? Colors.orange
|
||||||
: Colors.blue,
|
: Colors.blue,
|
||||||
onPressed: () {}, // TODO(camsim99): Add functionality back here.
|
onPressed: controller != null
|
||||||
|
? () => onSetFlashModeButtonPressed(FlashMode.torch)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -11,6 +11,7 @@ import 'package:stream_transform/stream_transform.dart';
|
|||||||
|
|
||||||
import 'analyzer.dart';
|
import 'analyzer.dart';
|
||||||
import 'camera.dart';
|
import 'camera.dart';
|
||||||
|
import 'camera_control.dart';
|
||||||
import 'camera_info.dart';
|
import 'camera_info.dart';
|
||||||
import 'camera_selector.dart';
|
import 'camera_selector.dart';
|
||||||
import 'camera_state.dart';
|
import 'camera_state.dart';
|
||||||
@ -111,6 +112,10 @@ class AndroidCameraCameraX extends CameraPlatform {
|
|||||||
/// The flash mode currently configured for [imageCapture].
|
/// The flash mode currently configured for [imageCapture].
|
||||||
int? _currentFlashMode;
|
int? _currentFlashMode;
|
||||||
|
|
||||||
|
/// Whether or not torch flash mode has been enabled for the [camera].
|
||||||
|
@visibleForTesting
|
||||||
|
bool torchEnabled = false;
|
||||||
|
|
||||||
/// The [ImageAnalysis] instance that can be configured to analyze individual
|
/// The [ImageAnalysis] instance that can be configured to analyze individual
|
||||||
/// frames.
|
/// frames.
|
||||||
ImageAnalysis? imageAnalysis;
|
ImageAnalysis? imageAnalysis;
|
||||||
@ -483,14 +488,37 @@ class AndroidCameraCameraX extends CameraPlatform {
|
|||||||
Future<XFile> takePicture(int cameraId) async {
|
Future<XFile> takePicture(int cameraId) async {
|
||||||
if (_currentFlashMode != null) {
|
if (_currentFlashMode != null) {
|
||||||
await imageCapture!.setFlashMode(_currentFlashMode!);
|
await imageCapture!.setFlashMode(_currentFlashMode!);
|
||||||
|
} else if (torchEnabled) {
|
||||||
|
// Ensure any previously set flash modes are unset when torch mode has
|
||||||
|
// been enabled.
|
||||||
|
await imageCapture!.setFlashMode(ImageCapture.flashModeOff);
|
||||||
}
|
}
|
||||||
final String picturePath = await imageCapture!.takePicture();
|
final String picturePath = await imageCapture!.takePicture();
|
||||||
return XFile(picturePath);
|
return XFile(picturePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the flash mode for the selected camera.
|
/// Sets the flash mode for the selected camera.
|
||||||
|
///
|
||||||
|
/// When the [FlashMode.torch] is enabled, any previously set [FlashMode] with
|
||||||
|
/// this method will be disabled, just as with any other [FlashMode]; while
|
||||||
|
/// this is not default native Android behavior as defined by the CameraX API,
|
||||||
|
/// this behavior is compliant with the plugin platform interface.
|
||||||
|
///
|
||||||
|
/// This method combines the notion of setting the flash mode of the
|
||||||
|
/// [imageCapture] UseCase and enabling the camera torch, as described
|
||||||
|
/// by https://developer.android.com/reference/androidx/camera/core/ImageCapture
|
||||||
|
/// and https://developer.android.com/reference/androidx/camera/core/CameraControl#enableTorch(boolean),
|
||||||
|
/// respectively.
|
||||||
@override
|
@override
|
||||||
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
|
Future<void> setFlashMode(int cameraId, FlashMode mode) async {
|
||||||
|
CameraControl? cameraControl;
|
||||||
|
// Turn off torch mode if it is enabled and not being redundantly set.
|
||||||
|
if (mode != FlashMode.torch && torchEnabled) {
|
||||||
|
cameraControl = await camera!.getCameraControl();
|
||||||
|
await cameraControl.enableTorch(false);
|
||||||
|
torchEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case FlashMode.off:
|
case FlashMode.off:
|
||||||
_currentFlashMode = ImageCapture.flashModeOff;
|
_currentFlashMode = ImageCapture.flashModeOff;
|
||||||
@ -502,7 +530,14 @@ class AndroidCameraCameraX extends CameraPlatform {
|
|||||||
_currentFlashMode = ImageCapture.flashModeOn;
|
_currentFlashMode = ImageCapture.flashModeOn;
|
||||||
break;
|
break;
|
||||||
case FlashMode.torch:
|
case FlashMode.torch:
|
||||||
// TODO(camsim99): Implement torch mode when CameraControl is wrapped.
|
_currentFlashMode = null;
|
||||||
|
if (torchEnabled) {
|
||||||
|
// Torch mode enabled already.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cameraControl = await camera!.getCameraControl();
|
||||||
|
await cameraControl.enableTorch(true);
|
||||||
|
torchEnabled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'analyzer.dart';
|
import 'analyzer.dart';
|
||||||
import 'camera.dart';
|
import 'camera.dart';
|
||||||
|
import 'camera_control.dart';
|
||||||
import 'camera_info.dart';
|
import 'camera_info.dart';
|
||||||
import 'camera_selector.dart';
|
import 'camera_selector.dart';
|
||||||
import 'camera_state.dart';
|
import 'camera_state.dart';
|
||||||
@ -26,8 +27,8 @@ import 'zoom_state.dart';
|
|||||||
/// Handles initialization of Flutter APIs for the Android CameraX library.
|
/// Handles initialization of Flutter APIs for the Android CameraX library.
|
||||||
class AndroidCameraXCameraFlutterApis {
|
class AndroidCameraXCameraFlutterApis {
|
||||||
/// Creates a [AndroidCameraXCameraFlutterApis].
|
/// Creates a [AndroidCameraXCameraFlutterApis].
|
||||||
AndroidCameraXCameraFlutterApis({
|
AndroidCameraXCameraFlutterApis(
|
||||||
JavaObjectFlutterApiImpl? javaObjectFlutterApiImpl,
|
{JavaObjectFlutterApiImpl? javaObjectFlutterApiImpl,
|
||||||
CameraFlutterApiImpl? cameraFlutterApiImpl,
|
CameraFlutterApiImpl? cameraFlutterApiImpl,
|
||||||
CameraInfoFlutterApiImpl? cameraInfoFlutterApiImpl,
|
CameraInfoFlutterApiImpl? cameraInfoFlutterApiImpl,
|
||||||
CameraSelectorFlutterApiImpl? cameraSelectorFlutterApiImpl,
|
CameraSelectorFlutterApiImpl? cameraSelectorFlutterApiImpl,
|
||||||
@ -46,7 +47,7 @@ class AndroidCameraXCameraFlutterApis {
|
|||||||
ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl,
|
ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl,
|
||||||
PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl,
|
PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl,
|
||||||
AnalyzerFlutterApiImpl? analyzerFlutterApiImpl,
|
AnalyzerFlutterApiImpl? analyzerFlutterApiImpl,
|
||||||
}) {
|
CameraControlFlutterApiImpl? cameraControlFlutterApiImpl}) {
|
||||||
this.javaObjectFlutterApiImpl =
|
this.javaObjectFlutterApiImpl =
|
||||||
javaObjectFlutterApiImpl ?? JavaObjectFlutterApiImpl();
|
javaObjectFlutterApiImpl ?? JavaObjectFlutterApiImpl();
|
||||||
this.cameraInfoFlutterApiImpl =
|
this.cameraInfoFlutterApiImpl =
|
||||||
@ -85,6 +86,8 @@ class AndroidCameraXCameraFlutterApis {
|
|||||||
imageProxyFlutterApiImpl ?? ImageProxyFlutterApiImpl();
|
imageProxyFlutterApiImpl ?? ImageProxyFlutterApiImpl();
|
||||||
this.planeProxyFlutterApiImpl =
|
this.planeProxyFlutterApiImpl =
|
||||||
planeProxyFlutterApiImpl ?? PlaneProxyFlutterApiImpl();
|
planeProxyFlutterApiImpl ?? PlaneProxyFlutterApiImpl();
|
||||||
|
this.cameraControlFlutterApiImpl =
|
||||||
|
cameraControlFlutterApiImpl ?? CameraControlFlutterApiImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _haveBeenSetUp = false;
|
static bool _haveBeenSetUp = false;
|
||||||
@ -153,6 +156,9 @@ class AndroidCameraXCameraFlutterApis {
|
|||||||
/// Flutter Api implementation for [PlaneProxy].
|
/// Flutter Api implementation for [PlaneProxy].
|
||||||
late final PlaneProxyFlutterApiImpl planeProxyFlutterApiImpl;
|
late final PlaneProxyFlutterApiImpl planeProxyFlutterApiImpl;
|
||||||
|
|
||||||
|
/// Flutter Api implementation for [CameraControl].
|
||||||
|
late final CameraControlFlutterApiImpl cameraControlFlutterApiImpl;
|
||||||
|
|
||||||
/// Ensures all the Flutter APIs have been setup to receive calls from native code.
|
/// Ensures all the Flutter APIs have been setup to receive calls from native code.
|
||||||
void ensureSetUp() {
|
void ensureSetUp() {
|
||||||
if (!_haveBeenSetUp) {
|
if (!_haveBeenSetUp) {
|
||||||
@ -176,6 +182,7 @@ class AndroidCameraXCameraFlutterApis {
|
|||||||
PlaneProxyFlutterApi.setup(planeProxyFlutterApiImpl);
|
PlaneProxyFlutterApi.setup(planeProxyFlutterApiImpl);
|
||||||
LiveDataFlutterApi.setup(liveDataFlutterApiImpl);
|
LiveDataFlutterApi.setup(liveDataFlutterApiImpl);
|
||||||
ObserverFlutterApi.setup(observerFlutterApiImpl);
|
ObserverFlutterApi.setup(observerFlutterApiImpl);
|
||||||
|
CameraControlFlutterApi.setup(cameraControlFlutterApiImpl);
|
||||||
_haveBeenSetUp = true;
|
_haveBeenSetUp = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/services.dart' show BinaryMessenger;
|
|||||||
import 'package:meta/meta.dart' show immutable;
|
import 'package:meta/meta.dart' show immutable;
|
||||||
|
|
||||||
import 'android_camera_camerax_flutter_api_impls.dart';
|
import 'android_camera_camerax_flutter_api_impls.dart';
|
||||||
|
import 'camera_control.dart';
|
||||||
import 'camera_info.dart';
|
import 'camera_info.dart';
|
||||||
import 'camerax_library.g.dart';
|
import 'camerax_library.g.dart';
|
||||||
import 'instance_manager.dart';
|
import 'instance_manager.dart';
|
||||||
@ -23,27 +24,33 @@ class Camera extends JavaObject {
|
|||||||
: super.detached(
|
: super.detached(
|
||||||
binaryMessenger: binaryMessenger,
|
binaryMessenger: binaryMessenger,
|
||||||
instanceManager: instanceManager) {
|
instanceManager: instanceManager) {
|
||||||
_api = CameraHostApiImpl(
|
_api = _CameraHostApiImpl(
|
||||||
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
|
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
|
||||||
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
|
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
late final CameraHostApiImpl _api;
|
late final _CameraHostApiImpl _api;
|
||||||
|
|
||||||
/// Retrieve the [CameraInfo] instance that contains information about this
|
/// Retrieves the [CameraInfo] instance that contains information about this
|
||||||
/// instance.
|
/// instance.
|
||||||
Future<CameraInfo> getCameraInfo() async {
|
Future<CameraInfo> getCameraInfo() async {
|
||||||
return _api.getCameraInfoFromInstance(this);
|
return _api.getCameraInfoFromInstance(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [CameraControl] instance that provides asynchronous
|
||||||
|
/// operations like zoom and focus & metering for this instance.
|
||||||
|
Future<CameraControl> getCameraControl() async {
|
||||||
|
return _api.getCameraControlFromInstance(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Host API implementation of [Camera].
|
/// Host API implementation of [Camera].
|
||||||
class CameraHostApiImpl extends CameraHostApi {
|
class _CameraHostApiImpl extends CameraHostApi {
|
||||||
/// Constructs a [CameraHostApiImpl].
|
/// Constructs a [_CameraHostApiImpl].
|
||||||
///
|
///
|
||||||
/// An [instanceManager] is typically passed when a copy of an instance
|
/// An [instanceManager] is typically passed when a copy of an instance
|
||||||
/// contained by an [InstanceManager] is being created.
|
/// contained by an [InstanceManager] is being created.
|
||||||
CameraHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager})
|
_CameraHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager})
|
||||||
: super(binaryMessenger: binaryMessenger) {
|
: super(binaryMessenger: binaryMessenger) {
|
||||||
this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
|
this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
|
||||||
}
|
}
|
||||||
@ -57,7 +64,7 @@ class CameraHostApiImpl extends CameraHostApi {
|
|||||||
/// Maintains instances stored to communicate with native language objects.
|
/// Maintains instances stored to communicate with native language objects.
|
||||||
late final InstanceManager instanceManager;
|
late final InstanceManager instanceManager;
|
||||||
|
|
||||||
/// Gets the [CameraInfo] associated with the specified instance of [Camera].
|
/// Gets the [CameraInfo] associated with the specified [Camera] instance.
|
||||||
Future<CameraInfo> getCameraInfoFromInstance(Camera instance) async {
|
Future<CameraInfo> getCameraInfoFromInstance(Camera instance) async {
|
||||||
final int identifier = instanceManager.getIdentifier(instance)!;
|
final int identifier = instanceManager.getIdentifier(instance)!;
|
||||||
final int cameraInfoId = await getCameraInfo(identifier);
|
final int cameraInfoId = await getCameraInfo(identifier);
|
||||||
@ -65,6 +72,15 @@ class CameraHostApiImpl extends CameraHostApi {
|
|||||||
return instanceManager
|
return instanceManager
|
||||||
.getInstanceWithWeakReference<CameraInfo>(cameraInfoId)!;
|
.getInstanceWithWeakReference<CameraInfo>(cameraInfoId)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the [CameraControl] associated with the specified [Camera] instance.
|
||||||
|
Future<CameraControl> getCameraControlFromInstance(Camera instance) async {
|
||||||
|
final int identifier = instanceManager.getIdentifier(instance)!;
|
||||||
|
final int cameraControlId = await getCameraControl(identifier);
|
||||||
|
|
||||||
|
return instanceManager
|
||||||
|
.getInstanceWithWeakReference<CameraControl>(cameraControlId)!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flutter API implementation of [Camera].
|
/// Flutter API implementation of [Camera].
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart' show BinaryMessenger, PlatformException;
|
||||||
|
import 'package:meta/meta.dart' show immutable;
|
||||||
|
|
||||||
|
import 'android_camera_camerax_flutter_api_impls.dart';
|
||||||
|
import 'camerax_library.g.dart';
|
||||||
|
import 'instance_manager.dart';
|
||||||
|
import 'java_object.dart';
|
||||||
|
import 'system_services.dart';
|
||||||
|
|
||||||
|
/// The interface that provides asynchronous operations like zoom and focus &
|
||||||
|
/// metering, which affects output of all [UseCase]s currently bound to the
|
||||||
|
/// corresponding [Camera] instance.
|
||||||
|
///
|
||||||
|
/// See https://developer.android.com/reference/androidx/camera/core/CameraControl.
|
||||||
|
@immutable
|
||||||
|
class CameraControl extends JavaObject {
|
||||||
|
/// Constructs a [CameraControl] that is not automatically attached to a native object.
|
||||||
|
CameraControl.detached(
|
||||||
|
{BinaryMessenger? binaryMessenger, InstanceManager? instanceManager})
|
||||||
|
: super.detached(
|
||||||
|
binaryMessenger: binaryMessenger,
|
||||||
|
instanceManager: instanceManager) {
|
||||||
|
_api = _CameraControlHostApiImpl(
|
||||||
|
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
|
||||||
|
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _CameraControlHostApiImpl _api;
|
||||||
|
|
||||||
|
/// Enables or disables the torch of related [Camera] instance.
|
||||||
|
Future<void> enableTorch(bool torch) async {
|
||||||
|
return _api.enableTorchFromInstance(this, torch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Host API implementation of [CameraControl].
|
||||||
|
class _CameraControlHostApiImpl extends CameraControlHostApi {
|
||||||
|
/// Constructs a [_CameraControlHostApiImpl].
|
||||||
|
///
|
||||||
|
/// An [instanceManager] is typically passed when a copy of an instance
|
||||||
|
/// contained by an [InstanceManager] is being created.
|
||||||
|
_CameraControlHostApiImpl(
|
||||||
|
{this.binaryMessenger, InstanceManager? instanceManager})
|
||||||
|
: super(binaryMessenger: binaryMessenger) {
|
||||||
|
this.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.
|
||||||
|
late final InstanceManager instanceManager;
|
||||||
|
|
||||||
|
/// Enables or disables the torch for the specified [CameraControl] instance.
|
||||||
|
Future<void> enableTorchFromInstance(
|
||||||
|
CameraControl instance, bool torch) async {
|
||||||
|
final int identifier = instanceManager.getIdentifier(instance)!;
|
||||||
|
try {
|
||||||
|
await enableTorch(identifier, torch);
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
SystemServices.cameraErrorStreamController
|
||||||
|
.add(e.message ?? 'The camera was unable to change torch modes.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flutter API implementation of [CameraControl].
|
||||||
|
class CameraControlFlutterApiImpl extends CameraControlFlutterApi {
|
||||||
|
/// Constructs a [CameraControlFlutterApiImpl].
|
||||||
|
///
|
||||||
|
/// If [binaryMessenger] is null, the default [BinaryMessenger] will be used,
|
||||||
|
/// which routes to the host platform.
|
||||||
|
///
|
||||||
|
/// An [instanceManager] is typically passed when a copy of an instance
|
||||||
|
/// contained by an [InstanceManager] is being created. If left null, it
|
||||||
|
/// will default to the global instance defined in [JavaObject].
|
||||||
|
CameraControlFlutterApiImpl({
|
||||||
|
BinaryMessenger? binaryMessenger,
|
||||||
|
InstanceManager? instanceManager,
|
||||||
|
}) : _binaryMessenger = binaryMessenger,
|
||||||
|
_instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
|
||||||
|
|
||||||
|
/// Receives binary data across the Flutter platform barrier.
|
||||||
|
final BinaryMessenger? _binaryMessenger;
|
||||||
|
|
||||||
|
/// Maintains instances stored to communicate with native language objects.
|
||||||
|
final InstanceManager _instanceManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void create(int identifier) {
|
||||||
|
_instanceManager.addHostCreatedInstance(
|
||||||
|
CameraControl.detached(
|
||||||
|
binaryMessenger: _binaryMessenger, instanceManager: _instanceManager),
|
||||||
|
identifier,
|
||||||
|
onCopy: (CameraControl original) {
|
||||||
|
return CameraControl.detached(
|
||||||
|
binaryMessenger: _binaryMessenger,
|
||||||
|
instanceManager: _instanceManager);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -780,6 +780,33 @@ class CameraHostApi {
|
|||||||
return (replyList[0] as int?)!;
|
return (replyList[0] as int?)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> getCameraControl(int arg_identifier) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.CameraHostApi.getCameraControl', codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList =
|
||||||
|
await channel.send(<Object?>[arg_identifier]) as List<Object?>?;
|
||||||
|
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 int?)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class CameraFlutterApi {
|
abstract class CameraFlutterApi {
|
||||||
@ -2626,3 +2653,65 @@ class FallbackStrategyHostApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CameraControlHostApi {
|
||||||
|
/// Constructor for [CameraControlHostApi]. 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.
|
||||||
|
CameraControlHostApi({BinaryMessenger? binaryMessenger})
|
||||||
|
: _binaryMessenger = binaryMessenger;
|
||||||
|
final BinaryMessenger? _binaryMessenger;
|
||||||
|
|
||||||
|
static const MessageCodec<Object?> codec = StandardMessageCodec();
|
||||||
|
|
||||||
|
Future<void> enableTorch(int arg_identifier, bool arg_torch) async {
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.CameraControlHostApi.enableTorch', codec,
|
||||||
|
binaryMessenger: _binaryMessenger);
|
||||||
|
final List<Object?>? replyList = await channel
|
||||||
|
.send(<Object?>[arg_identifier, arg_torch]) as List<Object?>?;
|
||||||
|
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 CameraControlFlutterApi {
|
||||||
|
static const MessageCodec<Object?> codec = StandardMessageCodec();
|
||||||
|
|
||||||
|
void create(int identifier);
|
||||||
|
|
||||||
|
static void setup(CameraControlFlutterApi? api,
|
||||||
|
{BinaryMessenger? binaryMessenger}) {
|
||||||
|
{
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.CameraControlFlutterApi.create', codec,
|
||||||
|
binaryMessenger: binaryMessenger);
|
||||||
|
if (api == null) {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler((Object? message) async {
|
||||||
|
assert(message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.CameraControlFlutterApi.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.CameraControlFlutterApi.create was null, expected non-null int.');
|
||||||
|
api.create(arg_identifier!);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -197,6 +197,8 @@ abstract class ProcessCameraProviderFlutterApi {
|
|||||||
@HostApi(dartHostTestHandler: 'TestCameraHostApi')
|
@HostApi(dartHostTestHandler: 'TestCameraHostApi')
|
||||||
abstract class CameraHostApi {
|
abstract class CameraHostApi {
|
||||||
int getCameraInfo(int identifier);
|
int getCameraInfo(int identifier);
|
||||||
|
|
||||||
|
int getCameraControl(int identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FlutterApi()
|
@FlutterApi()
|
||||||
@ -417,3 +419,14 @@ abstract class FallbackStrategyHostApi {
|
|||||||
void create(int identifier, VideoQuality quality,
|
void create(int identifier, VideoQuality quality,
|
||||||
VideoResolutionFallbackRule fallbackRule);
|
VideoResolutionFallbackRule fallbackRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostApi(dartHostTestHandler: 'TestCameraControlHostApi')
|
||||||
|
abstract class CameraControlHostApi {
|
||||||
|
@async
|
||||||
|
void enableTorch(int identifier, bool torch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FlutterApi()
|
||||||
|
abstract class CameraControlFlutterApi {
|
||||||
|
void create(int identifier);
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ name: camera_android_camerax
|
|||||||
description: Android implementation of the camera plugin using the CameraX library.
|
description: Android implementation of the camera plugin using the CameraX library.
|
||||||
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
|
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
|
||||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
|
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
|
||||||
version: 0.5.0+18
|
version: 0.5.0+19
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.0 <4.0.0"
|
sdk: ">=2.19.0 <4.0.0"
|
||||||
|
@ -8,6 +8,7 @@ import 'package:async/async.dart';
|
|||||||
import 'package:camera_android_camerax/camera_android_camerax.dart';
|
import 'package:camera_android_camerax/camera_android_camerax.dart';
|
||||||
import 'package:camera_android_camerax/src/analyzer.dart';
|
import 'package:camera_android_camerax/src/analyzer.dart';
|
||||||
import 'package:camera_android_camerax/src/camera.dart';
|
import 'package:camera_android_camerax/src/camera.dart';
|
||||||
|
import 'package:camera_android_camerax/src/camera_control.dart';
|
||||||
import 'package:camera_android_camerax/src/camera_info.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/camera_selector.dart';
|
||||||
import 'package:camera_android_camerax/src/camera_state.dart';
|
import 'package:camera_android_camerax/src/camera_state.dart';
|
||||||
@ -46,6 +47,7 @@ import 'test_camerax_library.g.dart';
|
|||||||
@GenerateNiceMocks(<MockSpec<Object>>[
|
@GenerateNiceMocks(<MockSpec<Object>>[
|
||||||
MockSpec<Camera>(),
|
MockSpec<Camera>(),
|
||||||
MockSpec<CameraInfo>(),
|
MockSpec<CameraInfo>(),
|
||||||
|
MockSpec<CameraControl>(),
|
||||||
MockSpec<CameraImageData>(),
|
MockSpec<CameraImageData>(),
|
||||||
MockSpec<CameraSelector>(),
|
MockSpec<CameraSelector>(),
|
||||||
MockSpec<ExposureState>(),
|
MockSpec<ExposureState>(),
|
||||||
@ -1025,12 +1027,35 @@ void main() {
|
|||||||
expect(imageFile.path, equals(testPicturePath));
|
expect(imageFile.path, equals(testPicturePath));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setFlashMode configures ImageCapture with expected flash mode',
|
test('takePicture turns non-torch flash mode off when torch mode enabled',
|
||||||
|
() async {
|
||||||
|
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
||||||
|
const int cameraId = 77;
|
||||||
|
final MockCameraControl mockCameraControl = MockCameraControl();
|
||||||
|
|
||||||
|
camera.imageCapture = MockImageCapture();
|
||||||
|
camera.camera = MockCamera();
|
||||||
|
|
||||||
|
when(camera.camera!.getCameraControl())
|
||||||
|
.thenAnswer((_) async => mockCameraControl);
|
||||||
|
|
||||||
|
await camera.setFlashMode(cameraId, FlashMode.torch);
|
||||||
|
await camera.takePicture(cameraId);
|
||||||
|
verify(camera.imageCapture!.setFlashMode(ImageCapture.flashModeOff));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'setFlashMode configures ImageCapture with expected non-torch flash mode',
|
||||||
() async {
|
() async {
|
||||||
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
||||||
const int cameraId = 22;
|
const int cameraId = 22;
|
||||||
|
final MockCameraControl mockCameraControl = MockCameraControl();
|
||||||
|
|
||||||
camera.imageCapture = MockImageCapture();
|
camera.imageCapture = MockImageCapture();
|
||||||
|
camera.camera = MockCamera();
|
||||||
|
|
||||||
|
when(camera.camera!.getCameraControl())
|
||||||
|
.thenAnswer((_) async => mockCameraControl);
|
||||||
|
|
||||||
for (final FlashMode flashMode in FlashMode.values) {
|
for (final FlashMode flashMode in FlashMode.values) {
|
||||||
await camera.setFlashMode(cameraId, flashMode);
|
await camera.setFlashMode(cameraId, flashMode);
|
||||||
@ -1047,19 +1072,68 @@ void main() {
|
|||||||
expectedFlashMode = ImageCapture.flashModeOn;
|
expectedFlashMode = ImageCapture.flashModeOn;
|
||||||
break;
|
break;
|
||||||
case FlashMode.torch:
|
case FlashMode.torch:
|
||||||
// TODO(camsim99): Test torch mode when implemented.
|
expectedFlashMode = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedFlashMode == null) {
|
if (expectedFlashMode == null) {
|
||||||
|
// Torch mode enabled and won't be used for configuring image capture.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifyNever(mockCameraControl.enableTorch(true));
|
||||||
|
expect(camera.torchEnabled, isFalse);
|
||||||
await camera.takePicture(cameraId);
|
await camera.takePicture(cameraId);
|
||||||
verify(camera.imageCapture!.setFlashMode(expectedFlashMode));
|
verify(camera.imageCapture!.setFlashMode(expectedFlashMode));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('setFlashMode turns on torch mode as expected', () async {
|
||||||
|
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
||||||
|
const int cameraId = 44;
|
||||||
|
final MockCameraControl mockCameraControl = MockCameraControl();
|
||||||
|
|
||||||
|
camera.camera = MockCamera();
|
||||||
|
|
||||||
|
when(camera.camera!.getCameraControl())
|
||||||
|
.thenAnswer((_) async => mockCameraControl);
|
||||||
|
|
||||||
|
await camera.setFlashMode(cameraId, FlashMode.torch);
|
||||||
|
|
||||||
|
verify(mockCameraControl.enableTorch(true));
|
||||||
|
expect(camera.torchEnabled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setFlashMode turns off torch mode when non-torch flash modes set',
|
||||||
|
() async {
|
||||||
|
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
||||||
|
const int cameraId = 33;
|
||||||
|
final MockCameraControl mockCameraControl = MockCameraControl();
|
||||||
|
|
||||||
|
camera.camera = MockCamera();
|
||||||
|
|
||||||
|
when(camera.camera!.getCameraControl())
|
||||||
|
.thenAnswer((_) async => mockCameraControl);
|
||||||
|
|
||||||
|
for (final FlashMode flashMode in FlashMode.values) {
|
||||||
|
camera.torchEnabled = true;
|
||||||
|
await camera.setFlashMode(cameraId, flashMode);
|
||||||
|
|
||||||
|
switch (flashMode) {
|
||||||
|
case FlashMode.off:
|
||||||
|
case FlashMode.auto:
|
||||||
|
case FlashMode.always:
|
||||||
|
verify(mockCameraControl.enableTorch(false));
|
||||||
|
expect(camera.torchEnabled, isFalse);
|
||||||
|
break;
|
||||||
|
case FlashMode.torch:
|
||||||
|
verifyNever(mockCameraControl.enableTorch(true));
|
||||||
|
expect(camera.torchEnabled, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('getMinExposureOffset returns expected exposure offset', () async {
|
test('getMinExposureOffset returns expected exposure offset', () async {
|
||||||
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
final AndroidCameraCameraX camera = AndroidCameraCameraX();
|
||||||
final MockCameraInfo mockCameraInfo = MockCameraInfo();
|
final MockCameraInfo mockCameraInfo = MockCameraInfo();
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||||
|
|
||||||
|
import 'package:camera_android_camerax/src/camera_control.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 'camera_control_test.mocks.dart';
|
||||||
|
import 'test_camerax_library.g.dart';
|
||||||
|
|
||||||
|
@GenerateMocks(<Type>[TestCameraControlHostApi, TestInstanceManagerHostApi])
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Mocks the call to clear the native InstanceManager.
|
||||||
|
TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi());
|
||||||
|
|
||||||
|
group('CameraControl', () {
|
||||||
|
tearDown(() => TestCameraHostApi.setup(null));
|
||||||
|
|
||||||
|
test('enableTorch makes call on Java side to enable torch', () async {
|
||||||
|
final MockTestCameraControlHostApi mockApi =
|
||||||
|
MockTestCameraControlHostApi();
|
||||||
|
TestCameraControlHostApi.setup(mockApi);
|
||||||
|
|
||||||
|
final InstanceManager instanceManager = InstanceManager(
|
||||||
|
onWeakReferenceRemoved: (_) {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final CameraControl cameraControl = CameraControl.detached(
|
||||||
|
instanceManager: instanceManager,
|
||||||
|
);
|
||||||
|
const int cameraControlIdentifier = 22;
|
||||||
|
|
||||||
|
instanceManager.addHostCreatedInstance(
|
||||||
|
cameraControl,
|
||||||
|
cameraControlIdentifier,
|
||||||
|
onCopy: (_) => CameraControl.detached(instanceManager: instanceManager),
|
||||||
|
);
|
||||||
|
|
||||||
|
const bool enableTorch = true;
|
||||||
|
await cameraControl.enableTorch(enableTorch);
|
||||||
|
|
||||||
|
verify(mockApi.enableTorch(cameraControlIdentifier, enableTorch));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flutterApiCreate makes call to add instance to instance manager', () {
|
||||||
|
final InstanceManager instanceManager = InstanceManager(
|
||||||
|
onWeakReferenceRemoved: (_) {},
|
||||||
|
);
|
||||||
|
final CameraControlFlutterApiImpl flutterApi =
|
||||||
|
CameraControlFlutterApiImpl(
|
||||||
|
instanceManager: instanceManager,
|
||||||
|
);
|
||||||
|
const int cameraControlIdentifier = 67;
|
||||||
|
|
||||||
|
flutterApi.create(cameraControlIdentifier);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
instanceManager.getInstanceWithWeakReference(cameraControlIdentifier),
|
||||||
|
isA<CameraControl>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
// Mocks generated by Mockito 5.4.1 from annotations
|
||||||
|
// in camera_android_camerax/test/camera_control_test.dart.
|
||||||
|
// Do not manually edit this file.
|
||||||
|
|
||||||
|
// @dart=2.19
|
||||||
|
|
||||||
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
|
import 'dart:async' as _i3;
|
||||||
|
|
||||||
|
import 'package:mockito/mockito.dart' as _i1;
|
||||||
|
|
||||||
|
import 'test_camerax_library.g.dart' as _i2;
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: avoid_redundant_argument_values
|
||||||
|
// ignore_for_file: avoid_setters_without_getters
|
||||||
|
// ignore_for_file: comment_references
|
||||||
|
// ignore_for_file: implementation_imports
|
||||||
|
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
// ignore_for_file: unnecessary_parenthesis
|
||||||
|
// ignore_for_file: camel_case_types
|
||||||
|
// ignore_for_file: subtype_of_sealed_class
|
||||||
|
|
||||||
|
/// A class which mocks [TestCameraControlHostApi].
|
||||||
|
///
|
||||||
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
|
class MockTestCameraControlHostApi extends _i1.Mock
|
||||||
|
implements _i2.TestCameraControlHostApi {
|
||||||
|
MockTestCameraControlHostApi() {
|
||||||
|
_i1.throwOnMissingStub(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i3.Future<void> enableTorch(
|
||||||
|
int? identifier,
|
||||||
|
bool? torch,
|
||||||
|
) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#enableTorch,
|
||||||
|
[
|
||||||
|
identifier,
|
||||||
|
torch,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
returnValue: _i3.Future<void>.value(),
|
||||||
|
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||||
|
) as _i3.Future<void>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:camera_android_camerax/src/camera.dart';
|
import 'package:camera_android_camerax/src/camera.dart';
|
||||||
|
import 'package:camera_android_camerax/src/camera_control.dart';
|
||||||
import 'package:camera_android_camerax/src/camera_info.dart';
|
import 'package:camera_android_camerax/src/camera_info.dart';
|
||||||
import 'package:camera_android_camerax/src/instance_manager.dart';
|
import 'package:camera_android_camerax/src/instance_manager.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -54,6 +55,39 @@ void main() {
|
|||||||
verify(mockApi.getCameraInfo(cameraIdentifier));
|
verify(mockApi.getCameraInfo(cameraIdentifier));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getCameraControl makes call to retrieve expected CameraControl',
|
||||||
|
() async {
|
||||||
|
final MockTestCameraHostApi mockApi = MockTestCameraHostApi();
|
||||||
|
TestCameraHostApi.setup(mockApi);
|
||||||
|
|
||||||
|
final InstanceManager instanceManager = InstanceManager(
|
||||||
|
onWeakReferenceRemoved: (_) {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final Camera camera = Camera.detached(
|
||||||
|
instanceManager: instanceManager,
|
||||||
|
);
|
||||||
|
const int cameraIdentifier = 42;
|
||||||
|
final CameraControl cameraControl = CameraControl.detached();
|
||||||
|
const int cameraControlIdentifier = 8;
|
||||||
|
instanceManager.addHostCreatedInstance(
|
||||||
|
camera,
|
||||||
|
cameraIdentifier,
|
||||||
|
onCopy: (_) => Camera.detached(instanceManager: instanceManager),
|
||||||
|
);
|
||||||
|
instanceManager.addHostCreatedInstance(
|
||||||
|
cameraControl,
|
||||||
|
cameraControlIdentifier,
|
||||||
|
onCopy: (_) => CameraControl.detached(instanceManager: instanceManager),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(mockApi.getCameraControl(cameraIdentifier))
|
||||||
|
.thenAnswer((_) => cameraControlIdentifier);
|
||||||
|
|
||||||
|
expect(await camera.getCameraControl(), equals(cameraControl));
|
||||||
|
verify(mockApi.getCameraControl(cameraIdentifier));
|
||||||
|
});
|
||||||
|
|
||||||
test('flutterApiCreate makes call to add instance to instance manager', () {
|
test('flutterApiCreate makes call to add instance to instance manager', () {
|
||||||
final InstanceManager instanceManager = InstanceManager(
|
final InstanceManager instanceManager = InstanceManager(
|
||||||
onWeakReferenceRemoved: (_) {},
|
onWeakReferenceRemoved: (_) {},
|
||||||
|
@ -36,6 +36,14 @@ class MockTestCameraHostApi extends _i1.Mock implements _i2.TestCameraHostApi {
|
|||||||
),
|
),
|
||||||
returnValue: 0,
|
returnValue: 0,
|
||||||
) as int);
|
) as int);
|
||||||
|
@override
|
||||||
|
int getCameraControl(int? identifier) => (super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getCameraControl,
|
||||||
|
[identifier],
|
||||||
|
),
|
||||||
|
returnValue: 0,
|
||||||
|
) as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [TestInstanceManagerHostApi].
|
/// A class which mocks [TestInstanceManagerHostApi].
|
||||||
|
@ -426,6 +426,8 @@ abstract class TestCameraHostApi {
|
|||||||
|
|
||||||
int getCameraInfo(int identifier);
|
int getCameraInfo(int identifier);
|
||||||
|
|
||||||
|
int getCameraControl(int identifier);
|
||||||
|
|
||||||
static void setup(TestCameraHostApi? api,
|
static void setup(TestCameraHostApi? api,
|
||||||
{BinaryMessenger? binaryMessenger}) {
|
{BinaryMessenger? binaryMessenger}) {
|
||||||
{
|
{
|
||||||
@ -450,6 +452,28 @@ abstract class TestCameraHostApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.CameraHostApi.getCameraControl', codec,
|
||||||
|
binaryMessenger: binaryMessenger);
|
||||||
|
if (api == null) {
|
||||||
|
_testBinaryMessengerBinding!.defaultBinaryMessenger
|
||||||
|
.setMockDecodedMessageHandler<Object?>(channel, null);
|
||||||
|
} else {
|
||||||
|
_testBinaryMessengerBinding!.defaultBinaryMessenger
|
||||||
|
.setMockDecodedMessageHandler<Object?>(channel,
|
||||||
|
(Object? message) async {
|
||||||
|
assert(message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.CameraHostApi.getCameraControl 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.CameraHostApi.getCameraControl was null, expected non-null int.');
|
||||||
|
final int output = api.getCameraControl(arg_identifier!);
|
||||||
|
return <Object?>[output];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1721,3 +1745,40 @@ abstract class TestFallbackStrategyHostApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class TestCameraControlHostApi {
|
||||||
|
static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding =>
|
||||||
|
TestDefaultBinaryMessengerBinding.instance;
|
||||||
|
static const MessageCodec<Object?> codec = StandardMessageCodec();
|
||||||
|
|
||||||
|
Future<void> enableTorch(int identifier, bool torch);
|
||||||
|
|
||||||
|
static void setup(TestCameraControlHostApi? api,
|
||||||
|
{BinaryMessenger? binaryMessenger}) {
|
||||||
|
{
|
||||||
|
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||||
|
'dev.flutter.pigeon.CameraControlHostApi.enableTorch', codec,
|
||||||
|
binaryMessenger: binaryMessenger);
|
||||||
|
if (api == null) {
|
||||||
|
_testBinaryMessengerBinding!.defaultBinaryMessenger
|
||||||
|
.setMockDecodedMessageHandler<Object?>(channel, null);
|
||||||
|
} else {
|
||||||
|
_testBinaryMessengerBinding!.defaultBinaryMessenger
|
||||||
|
.setMockDecodedMessageHandler<Object?>(channel,
|
||||||
|
(Object? message) async {
|
||||||
|
assert(message != null,
|
||||||
|
'Argument for dev.flutter.pigeon.CameraControlHostApi.enableTorch 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.CameraControlHostApi.enableTorch was null, expected non-null int.');
|
||||||
|
final bool? arg_torch = (args[1] as bool?);
|
||||||
|
assert(arg_torch != null,
|
||||||
|
'Argument for dev.flutter.pigeon.CameraControlHostApi.enableTorch was null, expected non-null bool.');
|
||||||
|
await api.enableTorch(arg_identifier!, arg_torch!);
|
||||||
|
return <Object?>[];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user