Skip to content

Commit bcfa15d

Browse files
authored
[camerax] Implements torch mode (#4903)
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.
1 parent f89e408 commit bcfa15d

26 files changed

+1337
-305
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.0+19
2+
3+
* Implements torch flash mode.
4+
15
## 0.5.0+18
26

37
* Implements `startVideoCapturing`.

packages/camera/camera_android_camerax/README.md

-4
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ and thus, the plugin will fall back to 480p if configured with a
3535

3636
`lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
3737

38-
### Torch mode \[[Issue #120715][120715]\]
39-
40-
Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing.
41-
4238
### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\]
4339

4440
`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
2525
private VideoCaptureHostApiImpl videoCaptureHostApiImpl;
2626
private ImageAnalysisHostApiImpl imageAnalysisHostApiImpl;
2727
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
28+
private CameraControlHostApiImpl cameraControlHostApiImpl;
2829
public @Nullable SystemServicesHostApiImpl systemServicesHostApiImpl;
2930

3031
@VisibleForTesting
@@ -81,7 +82,8 @@ public void setUp(
8182
GeneratedCameraXLibrary.LiveDataHostApi.setup(binaryMessenger, liveDataHostApiImpl);
8283
GeneratedCameraXLibrary.ObserverHostApi.setup(
8384
binaryMessenger, new ObserverHostApiImpl(binaryMessenger, instanceManager));
84-
imageAnalysisHostApiImpl = new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager);
85+
imageAnalysisHostApiImpl =
86+
new ImageAnalysisHostApiImpl(binaryMessenger, instanceManager, context);
8587
GeneratedCameraXLibrary.ImageAnalysisHostApi.setup(binaryMessenger, imageAnalysisHostApiImpl);
8688
GeneratedCameraXLibrary.AnalyzerHostApi.setup(
8789
binaryMessenger, new AnalyzerHostApiImpl(binaryMessenger, instanceManager));
@@ -107,6 +109,8 @@ public void setUp(
107109
binaryMessenger, new FallbackStrategyHostApiImpl(instanceManager));
108110
GeneratedCameraXLibrary.QualitySelectorHostApi.setup(
109111
binaryMessenger, new QualitySelectorHostApiImpl(instanceManager));
112+
cameraControlHostApiImpl = new CameraControlHostApiImpl(instanceManager, context);
113+
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
110114
}
111115

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

130134
setUp(pluginBinding.getBinaryMessenger(), activity, pluginBinding.getTextureRegistry());
131-
updateContext(activity);
132135

133136
if (activity instanceof LifecycleOwner) {
134137
processCameraProviderHostApiImpl.setLifecycleOwner((LifecycleOwner) activity);
@@ -183,5 +186,8 @@ public void updateContext(@NonNull Context context) {
183186
if (imageAnalysisHostApiImpl != null) {
184187
imageAnalysisHostApiImpl.setContext(context);
185188
}
189+
if (cameraControlHostApiImpl != null) {
190+
cameraControlHostApiImpl.setContext(context);
191+
}
186192
}
187193
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camerax;
6+
7+
import androidx.annotation.NonNull;
8+
import androidx.camera.core.CameraControl;
9+
import io.flutter.plugin.common.BinaryMessenger;
10+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraControlFlutterApi;
11+
12+
public class CameraControlFlutterApiImpl extends CameraControlFlutterApi {
13+
private final @NonNull InstanceManager instanceManager;
14+
15+
public CameraControlFlutterApiImpl(
16+
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
17+
super(binaryMessenger);
18+
this.instanceManager = instanceManager;
19+
}
20+
21+
/**
22+
* Creates a {@link CameraControl} instance in Dart. {@code reply} is not used so it can be empty.
23+
*/
24+
void create(CameraControl cameraControl, Reply<Void> reply) {
25+
if (!instanceManager.containsInstance(cameraControl)) {
26+
create(instanceManager.addHostCreatedInstance(cameraControl), reply);
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camerax;
6+
7+
import android.content.Context;
8+
import androidx.annotation.NonNull;
9+
import androidx.annotation.VisibleForTesting;
10+
import androidx.camera.core.CameraControl;
11+
import androidx.core.content.ContextCompat;
12+
import com.google.common.util.concurrent.FutureCallback;
13+
import com.google.common.util.concurrent.Futures;
14+
import com.google.common.util.concurrent.ListenableFuture;
15+
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraControlHostApi;
16+
import java.util.Objects;
17+
18+
/**
19+
* Host API implementation for {@link CameraControl}.
20+
*
21+
* <p>This class handles instantiating and adding native object instances that are attached to a
22+
* Dart instance or handle method calls on the associated native class or an instance of the class.
23+
*/
24+
public class CameraControlHostApiImpl implements CameraControlHostApi {
25+
private final InstanceManager instanceManager;
26+
private final CameraControlProxy proxy;
27+
28+
/** Proxy for constructors and static method of {@link CameraControl}. */
29+
@VisibleForTesting
30+
public static class CameraControlProxy {
31+
Context context;
32+
33+
/** Enables or disables the torch of the specified {@link CameraControl} instance. */
34+
@NonNull
35+
public void enableTorch(
36+
@NonNull CameraControl cameraControl,
37+
@NonNull Boolean torch,
38+
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
39+
ListenableFuture<Void> enableTorchFuture = cameraControl.enableTorch(torch);
40+
41+
Futures.addCallback(
42+
enableTorchFuture,
43+
new FutureCallback<Void>() {
44+
public void onSuccess(Void voidResult) {
45+
result.success(null);
46+
}
47+
48+
public void onFailure(Throwable t) {
49+
result.error(t);
50+
}
51+
},
52+
ContextCompat.getMainExecutor(context));
53+
}
54+
}
55+
56+
/**
57+
* Constructs an {@link CameraControlHostApiImpl}.
58+
*
59+
* @param instanceManager maintains instances stored to communicate with attached Dart objects
60+
*/
61+
public CameraControlHostApiImpl(
62+
@NonNull InstanceManager instanceManager, @NonNull Context context) {
63+
this(instanceManager, new CameraControlProxy(), context);
64+
}
65+
66+
/**
67+
* Constructs an {@link CameraControlHostApiImpl}.
68+
*
69+
* @param instanceManager maintains instances stored to communicate with attached Dart objects
70+
* @param proxy proxy for constructors and static method of {@link CameraControl}
71+
* @param context {@link Context} used to retrieve {@code Executor} used to enable torch mode
72+
*/
73+
@VisibleForTesting
74+
CameraControlHostApiImpl(
75+
@NonNull InstanceManager instanceManager,
76+
@NonNull CameraControlProxy proxy,
77+
@NonNull Context context) {
78+
this.instanceManager = instanceManager;
79+
this.proxy = proxy;
80+
proxy.context = context;
81+
}
82+
83+
/**
84+
* Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode.
85+
*
86+
* <p>If using the camera plugin in an add-to-app context, ensure that a new instance of the
87+
* {@code CameraControl} is fetched via {@code #enableTorch} anytime the context changes.
88+
*/
89+
public void setContext(@NonNull Context context) {
90+
this.proxy.context = context;
91+
}
92+
93+
@Override
94+
public void enableTorch(
95+
@NonNull Long identifier,
96+
@NonNull Boolean torch,
97+
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
98+
proxy.enableTorch(
99+
Objects.requireNonNull(instanceManager.getInstance(identifier)), torch, result);
100+
}
101+
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraHostApiImpl.java

+26-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import androidx.annotation.NonNull;
88
import androidx.camera.core.Camera;
9+
import androidx.camera.core.CameraControl;
910
import androidx.camera.core.CameraInfo;
1011
import io.flutter.plugin.common.BinaryMessenger;
1112
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraHostApi;
@@ -28,14 +29,33 @@ public CameraHostApiImpl(
2829
@Override
2930
@NonNull
3031
public Long getCameraInfo(@NonNull Long identifier) {
31-
Camera camera = (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier));
32+
Camera camera = getCameraInstance(identifier);
3233
CameraInfo cameraInfo = camera.getCameraInfo();
3334

34-
if (!instanceManager.containsInstance(cameraInfo)) {
35-
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
36-
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
37-
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
38-
}
35+
CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl =
36+
new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager);
37+
cameraInfoFlutterApiImpl.create(cameraInfo, reply -> {});
3938
return instanceManager.getIdentifierForStrongReference(cameraInfo);
4039
}
40+
41+
/**
42+
* Retrieves the {@link CameraControl} instance that provides access to asynchronous operations
43+
* like zoom and focus & metering on the {@link Camera} instance with the specified identifier.
44+
*/
45+
@Override
46+
@NonNull
47+
public Long getCameraControl(@NonNull Long identifier) {
48+
Camera camera = getCameraInstance(identifier);
49+
CameraControl cameraControl = camera.getCameraControl();
50+
51+
CameraControlFlutterApiImpl cameraControlFlutterApiImpl =
52+
new CameraControlFlutterApiImpl(binaryMessenger, instanceManager);
53+
cameraControlFlutterApiImpl.create(cameraControl, reply -> {});
54+
return instanceManager.getIdentifierForStrongReference(cameraControl);
55+
}
56+
57+
/** Retrieives the {@link Camera} instance associated with the specified {@code identifier}. */
58+
private Camera getCameraInstance(@NonNull Long identifier) {
59+
return (Camera) Objects.requireNonNull(instanceManager.getInstance(identifier));
60+
}
4161
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java

+106
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,9 @@ public interface CameraHostApi {
11361136
@NonNull
11371137
Long getCameraInfo(@NonNull Long identifier);
11381138

1139+
@NonNull
1140+
Long getCameraControl(@NonNull Long identifier);
1141+
11391142
/** The codec used by CameraHostApi. */
11401143
static @NonNull MessageCodec<Object> getCodec() {
11411144
return new StandardMessageCodec();
@@ -1166,6 +1169,31 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable CameraHost
11661169
channel.setMessageHandler(null);
11671170
}
11681171
}
1172+
{
1173+
BasicMessageChannel<Object> channel =
1174+
new BasicMessageChannel<>(
1175+
binaryMessenger, "dev.flutter.pigeon.CameraHostApi.getCameraControl", getCodec());
1176+
if (api != null) {
1177+
channel.setMessageHandler(
1178+
(message, reply) -> {
1179+
ArrayList<Object> wrapped = new ArrayList<Object>();
1180+
ArrayList<Object> args = (ArrayList<Object>) message;
1181+
Number identifierArg = (Number) args.get(0);
1182+
try {
1183+
Long output =
1184+
api.getCameraControl(
1185+
(identifierArg == null) ? null : identifierArg.longValue());
1186+
wrapped.add(0, output);
1187+
} catch (Throwable exception) {
1188+
ArrayList<Object> wrappedError = wrapError(exception);
1189+
wrapped = wrappedError;
1190+
}
1191+
reply.reply(wrapped);
1192+
});
1193+
} else {
1194+
channel.setMessageHandler(null);
1195+
}
1196+
}
11691197
}
11701198
}
11711199
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
@@ -3203,4 +3231,82 @@ static void setup(
32033231
}
32043232
}
32053233
}
3234+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
3235+
public interface CameraControlHostApi {
3236+
3237+
void enableTorch(
3238+
@NonNull Long identifier, @NonNull Boolean torch, @NonNull Result<Void> result);
3239+
3240+
/** The codec used by CameraControlHostApi. */
3241+
static @NonNull MessageCodec<Object> getCodec() {
3242+
return new StandardMessageCodec();
3243+
}
3244+
/**
3245+
* Sets up an instance of `CameraControlHostApi` to handle messages through the
3246+
* `binaryMessenger`.
3247+
*/
3248+
static void setup(
3249+
@NonNull BinaryMessenger binaryMessenger, @Nullable CameraControlHostApi api) {
3250+
{
3251+
BasicMessageChannel<Object> channel =
3252+
new BasicMessageChannel<>(
3253+
binaryMessenger, "dev.flutter.pigeon.CameraControlHostApi.enableTorch", getCodec());
3254+
if (api != null) {
3255+
channel.setMessageHandler(
3256+
(message, reply) -> {
3257+
ArrayList<Object> wrapped = new ArrayList<Object>();
3258+
ArrayList<Object> args = (ArrayList<Object>) message;
3259+
Number identifierArg = (Number) args.get(0);
3260+
Boolean torchArg = (Boolean) args.get(1);
3261+
Result<Void> resultCallback =
3262+
new Result<Void>() {
3263+
public void success(Void result) {
3264+
wrapped.add(0, null);
3265+
reply.reply(wrapped);
3266+
}
3267+
3268+
public void error(Throwable error) {
3269+
ArrayList<Object> wrappedError = wrapError(error);
3270+
reply.reply(wrappedError);
3271+
}
3272+
};
3273+
3274+
api.enableTorch(
3275+
(identifierArg == null) ? null : identifierArg.longValue(),
3276+
torchArg,
3277+
resultCallback);
3278+
});
3279+
} else {
3280+
channel.setMessageHandler(null);
3281+
}
3282+
}
3283+
}
3284+
}
3285+
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
3286+
public static class CameraControlFlutterApi {
3287+
private final @NonNull BinaryMessenger binaryMessenger;
3288+
3289+
public CameraControlFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
3290+
this.binaryMessenger = argBinaryMessenger;
3291+
}
3292+
3293+
/** Public interface for sending reply. */
3294+
@SuppressWarnings("UnknownNullness")
3295+
public interface Reply<T> {
3296+
void reply(T reply);
3297+
}
3298+
/** The codec used by CameraControlFlutterApi. */
3299+
static @NonNull MessageCodec<Object> getCodec() {
3300+
return new StandardMessageCodec();
3301+
}
3302+
3303+
public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
3304+
BasicMessageChannel<Object> channel =
3305+
new BasicMessageChannel<>(
3306+
binaryMessenger, "dev.flutter.pigeon.CameraControlFlutterApi.create", getCodec());
3307+
channel.send(
3308+
new ArrayList<Object>(Collections.singletonList(identifierArg)),
3309+
channelReply -> callback.reply(null));
3310+
}
3311+
}
32063312
}

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi {
2424
@VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy();
2525

2626
public ImageAnalysisHostApiImpl(
27-
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
27+
@NonNull BinaryMessenger binaryMessenger,
28+
@NonNull InstanceManager instanceManager,
29+
@NonNull Context context) {
2830
this.binaryMessenger = binaryMessenger;
2931
this.instanceManager = instanceManager;
32+
this.context = context;
3033
}
3134

3235
/**

0 commit comments

Comments
 (0)