Skip to content

Commit

Permalink
[camerax] Implements torch mode (flutter#4903)
Browse files Browse the repository at this point in the history
Implements the torch flash mode. Also wraps classes necessary for the implementation (a method in `Camera`, `CameraControl`).

Fixes flutter/flutter#120715.
Fixes flutter/flutter#115846.
Part of flutter/flutter#115847.
  • Loading branch information
camsim99 authored and HugoOlthof committed Dec 13, 2023
1 parent a3a330a commit d965258
Show file tree
Hide file tree
Showing 26 changed files with 1,337 additions and 305 deletions.
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.0+19

* Implements torch flash mode.

## 0.5.0+18

* Implements `startVideoCapturing`.
Expand Down
4 changes: 0 additions & 4 deletions packages/camera/camera_android_camerax/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -81,7 +82,8 @@ public void setUp(
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));
Expand All @@ -107,6 +109,8 @@ public void setUp(
binaryMessenger, new FallbackStrategyHostApiImpl(instanceManager));
GeneratedCameraXLibrary.QualitySelectorHostApi.setup(
binaryMessenger, new QualitySelectorHostApiImpl(instanceManager));
cameraControlHostApiImpl = new CameraControlHostApiImpl(instanceManager, context);
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
}

@Override
Expand All @@ -128,7 +132,6 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBi
Activity activity = activityPluginBinding.getActivity();

setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());
updateContext(activity);

if (activity instanceof LifecycleOwner) {
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
Expand Down Expand Up @@ -183,5 +186,8 @@ public void updateContext(@NonNull Context context) {
if (imageAnalysisHostApiImpl != null) {
imageAnalysisHostApiImpl.setContext(context);
}
if (cameraControlHostApiImpl != null) {
cameraControlHostApiImpl.setContext(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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;
Expand All @@ -28,14 +29,33 @@ public CameraHostApiImpl(
@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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,9 @@ public interface CameraHostApi {
@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();
Expand Down Expand Up @@ -1166,6 +1169,31 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable CameraHost
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. */
Expand Down Expand Up @@ -3203,4 +3231,82 @@ static void setup(
}
}
}
/** 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
Loading

0 comments on commit d965258

Please sign in to comment.