[camera] android-rework part 4: Android flash and zoom features (#3798)
* Base classes to support Android camera features
Co-authored-by: Andrew Coutts <andrewjohncoutts@gmail.com>
* Fixed formatting
* Applied feedback from PR
* Added Android Flash and Zoom features
Co-authored-by: Andrew Coutts <andrewjohncoutts@gmail.com>
* Use mockito-inline
* Fix formatting issue
* Processed feedback on pull request.
* Fixed formatting
* Fixed formatting
* Swap docs to match correct methods
Co-authored-by: Andrew Coutts <andrewjohncoutts@gmail.com>
diff --git a/packages/camera/camera/android/build.gradle b/packages/camera/camera/android/build.gradle
index 7d7eb81..0907c1e 100644
--- a/packages/camera/camera/android/build.gradle
+++ b/packages/camera/camera/android/build.gradle
@@ -49,7 +49,7 @@
dependencies {
compileOnly 'androidx.annotation:annotation:1.1.0'
testImplementation 'junit:junit:4.12'
- testImplementation 'org.mockito:mockito-core:3.5.13'
+ testImplementation 'org.mockito:mockito-inline:3.5.13'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'org.robolectric:robolectric:4.3'
}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashFeature.java
new file mode 100644
index 0000000..054c81f
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashFeature.java
@@ -0,0 +1,75 @@
+// 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.flash;
+
+import android.hardware.camera2.CaptureRequest;
+import io.flutter.plugins.camera.CameraProperties;
+import io.flutter.plugins.camera.features.CameraFeature;
+
+/** Controls the flash configuration on the {@link android.hardware.camera2} API. */
+public class FlashFeature extends CameraFeature<FlashMode> {
+ private FlashMode currentSetting = FlashMode.auto;
+
+ /**
+ * Creates a new instance of the {@link FlashFeature}.
+ *
+ * @param cameraProperties Collection of characteristics for the current camera device.
+ */
+ public FlashFeature(CameraProperties cameraProperties) {
+ super(cameraProperties);
+ }
+
+ @Override
+ public String getDebugName() {
+ return "FlashFeature";
+ }
+
+ @Override
+ public FlashMode getValue() {
+ return currentSetting;
+ }
+
+ @Override
+ public void setValue(FlashMode value) {
+ this.currentSetting = value;
+ }
+
+ @Override
+ public boolean checkIsSupported() {
+ Boolean available = cameraProperties.getFlashInfoAvailable();
+ return available != null && available;
+ }
+
+ @Override
+ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
+ if (!checkIsSupported()) {
+ return;
+ }
+
+ switch (currentSetting) {
+ case off:
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+
+ case always:
+ requestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+
+ case torch:
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
+ break;
+
+ case auto:
+ requestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+ }
+ }
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashMode.java
new file mode 100644
index 0000000..d4a5ee0
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashMode.java
@@ -0,0 +1,31 @@
+// 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.flash;
+
+// Mirrors flash_mode.dart
+public enum FlashMode {
+ off("off"),
+ auto("auto"),
+ always("always"),
+ torch("torch");
+
+ private final String strValue;
+
+ FlashMode(String strValue) {
+ this.strValue = strValue;
+ }
+
+ public static FlashMode getValueForString(String modeStr) {
+ for (FlashMode 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/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java
new file mode 100644
index 0000000..736fad4
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java
@@ -0,0 +1,94 @@
+// 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.zoomlevel;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CaptureRequest;
+import io.flutter.plugins.camera.CameraProperties;
+import io.flutter.plugins.camera.features.CameraFeature;
+
+/** Controls the zoom configuration on the {@link android.hardware.camera2} API. */
+public class ZoomLevelFeature extends CameraFeature<Float> {
+ private static final float MINIMUM_ZOOM_LEVEL = 1.0f;
+ private final boolean hasSupport;
+ private final Rect sensorArraySize;
+ private Float currentSetting = MINIMUM_ZOOM_LEVEL;
+ private Float maximumZoomLevel = MINIMUM_ZOOM_LEVEL;
+
+ /**
+ * Creates a new instance of the {@link ZoomLevelFeature}.
+ *
+ * @param cameraProperties Collection of characteristics for the current camera device.
+ */
+ public ZoomLevelFeature(CameraProperties cameraProperties) {
+ super(cameraProperties);
+
+ sensorArraySize = cameraProperties.getSensorInfoActiveArraySize();
+
+ if (sensorArraySize == null) {
+ maximumZoomLevel = MINIMUM_ZOOM_LEVEL;
+ hasSupport = false;
+ return;
+ }
+
+ Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom();
+ maximumZoomLevel =
+ ((maxDigitalZoom == null) || (maxDigitalZoom < MINIMUM_ZOOM_LEVEL))
+ ? MINIMUM_ZOOM_LEVEL
+ : maxDigitalZoom;
+
+ hasSupport = (Float.compare(maximumZoomLevel, MINIMUM_ZOOM_LEVEL) > 0);
+ }
+
+ @Override
+ public String getDebugName() {
+ return "ZoomLevelFeature";
+ }
+
+ @Override
+ public Float getValue() {
+ return currentSetting;
+ }
+
+ @Override
+ public void setValue(Float value) {
+ currentSetting = value;
+ }
+
+ @Override
+ public boolean checkIsSupported() {
+ return hasSupport;
+ }
+
+ @Override
+ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
+ if (!checkIsSupported()) {
+ return;
+ }
+
+ final Rect computedZoom =
+ ZoomUtils.computeZoom(
+ currentSetting, sensorArraySize, MINIMUM_ZOOM_LEVEL, maximumZoomLevel);
+ requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
+ }
+
+ /**
+ * Gets the minimum supported zoom level.
+ *
+ * @return The minimum zoom level.
+ */
+ public float getMinimumZoomLevel() {
+ return MINIMUM_ZOOM_LEVEL;
+ }
+
+ /**
+ * Gets the maximum supported zoom level.
+ *
+ * @return The maximum zoom level.
+ */
+ public float getMaximumZoomLevel() {
+ return maximumZoomLevel;
+ }
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java
new file mode 100644
index 0000000..a4890b9
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java
@@ -0,0 +1,40 @@
+// 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.zoomlevel;
+
+import android.graphics.Rect;
+import androidx.annotation.NonNull;
+import androidx.core.math.MathUtils;
+
+/**
+ * Utility class containing methods that assist with zoom features in the {@link
+ * android.hardware.camera2} API.
+ */
+final class ZoomUtils {
+
+ /**
+ * Computes an image sensor area based on the supplied zoom settings.
+ *
+ * <p>The returned image sensor area can be applied to the {@link android.hardware.camera2} API in
+ * order to control zoom levels.
+ *
+ * @param zoom The desired zoom level.
+ * @param sensorArraySize The current area of the image sensor.
+ * @param minimumZoomLevel The minimum supported zoom level.
+ * @param maximumZoomLevel The maximim supported zoom level.
+ * @return An image sensor area based on the supplied zoom settings
+ */
+ static Rect computeZoom(
+ float zoom, @NonNull Rect sensorArraySize, float minimumZoomLevel, float maximumZoomLevel) {
+ final float newZoom = MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel);
+
+ final int centerX = sensorArraySize.width() / 2;
+ final int centerY = sensorArraySize.height() / 2;
+ final int deltaX = (int) ((0.5f * sensorArraySize.width()) / newZoom);
+ final int deltaY = (int) ((0.5f * sensorArraySize.height()) / newZoom);
+
+ return new Rect(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY);
+ }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java
new file mode 100644
index 0000000..eccfb07
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java
@@ -0,0 +1,156 @@
+// 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.flash;
+
+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.CaptureRequest;
+import io.flutter.plugins.camera.CameraProperties;
+import org.junit.Test;
+
+public class FlashFeatureTest {
+ @Test
+ public void getDebugName_should_return_the_name_of_the_feature() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ assertEquals("FlashFeature", flashFeature.getDebugName());
+ }
+
+ @Test
+ public void getValue_should_return_auto_if_not_set() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ assertEquals(FlashMode.auto, flashFeature.getValue());
+ }
+
+ @Test
+ public void getValue_should_echo_the_set_value() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+ FlashMode expectedValue = FlashMode.torch;
+
+ flashFeature.setValue(expectedValue);
+ FlashMode actualValue = flashFeature.getValue();
+
+ assertEquals(expectedValue, actualValue);
+ }
+
+ @Test
+ public void checkIsSupported_should_return_false_when_flash_info_available_is_null() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(null);
+
+ assertFalse(flashFeature.checkIsSupported());
+ }
+
+ @Test
+ public void checkIsSupported_should_return_false_when_flash_info_available_is_false() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(false);
+
+ assertFalse(flashFeature.checkIsSupported());
+ }
+
+ @Test
+ public void checkIsSupported_should_return_true_when_flash_info_available_is_true() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true);
+
+ assertTrue(flashFeature.checkIsSupported());
+ }
+
+ @Test
+ public void updateBuilder_should_return_when_checkIsSupported_is_false() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(false);
+
+ flashFeature.updateBuilder(mockBuilder);
+
+ verify(mockBuilder, never()).set(any(), any());
+ }
+
+ @Test
+ public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_off() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true);
+
+ flashFeature.setValue(FlashMode.off);
+ flashFeature.updateBuilder(mockBuilder);
+
+ verify(mockBuilder, times(1))
+ .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ }
+
+ @Test
+ public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_always() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true);
+
+ flashFeature.setValue(FlashMode.always);
+ flashFeature.updateBuilder(mockBuilder);
+
+ verify(mockBuilder, times(1))
+ .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ }
+
+ @Test
+ public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_torch() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true);
+
+ flashFeature.setValue(FlashMode.torch);
+ flashFeature.updateBuilder(mockBuilder);
+
+ verify(mockBuilder, times(1))
+ .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
+ }
+
+ @Test
+ public void updateBuilder_should_set_ae_mode_and_flash_mode_when_flash_mode_is_auto() {
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+ FlashFeature flashFeature = new FlashFeature(mockCameraProperties);
+
+ when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true);
+
+ flashFeature.setValue(FlashMode.auto);
+ flashFeature.updateBuilder(mockBuilder);
+
+ verify(mockBuilder, times(1))
+ .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
new file mode 100644
index 0000000..c76708a
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java
@@ -0,0 +1,166 @@
+// 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.zoomlevel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+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.graphics.Rect;
+import android.hardware.camera2.CaptureRequest;
+import io.flutter.plugins.camera.CameraProperties;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+
+public class ZoomLevelFeatureTest {
+ private MockedStatic<ZoomUtils> mockedStaticCameraZoom;
+ private CameraProperties mockCameraProperties;
+ private ZoomUtils mockCameraZoom;
+ private Rect mockZoomArea;
+ private Rect mockSensorArray;
+
+ @Before
+ public void before() {
+ mockedStaticCameraZoom = mockStatic(ZoomUtils.class);
+ mockCameraProperties = mock(CameraProperties.class);
+ mockCameraZoom = mock(ZoomUtils.class);
+ mockZoomArea = mock(Rect.class);
+ mockSensorArray = mock(Rect.class);
+
+ mockedStaticCameraZoom
+ .when(() -> ZoomUtils.computeZoom(anyFloat(), any(), anyFloat(), anyFloat()))
+ .thenReturn(mockZoomArea);
+ }
+
+ @After
+ public void after() {
+ mockedStaticCameraZoom.close();
+ }
+
+ @Test
+ public void ctor_when_parameters_are_valid() {
+ when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+ when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
+
+ final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize();
+ verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom();
+ assertNotNull(zoomLevelFeature);
+ assertEquals(42f, zoomLevelFeature.getMaximumZoomLevel(), 0);
+ }
+
+ @Test
+ public void ctor_when_sensor_size_is_null() {
+ when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null);
+ when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
+
+ final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize();
+ verify(mockCameraProperties, never()).getScalerAvailableMaxDigitalZoom();
+ assertNotNull(zoomLevelFeature);
+ assertFalse(zoomLevelFeature.checkIsSupported());
+ assertEquals(zoomLevelFeature.getMaximumZoomLevel(), 1.0f, 0);
+ }
+
+ @Test
+ public void ctor_when_max_zoom_is_null() {
+ when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+ when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(null);
+
+ final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize();
+ verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom();
+ assertNotNull(zoomLevelFeature);
+ assertFalse(zoomLevelFeature.checkIsSupported());
+ assertEquals(zoomLevelFeature.getMaximumZoomLevel(), 1.0f, 0);
+ }
+
+ @Test
+ public void ctor_when_max_zoom_is_smaller_then_default_zoom_factor() {
+ when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+ when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(0.5f);
+
+ final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize();
+ verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom();
+ assertNotNull(zoomLevelFeature);
+ assertFalse(zoomLevelFeature.checkIsSupported());
+ assertEquals(zoomLevelFeature.getMaximumZoomLevel(), 1.0f, 0);
+ }
+
+ @Test
+ public void getDebugName_should_return_the_name_of_the_feature() {
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ assertEquals("ZoomLevelFeature", zoomLevelFeature.getDebugName());
+ }
+
+ @Test
+ public void getValue_should_return_null_if_not_set() {
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ assertEquals(1.0, (float) zoomLevelFeature.getValue(), 0);
+ }
+
+ @Test
+ public void getValue_should_echo_setValue() {
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ zoomLevelFeature.setValue(2.3f);
+
+ assertEquals(2.3f, (float) zoomLevelFeature.getValue(), 0);
+ }
+
+ @Test
+ public void checkIsSupport_returns_false_by_default() {
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ assertFalse(zoomLevelFeature.checkIsSupported());
+ }
+
+ @Test
+ public void updateBuilder_should_set_scalar_crop_region_when_checkIsSupport_is_true() {
+ when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+ when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
+
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+ CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class);
+
+ zoomLevelFeature.updateBuilder(mockBuilder);
+
+ verify(mockBuilder, times(1)).set(CaptureRequest.SCALER_CROP_REGION, mockZoomArea);
+ }
+
+ @Test
+ public void getMinimumZoomLevel() {
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ assertEquals(1.0f, zoomLevelFeature.getMinimumZoomLevel(), 0);
+ }
+
+ @Test
+ public void getMaximumZoomLevel() {
+ when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray);
+ when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f);
+
+ ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties);
+
+ assertEquals(42f, zoomLevelFeature.getMaximumZoomLevel(), 0);
+ }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
new file mode 100644
index 0000000..f83e5fb
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java
@@ -0,0 +1,64 @@
+// 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.zoomlevel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.Rect;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZoomUtilsTest {
+ @Test
+ public void setZoom_when_sensor_size_equals_zero_should_return_crop_region_of_zero() {
+ final Rect sensorSize = new Rect(0, 0, 0, 0);
+ final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f);
+
+ assertNotNull(computedZoom);
+ assertEquals(computedZoom.left, 0);
+ assertEquals(computedZoom.top, 0);
+ assertEquals(computedZoom.right, 0);
+ assertEquals(computedZoom.bottom, 0);
+ }
+
+ @Test
+ public void setZoom_when_sensor_size_is_valid_should_return_crop_region() {
+ final Rect sensorSize = new Rect(0, 0, 100, 100);
+ final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f);
+
+ assertNotNull(computedZoom);
+ assertEquals(computedZoom.left, 48);
+ assertEquals(computedZoom.top, 48);
+ assertEquals(computedZoom.right, 52);
+ assertEquals(computedZoom.bottom, 52);
+ }
+
+ @Test
+ public void setZoom_when_zoom_is_greater_then_max_zoom_clamp_to_max_zoom() {
+ final Rect sensorSize = new Rect(0, 0, 100, 100);
+ final Rect computedZoom = ZoomUtils.computeZoom(25f, sensorSize, 1f, 10f);
+
+ assertNotNull(computedZoom);
+ assertEquals(computedZoom.left, 45);
+ assertEquals(computedZoom.top, 45);
+ assertEquals(computedZoom.right, 55);
+ assertEquals(computedZoom.bottom, 55);
+ }
+
+ @Test
+ public void setZoom_when_zoom_is_smaller_then_min_zoom_clamp_to_min_zoom() {
+ final Rect sensorSize = new Rect(0, 0, 100, 100);
+ final Rect computedZoom = ZoomUtils.computeZoom(0.5f, sensorSize, 1f, 10f);
+
+ assertNotNull(computedZoom);
+ assertEquals(computedZoom.left, 0);
+ assertEquals(computedZoom.top, 0);
+ assertEquals(computedZoom.right, 100);
+ assertEquals(computedZoom.bottom, 100);
+ }
+}