Skip to content

Commit

Permalink
[camera] android-rework part 2: Android auto focus feature (flutter#3796
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mvanbeusekom authored Apr 27, 2021
1 parent 4407b5a commit 5c1c0c0
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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.camera.features.autofocus;

import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.features.CameraFeature;

/** Controls the auto focus configuration on the {@see anddroid.hardware.camera2} API. */
public class AutoFocusFeature extends CameraFeature<FocusMode> {
private FocusMode currentSetting = FocusMode.auto;

// When switching recording modes this feature is re-created with the appropriate setting here.
private final boolean recordingVideo;

/**
* Creates a new instance of the {@see AutoFocusFeature}.
*
* @param cameraProperties Collection of the characteristics for the current camera device.
* @param recordingVideo Indicates whether the camera is currently recording video.
*/
public AutoFocusFeature(CameraProperties cameraProperties, boolean recordingVideo) {
super(cameraProperties);
this.recordingVideo = recordingVideo;
}

@Override
public String getDebugName() {
return "AutoFocusFeature";
}

@Override
public FocusMode getValue() {
return currentSetting;
}

@Override
public void setValue(FocusMode value) {
this.currentSetting = value;
}

@Override
public boolean checkIsSupported() {
int[] modes = cameraProperties.getControlAutoFocusAvailableModes();

final Float minFocus = cameraProperties.getLensInfoMinimumFocusDistance();

// Check if the focal length of the lens is fixed. If the minimum focus distance == 0, then the
// focal length is fixed. The minimum focus distance can be null on some devices: https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
boolean isFixedLength = minFocus == null || minFocus == 0;

return !isFixedLength
&& !(modes.length == 0
|| (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF));
}

@Override
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
if (!checkIsSupported()) {
return;
}

switch (currentSetting) {
case locked:
// When locking the auto-focus the camera device should do a one-time focus and afterwards
// set the auto-focus to idle. This is accomplished by setting the CONTROL_AF_MODE to
// CONTROL_AF_MODE_AUTO.
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
break;
case auto:
requestBuilder.set(
CaptureRequest.CONTROL_AF_MODE,
recordingVideo
? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO
: CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
default:
break;
}
}
}
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.camera.features.autofocus;

// Mirrors focus_mode.dart
public enum FocusMode {
auto("auto"),
locked("locked");

private final String strValue;

FocusMode(String strValue) {
this.strValue = strValue;
}

public static FocusMode getValueForString(String modeStr) {
for (FocusMode value : values()) {
if (value.strValue.equals(modeStr)) return value;
}
return null;
}

@Override
public String toString() {
return strValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// 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.camera.features.autofocus;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import io.flutter.plugins.camera.CameraProperties;
import org.junit.Test;

public class AutoFocusFeatureTest {
private static final int[] FOCUS_MODES_ONLY_OFF =
new int[] {CameraCharacteristics.CONTROL_AF_MODE_OFF};
private static final int[] FOCUS_MODES =
new int[] {
CameraCharacteristics.CONTROL_AF_MODE_OFF, CameraCharacteristics.CONTROL_AF_MODE_AUTO
};

@Test
public void getDebugName_should_return_the_name_of_the_feature() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

assertEquals("AutoFocusFeature", autoFocusFeature.getDebugName());
}

@Test
public void getValue_should_return_auto_if_not_set() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

assertEquals(FocusMode.auto, autoFocusFeature.getValue());
}

@Test
public void getValue_should_echo_the_set_value() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);
FocusMode expectedValue = FocusMode.locked;

autoFocusFeature.setValue(expectedValue);
FocusMode actualValue = autoFocusFeature.getValue();

assertEquals(expectedValue, actualValue);
}

@Test
public void checkIsSupported_should_return_false_when_minimum_focus_distance_is_zero() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(0.0F);

assertFalse(autoFocusFeature.checkIsSupported());
}

@Test
public void checkIsSupported_should_return_false_when_minimum_focus_distance_is_null() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(null);

assertFalse(autoFocusFeature.checkIsSupported());
}

@Test
public void checkIsSupport_should_return_false_when_no_focus_modes_are_available() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(new int[] {});
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F);

assertFalse(autoFocusFeature.checkIsSupported());
}

@Test
public void checkIsSupport_should_return_false_when_only_focus_off_is_available() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES_ONLY_OFF);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F);

assertFalse(autoFocusFeature.checkIsSupported());
}

@Test
public void checkIsSupport_should_return_true_when_only_multiple_focus_modes_are_available() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F);

assertTrue(autoFocusFeature.checkIsSupported());
}

@Test
public void updateBuilder_should_return_when_checkIsSupported_is_false() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(0.0F);

autoFocusFeature.updateBuilder(mockBuilder);

verify(mockBuilder, never()).set(any(), any());
}

@Test
public void updateBuilder_should_set_control_mode_to_auto_when_focus_is_locked() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F);

autoFocusFeature.setValue(FocusMode.locked);
autoFocusFeature.updateBuilder(mockBuilder);

verify(mockBuilder, times(1))
.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
}

@Test
public void
updateBuilder_should_set_control_mode_to_continuous_video_when_focus_is_auto_and_recording_video() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, true);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F);

autoFocusFeature.setValue(FocusMode.auto);
autoFocusFeature.updateBuilder(mockBuilder);

verify(mockBuilder, times(1))
.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
}

@Test
public void
updateBuilder_should_set_control_mode_to_continuous_video_when_focus_is_auto_and_not_recording_video() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false);

when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES);
when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F);

autoFocusFeature.setValue(FocusMode.auto);
autoFocusFeature.updateBuilder(mockBuilder);

verify(mockBuilder, times(1))
.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.camera.features.autofocus;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class FocusModeTest {

@Test
public void getValueForString_returns_correct_values() {
assertEquals(
"Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto);
assertEquals(
"Returns FocusMode.locked for 'locked'",
FocusMode.getValueForString("locked"),
FocusMode.locked);
}

@Test
public void getValueForString_returns_null_for_nonexistant_value() {
assertEquals(
"Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null);
}

@Test
public void toString_returns_correct_value() {
assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto");
assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked");
}
}

0 comments on commit 5c1c0c0

Please sign in to comment.