Skip to content

Commit

Permalink
[camerax] Implements setFocusMode (flutter#6176)
Browse files Browse the repository at this point in the history
Implements `setFocusMode` (also adds support for disabling auto-cancel to `FocusMeteringAction` host API implementation to accomplish this) + some minor documentation improvements based on discoveries I made while working on this :)

Fixes flutter/flutter#120467.

~To be landed after: flutter#6110 Done :)
  • Loading branch information
camsim99 authored Mar 19, 2024
1 parent 52ed702 commit 3be3ec1
Show file tree
Hide file tree
Showing 18 changed files with 1,223 additions and 176 deletions.
5 changes: 5 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.6.0

* Implements `setFocusMode`, which makes this plugin reach feature parity with camera_android.
* Fixes `setExposureCompensationIndex` return value to use index returned by CameraX.

## 0.5.0+36

* Implements `setExposureMode`.
Expand Down
8 changes: 2 additions & 6 deletions packages/camera/camera_android_camerax/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
An Android implementation of [`camera`][1] that uses the [CameraX library][2].

*Note*: This package is under development, so please note the
[missing features and limitations](#missing-features-and-limitations), but
[missing features and limitations](#limitations), but
otherwise feel free to try out the current implementation and provide any
feedback by filing issues under [`flutter/flutter`][5] with `[camerax]` in
the title, which will be actively triaged.
Expand All @@ -22,18 +22,14 @@ dependencies:
camera_android_camerax: ^0.5.0
```
## Missing features and limitations
## Limitations
### 240p resolution configuration for video recording
240p resolution configuration for video recording is unsupported by CameraX,
and thus, the plugin will fall back to 480p if configured with a
`ResolutionPreset`.

### Focus mode configuration \[[Issue #120467][120467]\]

`setFocusMode` is unimplemented.

### Setting maximum duration and stream options for video capture

Calling `startVideoCapturing` with `VideoCaptureOptions` configured with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.flutter.plugins.camerax;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.MeteringPoint;
Expand All @@ -29,7 +30,9 @@ public class FocusMeteringActionHostApiImpl implements FocusMeteringActionHostAp
public static class FocusMeteringActionProxy {
/** Creates an instance of {@link FocusMeteringAction}. */
public @NonNull FocusMeteringAction create(
@NonNull List<MeteringPoint> meteringPoints, @NonNull List<Integer> meteringPointModes) {
@NonNull List<MeteringPoint> meteringPoints,
@NonNull List<Integer> meteringPointModes,
@Nullable Boolean disableAutoCancel) {
if (meteringPoints.size() >= 1 && meteringPoints.size() != meteringPointModes.size()) {
throw new IllegalArgumentException(
"One metering point must be specified and the number of specified metering points must match the number of specified metering point modes.");
Expand Down Expand Up @@ -59,6 +62,10 @@ public static class FocusMeteringActionProxy {
}
}

if (disableAutoCancel != null && disableAutoCancel == true) {
focusMeteringActionBuilder.disableAutoCancel();
}

return focusMeteringActionBuilder.build();
}

Expand Down Expand Up @@ -100,7 +107,9 @@ public FocusMeteringActionHostApiImpl(@NonNull InstanceManager instanceManager)

@Override
public void create(
@NonNull Long identifier, @NonNull List<MeteringPointInfo> meteringPointInfos) {
@NonNull Long identifier,
@NonNull List<MeteringPointInfo> meteringPointInfos,
@Nullable Boolean disableAutoCancel) {
final List<MeteringPoint> meteringPoints = new ArrayList<MeteringPoint>();
final List<Integer> meteringPointModes = new ArrayList<Integer>();
for (MeteringPointInfo meteringPointInfo : meteringPointInfos) {
Expand All @@ -110,6 +119,6 @@ public void create(
}

instanceManager.addDartCreatedInstance(
proxy.create(meteringPoints, meteringPointModes), identifier);
proxy.create(meteringPoints, meteringPointModes, disableAutoCancel), identifier);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3796,7 +3796,10 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface FocusMeteringActionHostApi {

void create(@NonNull Long identifier, @NonNull List<MeteringPointInfo> meteringPointInfos);
void create(
@NonNull Long identifier,
@NonNull List<MeteringPointInfo> meteringPointInfos,
@Nullable Boolean disableAutoCancel);

/** The codec used by FocusMeteringActionHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
Expand All @@ -3822,10 +3825,12 @@ static void setup(
Number identifierArg = (Number) args.get(0);
List<MeteringPointInfo> meteringPointInfosArg =
(List<MeteringPointInfo>) args.get(1);
Boolean disableAutoCancelArg = (Boolean) args.get(2);
try {
api.create(
(identifierArg == null) ? null : identifierArg.longValue(),
meteringPointInfosArg);
meteringPointInfosArg,
disableAutoCancelArg);
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -43,7 +44,7 @@ public void tearDown() {
}

@Test
public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatHasMode() {
public void hostApiCreate_createsExpectedFocusMeteringActionWithInitialPointThatHasMode() {
FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy =
spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy());
FocusMeteringActionHostApiImpl hostApi =
Expand Down Expand Up @@ -89,7 +90,7 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatH
List<MeteringPointInfo> mockMeteringPointInfos =
Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3);

hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos);
hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos, null);

verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode);
verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3);
Expand All @@ -98,7 +99,8 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatH
}

@Test
public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() {
public void
hostApiCreate_createsExpectedFocusMeteringActionWithInitialPointThatDoesNotHaveMode() {
FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy =
spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy());
FocusMeteringActionHostApiImpl hostApi =
Expand Down Expand Up @@ -142,11 +144,49 @@ public void hostApiCreatecreatesExpectedFocusMeteringActionWithInitialPointThatD
List<MeteringPointInfo> mockMeteringPointInfos =
Arrays.asList(fakeMeteringPointInfo1, fakeMeteringPointInfo2, fakeMeteringPointInfo3);

hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos);
hostApi.create(focusMeteringActionIdentifier, mockMeteringPointInfos, null);

verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint2, mockMeteringPoint2Mode);
verify(mockFocusMeteringActionBuilder).addPoint(mockMeteringPoint3);
assertEquals(
testInstanceManager.getInstance(focusMeteringActionIdentifier), focusMeteringAction);
}

@Test
public void hostApiCreate_disablesAutoCancelAsExpected() {
FocusMeteringActionHostApiImpl.FocusMeteringActionProxy proxySpy =
spy(new FocusMeteringActionHostApiImpl.FocusMeteringActionProxy());
FocusMeteringActionHostApiImpl hostApi =
new FocusMeteringActionHostApiImpl(testInstanceManager, proxySpy);

FocusMeteringAction.Builder mockFocusMeteringActionBuilder =
mock(FocusMeteringAction.Builder.class);
final MeteringPoint mockMeteringPoint = mock(MeteringPoint.class);
final Long mockMeteringPointId = 47L;

MeteringPointInfo fakeMeteringPointInfo =
new MeteringPointInfo.Builder()
.setMeteringPointId(mockMeteringPointId)
.setMeteringMode(null)
.build();

testInstanceManager.addDartCreatedInstance(mockMeteringPoint, mockMeteringPointId);

when(proxySpy.getFocusMeteringActionBuilder(mockMeteringPoint))
.thenReturn(mockFocusMeteringActionBuilder);
when(mockFocusMeteringActionBuilder.build()).thenReturn(focusMeteringAction);

List<MeteringPointInfo> mockMeteringPointInfos = Arrays.asList(fakeMeteringPointInfo);

// Test not disabling auto cancel.
hostApi.create(73L, mockMeteringPointInfos, /* disableAutoCancel */ null);
verify(mockFocusMeteringActionBuilder, never()).disableAutoCancel();

hostApi.create(74L, mockMeteringPointInfos, /* disableAutoCancel */ false);
verify(mockFocusMeteringActionBuilder, never()).disableAutoCancel();

// Test disabling auto cancel.
hostApi.create(75L, mockMeteringPointInfos, /* disableAutoCancel */ true);
verify(mockFocusMeteringActionBuilder).disableAutoCancel();
}
}
10 changes: 6 additions & 4 deletions packages/camera/camera_android_camerax/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,9 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
children: <Widget>[
TextButton(
style: styleAuto,
onPressed:
() {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.auto)
: null,
onLongPress: () {
if (controller != null) {
CameraPlatform.instance
Expand All @@ -490,8 +491,9 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
),
TextButton(
style: styleLocked,
onPressed:
() {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.locked)
: null,
child: const Text('LOCKED'),
),
],
Expand Down
Loading

0 comments on commit 3be3ec1

Please sign in to comment.