diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeature.java new file mode 100644 index 000000000000..1789a964253b --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeature.java @@ -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 { + 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; + } + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/autofocus/FocusMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/autofocus/FocusMode.java new file mode 100644 index 000000000000..98434bc69f47 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/autofocus/FocusMode.java @@ -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; + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java new file mode 100644 index 000000000000..84e4ad0d0e91 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java @@ -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); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java new file mode 100644 index 000000000000..70d52d458d4d --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java @@ -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"); + } +}