Skip to content

Commit f2124f7

Browse files
authored
[camerax] Implement setZoomLevel (flutter#4950)
Implements `setZoomLevel`. Fixes flutter#125371. Fixes flutter#115847.
1 parent fdfafcd commit f2124f7

16 files changed

Lines changed: 287 additions & 18 deletions

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.0+20
2+
3+
* Implements `setZoomLevel`.
4+
15
## 0.5.0+19
26

37
* Implements torch flash mode.

packages/camera/camera_android_camerax/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ and thus, the plugin will fall back to 480p if configured with a
4343

4444
`setFocusMode` & `setFocusPoint` are unimplemented.
4545

46-
### Zoom configuration \[[Issue #125371][125371]\]
47-
48-
`setZoomLevel` is unimplemented.
49-
5046
### Setting maximum duration and stream options for video capture
5147

5248
Calling `startVideoCapturing` with `VideoCaptureOptions` configured with

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ public void onFailure(Throwable t) {
5151
},
5252
ContextCompat.getMainExecutor(context));
5353
}
54+
55+
/** Sets the zoom ratio of the specified {@link CameraControl} instance. */
56+
@NonNull
57+
public void setZoomRatio(
58+
@NonNull CameraControl cameraControl,
59+
@NonNull Double ratio,
60+
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
61+
float ratioAsFloat = ratio.floatValue();
62+
ListenableFuture<Void> setZoomRatioFuture = cameraControl.setZoomRatio(ratioAsFloat);
63+
64+
Futures.addCallback(
65+
setZoomRatioFuture,
66+
new FutureCallback<Void>() {
67+
public void onSuccess(Void voidResult) {
68+
result.success(null);
69+
}
70+
71+
public void onFailure(Throwable t) {
72+
result.error(t);
73+
}
74+
},
75+
ContextCompat.getMainExecutor(context));
76+
}
5477
}
5578

5679
/**
@@ -81,7 +104,8 @@ public CameraControlHostApiImpl(
81104
}
82105

83106
/**
84-
* Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode.
107+
* Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode
108+
* and set the zoom ratio.
85109
*
86110
* <p>If using the camera plugin in an add-to-app context, ensure that a new instance of the
87111
* {@code CameraControl} is fetched via {@code #enableTorch} anytime the context changes.
@@ -98,4 +122,13 @@ public void enableTorch(
98122
proxy.enableTorch(
99123
Objects.requireNonNull(instanceManager.getInstance(identifier)), torch, result);
100124
}
125+
126+
@Override
127+
public void setZoomRatio(
128+
@NonNull Long identifier,
129+
@NonNull Double ratio,
130+
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
131+
proxy.setZoomRatio(
132+
Objects.requireNonNull(instanceManager.getInstance(identifier)), ratio, result);
133+
}
101134
}

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,6 +3237,9 @@ public interface CameraControlHostApi {
32373237
void enableTorch(
32383238
@NonNull Long identifier, @NonNull Boolean torch, @NonNull Result<Void> result);
32393239

3240+
void setZoomRatio(
3241+
@NonNull Long identifier, @NonNull Double ratio, @NonNull Result<Void> result);
3242+
32403243
/** The codec used by CameraControlHostApi. */
32413244
static @NonNull MessageCodec<Object> getCodec() {
32423245
return new StandardMessageCodec();
@@ -3280,6 +3283,41 @@ public void error(Throwable error) {
32803283
channel.setMessageHandler(null);
32813284
}
32823285
}
3286+
{
3287+
BasicMessageChannel<Object> channel =
3288+
new BasicMessageChannel<>(
3289+
binaryMessenger,
3290+
"dev.flutter.pigeon.CameraControlHostApi.setZoomRatio",
3291+
getCodec());
3292+
if (api != null) {
3293+
channel.setMessageHandler(
3294+
(message, reply) -> {
3295+
ArrayList<Object> wrapped = new ArrayList<Object>();
3296+
ArrayList<Object> args = (ArrayList<Object>) message;
3297+
Number identifierArg = (Number) args.get(0);
3298+
Double ratioArg = (Double) args.get(1);
3299+
Result<Void> resultCallback =
3300+
new Result<Void>() {
3301+
public void success(Void result) {
3302+
wrapped.add(0, null);
3303+
reply.reply(wrapped);
3304+
}
3305+
3306+
public void error(Throwable error) {
3307+
ArrayList<Object> wrappedError = wrapError(error);
3308+
reply.reply(wrappedError);
3309+
}
3310+
};
3311+
3312+
api.setZoomRatio(
3313+
(identifierArg == null) ? null : identifierArg.longValue(),
3314+
ratioArg,
3315+
resultCallback);
3316+
});
3317+
} else {
3318+
channel.setMessageHandler(null);
3319+
}
3320+
}
32833321
}
32843322
}
32853323
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */

packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,57 @@ public void enableTorch_turnsTorchModeOnAndOffAsExpected() {
9797
}
9898
}
9999

100+
@Test
101+
public void setZoomRatio_setsZoomAsExpected() {
102+
try (MockedStatic<Futures> mockedFutures = Mockito.mockStatic(Futures.class)) {
103+
final CameraControlHostApiImpl cameraControlHostApiImpl =
104+
new CameraControlHostApiImpl(testInstanceManager, mock(Context.class));
105+
final Long cameraControlIdentifier = 33L;
106+
final Double zoomRatio = 0.2D;
107+
108+
@SuppressWarnings("unchecked")
109+
final ListenableFuture<Void> setZoomRatioFuture = mock(ListenableFuture.class);
110+
111+
testInstanceManager.addDartCreatedInstance(cameraControl, cameraControlIdentifier);
112+
113+
when(cameraControl.setZoomRatio(zoomRatio.floatValue())).thenReturn(setZoomRatioFuture);
114+
115+
@SuppressWarnings("unchecked")
116+
final ArgumentCaptor<FutureCallback<Void>> futureCallbackCaptor =
117+
ArgumentCaptor.forClass(FutureCallback.class);
118+
119+
// Test successful behavior.
120+
@SuppressWarnings("unchecked")
121+
final GeneratedCameraXLibrary.Result<Void> successfulMockResult =
122+
mock(GeneratedCameraXLibrary.Result.class);
123+
cameraControlHostApiImpl.setZoomRatio(
124+
cameraControlIdentifier, zoomRatio, successfulMockResult);
125+
mockedFutures.verify(
126+
() -> Futures.addCallback(eq(setZoomRatioFuture), futureCallbackCaptor.capture(), any()));
127+
mockedFutures.clearInvocations();
128+
129+
FutureCallback<Void> successfulSetZoomRatioCallback = futureCallbackCaptor.getValue();
130+
131+
successfulSetZoomRatioCallback.onSuccess(mock(Void.class));
132+
verify(successfulMockResult).success(null);
133+
134+
// Test failed behavior.
135+
@SuppressWarnings("unchecked")
136+
final GeneratedCameraXLibrary.Result<Void> failedMockResult =
137+
mock(GeneratedCameraXLibrary.Result.class);
138+
final Throwable testThrowable = new Throwable();
139+
cameraControlHostApiImpl.setZoomRatio(cameraControlIdentifier, zoomRatio, failedMockResult);
140+
mockedFutures.verify(
141+
() -> Futures.addCallback(eq(setZoomRatioFuture), futureCallbackCaptor.capture(), any()));
142+
mockedFutures.clearInvocations();
143+
144+
FutureCallback<Void> failedSetZoomRatioCallback = futureCallbackCaptor.getValue();
145+
146+
failedSetZoomRatioCallback.onFailure(testThrowable);
147+
verify(failedMockResult).error(testThrowable);
148+
}
149+
}
150+
100151
@Test
101152
public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() {
102153
final CameraControlFlutterApiImpl spyFlutterApi =

packages/camera/camera_android_camerax/example/lib/main.dart

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -392,12 +392,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
392392
style: styleAuto,
393393
onPressed:
394394
() {}, // TODO(camsim99): Add functionality back here.
395-
onLongPress: () {
396-
if (controller != null) {
397-
controller!.setExposurePoint(null);
398-
showInSnackBar('Resetting exposure point');
399-
}
400-
},
395+
onLongPress:
396+
() {}, // TODO(camsim99): Add functionality back here.,
401397
child: const Text('AUTO'),
402398
),
403399
TextButton(
@@ -470,12 +466,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
470466
style: styleAuto,
471467
onPressed:
472468
() {}, // TODO(camsim99): Add functionality back here.
473-
onLongPress: () {
474-
if (controller != null) {
475-
controller!.setFocusPoint(null);
476-
}
477-
showInSnackBar('Resetting focus point');
478-
},
469+
onLongPress:
470+
() {}, // TODO(camsim99): Add functionality back here.
479471
child: const Text('AUTO'),
480472
),
481473
TextButton(

packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,17 @@ class AndroidCameraCameraX extends CameraPlatform {
439439
return zoomState.minZoomRatio;
440440
}
441441

442+
/// Set the zoom level for the selected camera.
443+
///
444+
/// The supplied [zoom] value should be between the minimum and the maximum
445+
/// supported zoom level returned by `getMinZoomLevel` and `getMaxZoomLevel`.
446+
/// Throws a `CameraException` when an illegal zoom level is supplied.
447+
@override
448+
Future<void> setZoomLevel(int cameraId, double zoom) async {
449+
final CameraControl cameraControl = await camera!.getCameraControl();
450+
await cameraControl.setZoomRatio(zoom);
451+
}
452+
442453
/// The ui orientation changed.
443454
@override
444455
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {

packages/camera/camera_android_camerax/lib/src/camera_control.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,22 @@ class CameraControl extends JavaObject {
3232
late final _CameraControlHostApiImpl _api;
3333

3434
/// Enables or disables the torch of related [Camera] instance.
35+
///
36+
/// If the torch mode was unable to be changed, an error message will be
37+
/// added to [SystemServices.cameraErrorStreamController].
3538
Future<void> enableTorch(bool torch) async {
3639
return _api.enableTorchFromInstance(this, torch);
3740
}
41+
42+
/// Sets zoom of related [Camera] by ratio.
43+
///
44+
/// Ratio should be between what the `minZoomRatio` and `maxZoomRatio` of the
45+
/// [ZoomState] of the [CameraInfo] instance that is retrievable from the same
46+
/// [Camera] instance; otherwise, an error message will be added to
47+
/// [SystemServices.cameraErrorStreamController].
48+
Future<void> setZoomRatio(double ratio) async {
49+
return _api.setZoomRatioFromInstance(this, ratio);
50+
}
3851
}
3952

4053
/// Host API implementation of [CameraControl].
@@ -69,6 +82,18 @@ class _CameraControlHostApiImpl extends CameraControlHostApi {
6982
.add(e.message ?? 'The camera was unable to change torch modes.');
7083
}
7184
}
85+
86+
/// Sets zoom of specified [CameraControl] instance by ratio.
87+
Future<void> setZoomRatioFromInstance(
88+
CameraControl instance, double ratio) async {
89+
final int identifier = instanceManager.getIdentifier(instance)!;
90+
try {
91+
await setZoomRatio(identifier, ratio);
92+
} on PlatformException catch (e) {
93+
SystemServices.cameraErrorStreamController.add(e.message ??
94+
'Zoom ratio was unable to be set. If ratio was not out of range, newer value may have been set; otherwise, the camera may be closed.');
95+
}
96+
}
7297
}
7398

7499
/// Flutter API implementation of [CameraControl].

packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,6 +2685,28 @@ class CameraControlHostApi {
26852685
return;
26862686
}
26872687
}
2688+
2689+
Future<void> setZoomRatio(int arg_identifier, double arg_ratio) async {
2690+
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
2691+
'dev.flutter.pigeon.CameraControlHostApi.setZoomRatio', codec,
2692+
binaryMessenger: _binaryMessenger);
2693+
final List<Object?>? replyList = await channel
2694+
.send(<Object?>[arg_identifier, arg_ratio]) as List<Object?>?;
2695+
if (replyList == null) {
2696+
throw PlatformException(
2697+
code: 'channel-error',
2698+
message: 'Unable to establish connection on channel.',
2699+
);
2700+
} else if (replyList.length > 1) {
2701+
throw PlatformException(
2702+
code: replyList[0]! as String,
2703+
message: replyList[1] as String?,
2704+
details: replyList[2],
2705+
);
2706+
} else {
2707+
return;
2708+
}
2709+
}
26882710
}
26892711

26902712
abstract class CameraControlFlutterApi {

packages/camera/camera_android_camerax/pigeons/camerax_library.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,9 @@ abstract class FallbackStrategyHostApi {
424424
abstract class CameraControlHostApi {
425425
@async
426426
void enableTorch(int identifier, bool torch);
427+
428+
@async
429+
void setZoomRatio(int identifier, double ratio);
427430
}
428431

429432
@FlutterApi()

0 commit comments

Comments
 (0)