[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:
Camille Simon
2023-10-10 22:01:13 +00:00
committed by GitHub
parent f89e408802
commit bcfa15d8ed
26 changed files with 1337 additions and 305 deletions

View File

@ -1,3 +1,7 @@
## 0.5.0+19
* Implements torch flash mode.
## 0.5.0+18
* Implements `startVideoCapturing`.

View File

@ -35,10 +35,6 @@ and thus, the plugin will fall back to 480p if configured with a
`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]\]
`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.

View File

@ -25,6 +25,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
private VideoCaptureHostApiImpl videoCaptureHostApiImpl;
private ImageAnalysisHostApiImpl imageAnalysisHostApiImpl;
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
private CameraControlHostApiImpl cameraControlHostApiImpl;
public @Nullable SystemServicesHostApiImpl systemServicesHostApiImpl;
@VisibleForTesting
@ -81,7 +82,8 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
GeneratedCameraXLibrary.LiveDataHostApi.setup(binaryMessenger, liveDataHostApiImpl);
GeneratedCameraXLibrary.ObserverHostApi.setup(
binaryMessenger, new ObserverHostApiImpl(binaryMessenger, instanceManager));
imageAnalysisHostApiImpl = new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager);
imageAnalysisHostApiImpl =
new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.ImageAnalysisHostApi.setup(binaryMessenger, imageAnalysisHostApiImpl);
GeneratedCameraXLibrary.AnalyzerHostApi.setup(
binaryMessenger, new AnalyzerHostApiImpl(binaryMessenger, instanceManager));
@ -107,6 +109,8 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
binaryMessenger, new FallbackStrategyHostApiImpl(instanceManager));
GeneratedCameraXLibrary.QualitySelectorHostApi.setup(
binaryMessenger, new QualitySelectorHostApiImpl(instanceManager));
cameraControlHostApiImpl = new CameraControlHostApiImpl(instanceManager, context);
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
}
@Override
@ -128,7 +132,6 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
Activity activity = activityPluginBinding.getActivity();
setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());
updateContext(activity);
if (activity instanceof LifecycleOwner) {
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
@ -183,5 +186,8 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
if (imageAnalysisHostApiImpl != null) {
imageAnalysisHostApiImpl.setContext(context);
}
if (cameraControlHostApiImpl != null) {
cameraControlHostApiImpl.setContext(context);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -6,6 +6,7 @@ package io.flutter.plugins.camerax;
import androidx.annotation.NonNull;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraInfo;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraHostApi;
@ -28,14 +29,33 @@ public class CameraHostApiImpl implements CameraHostApi {
@Override
@NonNull
public Long getCameraInfo(@NonNull Long identifier) {
Camera camera = (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier));
Camera camera = getCameraInstance(identifier);
CameraInfo cameraInfo = camera.getCameraInfo();
if (!instanceManager.containsInstance(cameraInfo)) {
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
}
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
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));
}
}

View File

@ -1136,6 +1136,9 @@ public class GeneratedCameraXLibrary {
@NonNull
Long getCameraInfo(@NonNull Long identifier);
@NonNull
Long getCameraControl(@NonNull Long identifier);
/** The codec used by CameraHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
@ -1166,6 +1169,31 @@ public class GeneratedCameraXLibrary {
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. */
@ -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));
}
}
}

View File

@ -24,9 +24,12 @@ public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi {
@VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy();
public ImageAnalysisHostApiImpl(
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
@NonNull BinaryMessenger binaryMessenger,
@NonNull InstanceManager instanceManager,
@NonNull Context context) {
this.binaryMessenger = binaryMessenger;
this.instanceManager = instanceManager;
this.context = context;
}
/**

View File

@ -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());
}
}

View File

@ -13,6 +13,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraInfo;
import io.flutter.plugin.common.BinaryMessenger;
import java.util.Objects;
@ -59,6 +60,23 @@ public class CameraTest {
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
public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
final CameraFlutterApiImpl spyFlutterApi =

View File

@ -50,7 +50,7 @@ public class ImageAnalysisTest {
@Test
public void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdentifier() {
final ImageAnalysisHostApiImpl hostApi =
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager);
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class);
final ImageAnalysis.Builder mockImageAnalysisBuilder = mock(ImageAnalysis.Builder.class);
final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class);
@ -72,8 +72,7 @@ public class ImageAnalysisTest {
@Test
public void setAnalyzer_makesCallToSetAnalyzerOnExpectedImageAnalysisInstance() {
final ImageAnalysisHostApiImpl hostApi =
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager);
hostApi.setContext(context);
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
final ImageAnalysis.Analyzer mockAnalyzer = mock(ImageAnalysis.Analyzer.class);
final long analyzerIdentifier = 10;
@ -90,7 +89,7 @@ public class ImageAnalysisTest {
@Test
public void clearAnalyzer_makesCallToClearAnalyzerOnExpectedImageAnalysisInstance() {
final ImageAnalysisHostApiImpl hostApi =
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager);
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
final long instanceIdentifier = 22;
instanceManager.addDartCreatedInstance(mockImageAnalysis, instanceIdentifier);

View File

@ -274,7 +274,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
IconButton(
icon: const Icon(Icons.flash_on),
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.
...!kIsWeb
@ -326,28 +326,36 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
color: controller?.value.flashMode == FlashMode.off
? Colors.orange
: Colors.blue,
onPressed: () {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.off)
: null,
),
IconButton(
icon: const Icon(Icons.flash_auto),
color: controller?.value.flashMode == FlashMode.auto
? Colors.orange
: Colors.blue,
onPressed: () {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.auto)
: null,
),
IconButton(
icon: const Icon(Icons.flash_on),
color: controller?.value.flashMode == FlashMode.always
? Colors.orange
: Colors.blue,
onPressed: () {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.always)
: null,
),
IconButton(
icon: const Icon(Icons.highlight),
color: controller?.value.flashMode == FlashMode.torch
? Colors.orange
: Colors.blue,
onPressed: () {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.torch)
: null,
),
],
),

View File

@ -11,6 +11,7 @@ import 'package:stream_transform/stream_transform.dart';
import 'analyzer.dart';
import 'camera.dart';
import 'camera_control.dart';
import 'camera_info.dart';
import 'camera_selector.dart';
import 'camera_state.dart';
@ -111,6 +112,10 @@ class AndroidCameraCameraX extends CameraPlatform {
/// The flash mode currently configured for [imageCapture].
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
/// frames.
ImageAnalysis? imageAnalysis;
@ -483,14 +488,37 @@ class AndroidCameraCameraX extends CameraPlatform {
Future<XFile> takePicture(int cameraId) async {
if (_currentFlashMode != null) {
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();
return XFile(picturePath);
}
/// 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
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) {
case FlashMode.off:
_currentFlashMode = ImageCapture.flashModeOff;
@ -502,7 +530,14 @@ class AndroidCameraCameraX extends CameraPlatform {
_currentFlashMode = ImageCapture.flashModeOn;
break;
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;
}
}

View File

@ -4,6 +4,7 @@
import 'analyzer.dart';
import 'camera.dart';
import 'camera_control.dart';
import 'camera_info.dart';
import 'camera_selector.dart';
import 'camera_state.dart';
@ -26,27 +27,27 @@ import 'zoom_state.dart';
/// Handles initialization of Flutter APIs for the Android CameraX library.
class AndroidCameraXCameraFlutterApis {
/// Creates a [AndroidCameraXCameraFlutterApis].
AndroidCameraXCameraFlutterApis({
JavaObjectFlutterApiImpl? javaObjectFlutterApiImpl,
CameraFlutterApiImpl? cameraFlutterApiImpl,
CameraInfoFlutterApiImpl? cameraInfoFlutterApiImpl,
CameraSelectorFlutterApiImpl? cameraSelectorFlutterApiImpl,
ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApiImpl,
SystemServicesFlutterApiImpl? systemServicesFlutterApiImpl,
CameraStateErrorFlutterApiImpl? cameraStateErrorFlutterApiImpl,
CameraStateFlutterApiImpl? cameraStateFlutterApiImpl,
PendingRecordingFlutterApiImpl? pendingRecordingFlutterApiImpl,
RecordingFlutterApiImpl? recordingFlutterApiImpl,
RecorderFlutterApiImpl? recorderFlutterApiImpl,
VideoCaptureFlutterApiImpl? videoCaptureFlutterApiImpl,
ExposureStateFlutterApiImpl? exposureStateFlutterApiImpl,
ZoomStateFlutterApiImpl? zoomStateFlutterApiImpl,
LiveDataFlutterApiImpl? liveDataFlutterApiImpl,
ObserverFlutterApiImpl? observerFlutterApiImpl,
ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl,
PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl,
AnalyzerFlutterApiImpl? analyzerFlutterApiImpl,
}) {
AndroidCameraXCameraFlutterApis(
{JavaObjectFlutterApiImpl? javaObjectFlutterApiImpl,
CameraFlutterApiImpl? cameraFlutterApiImpl,
CameraInfoFlutterApiImpl? cameraInfoFlutterApiImpl,
CameraSelectorFlutterApiImpl? cameraSelectorFlutterApiImpl,
ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApiImpl,
SystemServicesFlutterApiImpl? systemServicesFlutterApiImpl,
CameraStateErrorFlutterApiImpl? cameraStateErrorFlutterApiImpl,
CameraStateFlutterApiImpl? cameraStateFlutterApiImpl,
PendingRecordingFlutterApiImpl? pendingRecordingFlutterApiImpl,
RecordingFlutterApiImpl? recordingFlutterApiImpl,
RecorderFlutterApiImpl? recorderFlutterApiImpl,
VideoCaptureFlutterApiImpl? videoCaptureFlutterApiImpl,
ExposureStateFlutterApiImpl? exposureStateFlutterApiImpl,
ZoomStateFlutterApiImpl? zoomStateFlutterApiImpl,
LiveDataFlutterApiImpl? liveDataFlutterApiImpl,
ObserverFlutterApiImpl? observerFlutterApiImpl,
ImageProxyFlutterApiImpl? imageProxyFlutterApiImpl,
PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl,
AnalyzerFlutterApiImpl? analyzerFlutterApiImpl,
CameraControlFlutterApiImpl? cameraControlFlutterApiImpl}) {
this.javaObjectFlutterApiImpl =
javaObjectFlutterApiImpl ?? JavaObjectFlutterApiImpl();
this.cameraInfoFlutterApiImpl =
@ -85,6 +86,8 @@ class AndroidCameraXCameraFlutterApis {
imageProxyFlutterApiImpl ?? ImageProxyFlutterApiImpl();
this.planeProxyFlutterApiImpl =
planeProxyFlutterApiImpl ?? PlaneProxyFlutterApiImpl();
this.cameraControlFlutterApiImpl =
cameraControlFlutterApiImpl ?? CameraControlFlutterApiImpl();
}
static bool _haveBeenSetUp = false;
@ -153,6 +156,9 @@ class AndroidCameraXCameraFlutterApis {
/// Flutter Api implementation for [PlaneProxy].
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.
void ensureSetUp() {
if (!_haveBeenSetUp) {
@ -176,6 +182,7 @@ class AndroidCameraXCameraFlutterApis {
PlaneProxyFlutterApi.setup(planeProxyFlutterApiImpl);
LiveDataFlutterApi.setup(liveDataFlutterApiImpl);
ObserverFlutterApi.setup(observerFlutterApiImpl);
CameraControlFlutterApi.setup(cameraControlFlutterApiImpl);
_haveBeenSetUp = true;
}
}

View File

@ -6,6 +6,7 @@ import 'package:flutter/services.dart' show BinaryMessenger;
import 'package:meta/meta.dart' show immutable;
import 'android_camera_camerax_flutter_api_impls.dart';
import 'camera_control.dart';
import 'camera_info.dart';
import 'camerax_library.g.dart';
import 'instance_manager.dart';
@ -23,27 +24,33 @@ class Camera extends JavaObject {
: super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager) {
_api = CameraHostApiImpl(
_api = _CameraHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
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.
Future<CameraInfo> getCameraInfo() async {
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].
class CameraHostApiImpl extends CameraHostApi {
/// Constructs a [CameraHostApiImpl].
class _CameraHostApiImpl extends CameraHostApi {
/// Constructs a [_CameraHostApiImpl].
///
/// An [instanceManager] is typically passed when a copy of an instance
/// contained by an [InstanceManager] is being created.
CameraHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager})
_CameraHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager})
: super(binaryMessenger: binaryMessenger) {
this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
}
@ -57,7 +64,7 @@ class CameraHostApiImpl extends CameraHostApi {
/// Maintains instances stored to communicate with native language objects.
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 {
final int identifier = instanceManager.getIdentifier(instance)!;
final int cameraInfoId = await getCameraInfo(identifier);
@ -65,6 +72,15 @@ class CameraHostApiImpl extends CameraHostApi {
return instanceManager
.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].

View File

@ -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);
},
);
}
}

View File

@ -780,6 +780,33 @@ class CameraHostApi {
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 {
@ -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;
});
}
}
}
}

View File

@ -197,6 +197,8 @@ abstract class ProcessCameraProviderFlutterApi {
@HostApi(dartHostTestHandler: 'TestCameraHostApi')
abstract class CameraHostApi {
int getCameraInfo(int identifier);
int getCameraControl(int identifier);
}
@FlutterApi()
@ -417,3 +419,14 @@ abstract class FallbackStrategyHostApi {
void create(int identifier, VideoQuality quality,
VideoResolutionFallbackRule fallbackRule);
}
@HostApi(dartHostTestHandler: 'TestCameraControlHostApi')
abstract class CameraControlHostApi {
@async
void enableTorch(int identifier, bool torch);
}
@FlutterApi()
abstract class CameraControlFlutterApi {
void create(int identifier);
}

View File

@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.5.0+18
version: 0.5.0+19
environment:
sdk: ">=2.19.0 <4.0.0"

View File

@ -8,6 +8,7 @@ import 'package:async/async.dart';
import 'package:camera_android_camerax/camera_android_camerax.dart';
import 'package:camera_android_camerax/src/analyzer.dart';
import 'package:camera_android_camerax/src/camera.dart';
import 'package:camera_android_camerax/src/camera_control.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_state.dart';
@ -46,6 +47,7 @@ import 'test_camerax_library.g.dart';
@GenerateNiceMocks(<MockSpec<Object>>[
MockSpec<Camera>(),
MockSpec<CameraInfo>(),
MockSpec<CameraControl>(),
MockSpec<CameraImageData>(),
MockSpec<CameraSelector>(),
MockSpec<ExposureState>(),
@ -1025,12 +1027,35 @@ void main() {
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 {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 22;
final MockCameraControl mockCameraControl = MockCameraControl();
camera.imageCapture = MockImageCapture();
camera.camera = MockCamera();
when(camera.camera!.getCameraControl())
.thenAnswer((_) async => mockCameraControl);
for (final FlashMode flashMode in FlashMode.values) {
await camera.setFlashMode(cameraId, flashMode);
@ -1047,19 +1072,68 @@ void main() {
expectedFlashMode = ImageCapture.flashModeOn;
break;
case FlashMode.torch:
// TODO(camsim99): Test torch mode when implemented.
expectedFlashMode = null;
break;
}
if (expectedFlashMode == null) {
// Torch mode enabled and won't be used for configuring image capture.
continue;
}
verifyNever(mockCameraControl.enableTorch(true));
expect(camera.torchEnabled, isFalse);
await camera.takePicture(cameraId);
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 {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockCameraInfo mockCameraInfo = MockCameraInfo();

View File

@ -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>());
});
});
}

View File

@ -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,
);
}

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
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/instance_manager.dart';
import 'package:flutter_test/flutter_test.dart';
@ -54,6 +55,39 @@ void main() {
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', () {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},

View File

@ -36,6 +36,14 @@ class MockTestCameraHostApi extends _i1.Mock implements _i2.TestCameraHostApi {
),
returnValue: 0,
) as int);
@override
int getCameraControl(int? identifier) => (super.noSuchMethod(
Invocation.method(
#getCameraControl,
[identifier],
),
returnValue: 0,
) as int);
}
/// A class which mocks [TestInstanceManagerHostApi].

View File

@ -426,6 +426,8 @@ abstract class TestCameraHostApi {
int getCameraInfo(int identifier);
int getCameraControl(int identifier);
static void setup(TestCameraHostApi? api,
{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?>[];
});
}
}
}
}