From 2b7aa9b12c5cbd57b8225f38670e3ec81f11c671 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 8 Apr 2021 13:11:03 +0200 Subject: [PATCH 01/10] Base classes to support Android camera features Co-authored-by: Andrew Coutts --- .../plugins/camera/CameraProperties.java | 168 ++++++++++++ .../camera/features/CameraFeature.java | 56 ++++ .../camera/CameraPropertiesImplTest.java | 254 ++++++++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java new file mode 100644 index 000000000000..1a8bb742d811 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java @@ -0,0 +1,168 @@ +// 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; + +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.os.Build.VERSION_CODES; +import android.util.Range; +import android.util.Rational; +import android.util.Size; +import androidx.annotation.RequiresApi; + +/** + * An interface allowing access to the different characteristics of the device's camera. + */ +public interface CameraProperties { + String getCameraName(); + + Range[] getControlAutoExposureAvailableTargetFpsRanges(); + + Range getControlAutoExposureCompensationRange(); + + double getControlAutoExposureCompensationStep(); + + int[] getControlAutoFocusAvailableModes(); + + Integer getControlMaxRegionsAutoExposure(); + + Integer getControlMaxRegionsAutoFocus(); + + int[] getDistortionCorrectionAvailableModes(); + + Boolean getFlashInfoAvailable(); + + int getLensFacing(); + + Float getLensInfoMinimumFocusDistance(); + + Float getScalerAvailableMaxDigitalZoom(); + + Rect getSensorInfoActiveArraySize(); + + Size getSensorInfoPixelArraySize(); + + Rect getSensorInfoPreCorrectionActiveArraySize(); + + int getSensorOrientation(); + + int getHardwareLevel(); + + int[] getAvailableNoiseReductionModes(); +} + +/** + * Implementation of the @see CameraProperties interface using the @see android.hardware.camera2.CameraCharacteristics + * class to access the different characteristics. + */ +class CameraPropertiesImpl implements CameraProperties { + private final CameraCharacteristics cameraCharacteristics; + private final String cameraName; + + public CameraPropertiesImpl(String cameraName, CameraManager cameraManager) + throws CameraAccessException { + this.cameraName = cameraName; + this.cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName); + } + + @Override + public String getCameraName() { + return cameraName; + } + + @Override + public Range[] getControlAutoExposureAvailableTargetFpsRanges() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + } + + @Override + public Range getControlAutoExposureCompensationRange() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + } + + @Override + public double getControlAutoExposureCompensationStep() { + Rational rational = + cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + + return rational == null ? 0.0 : rational.doubleValue(); + } + + @Override + public int[] getControlAutoFocusAvailableModes() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + } + + @Override + public Integer getControlMaxRegionsAutoExposure() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + } + + @Override + public Integer getControlMaxRegionsAutoFocus() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + } + + @RequiresApi(api = VERSION_CODES.P) + @Override + public int[] getDistortionCorrectionAvailableModes() { + return cameraCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + } + + @Override + public Boolean getFlashInfoAvailable() { + return cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + } + + @Override + public int getLensFacing() { + return cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); + } + + @Override + public Float getLensInfoMinimumFocusDistance() { + return cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); + } + + @Override + public Float getScalerAvailableMaxDigitalZoom() { + return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); + } + + @Override + public Rect getSensorInfoActiveArraySize() { + return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + } + + @Override + public Size getSensorInfoPixelArraySize() { + return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); + } + + @RequiresApi(api = VERSION_CODES.M) + @Override + public Rect getSensorInfoPreCorrectionActiveArraySize() { + return cameraCharacteristics.get( + CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); + } + + @Override + public int getSensorOrientation() { + return cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + } + + @Override + public int getHardwareLevel() { + return cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + } + + @Override + public int[] getAvailableNoiseReductionModes() { + return cameraCharacteristics.get( + CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); + } +} \ No newline at end of file diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java new file mode 100644 index 000000000000..618c637f7589 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java @@ -0,0 +1,56 @@ +// 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; + +import android.hardware.camera2.CaptureRequest; +import androidx.annotation.NonNull; +import io.flutter.plugins.camera.CameraProperties; + +/** + * An interface describing a feature in the camera. This holds a setting value of type T and must + * implement a means to check if this setting is supported by the current camera properties. It also + * must implement a builder update method which will update a given capture request builder for this + * feature's current setting value. + * + * @param + */ +public abstract class CameraFeature { + protected final CameraProperties cameraProperties; + + protected CameraFeature(@NonNull CameraProperties cameraProperties) { + this.cameraProperties = cameraProperties; + } + + /** Debug name for this feature. */ + public abstract String getDebugName(); + + /** + * Get the current value of this feature's setting. + * + * @return + */ + public abstract T getValue(); + + /** + * Set a new value for this feature's setting. + * + * @param value + */ + public abstract void setValue(T value); + + /** + * Returns whether or not this feature is supported. + * + * @return + */ + public abstract boolean checkIsSupported(); + + /** + * Update the setting in a provided request builder. + * + * @param requestBuilder + */ + public abstract void updateBuilder(CaptureRequest.Builder requestBuilder); +} \ No newline at end of file diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java new file mode 100644 index 000000000000..189bb2cd61fb --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java @@ -0,0 +1,254 @@ +// 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; + +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.util.Range; +import android.util.Rational; +import android.util.Size; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CameraPropertiesImplTest { + private static final String CAMERA_NAME = "test_camera"; + private final CameraCharacteristics mockCharacteristics = mock(CameraCharacteristics.class); + private final CameraManager mockCameraManager = mock(CameraManager.class); + + private CameraPropertiesImpl cameraProperties; + + @Before + public void before() { + try { + when(mockCameraManager.getCameraCharacteristics(CAMERA_NAME)).thenReturn(mockCharacteristics); + cameraProperties = new CameraPropertiesImpl(CAMERA_NAME, mockCameraManager); + } catch (CameraAccessException e) { + fail(); + } + } + + @Test + public void ctor_Should_return_valid_instance() throws CameraAccessException { + verify(mockCameraManager, times(1)).getCameraCharacteristics(CAMERA_NAME); + assertNotNull(cameraProperties); + } + + @Test + @SuppressWarnings("unchecked") + public void getControlAutoExposureAvailableTargetFpsRangesTest() { + Range mockRange = mock(Range.class); + Range[] mockRanges = new Range[] { mockRange }; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)).thenReturn(mockRanges); + + Range[] actualRanges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + assertArrayEquals(actualRanges, mockRanges); + } + + @Test + @SuppressWarnings("unchecked") + public void getControlAutoExposureCompensationRangeTest() { + Range mockRange = mock(Range.class); + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)).thenReturn(mockRange); + + Range actualRange = cameraProperties.getControlAutoExposureCompensationRange(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + assertEquals(actualRange, mockRange); + } + + @Test + public void getControlAutoExposureCompensationStep_Should_return_double_When_rational_is_not_null() { + double expectedStep = 3.1415926535; + Rational mockRational = mock(Rational.class); + + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)).thenReturn(mockRational); + when(mockRational.doubleValue()).thenReturn(expectedStep); + + double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + assertEquals(actualSteps, expectedStep, 0); + } + + @Test + public void getControlAutoExposureCompensationStep_Should_return_zero_When_rational_is_null() { + double expectedStep = 0.0; + + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)).thenReturn(null); + + double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + assertEquals(actualSteps, expectedStep, 0); + } + + @Test + public void getControlAutoFocusAvailableModesTest() { + int[] expectedAutoFocusModes = new int[] {0, 1, 2}; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)).thenReturn(expectedAutoFocusModes); + + int[] actualAutoFocusModes = cameraProperties.getControlAutoFocusAvailableModes(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + assertEquals(actualAutoFocusModes, expectedAutoFocusModes); + } + + @Test + public void getControlMaxRegionsAutoExposureTest() { + int expectedRegions = 42; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)).thenReturn(expectedRegions); + + int actualRegions = cameraProperties.getControlMaxRegionsAutoExposure(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + assertEquals(actualRegions, expectedRegions); + } + + @Test + public void getControlMaxRegionsAutoFocusTest() { + int expectedRegions = 42; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)).thenReturn(expectedRegions); + + int actualRegions = cameraProperties.getControlMaxRegionsAutoFocus(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + assertEquals(actualRegions, expectedRegions); + } + + @Test + public void getDistortionCorrectionAvailableModesTest() { + int[] expectedCorrectionModes = new int[] {0, 1, 2}; + when(mockCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES)).thenReturn(expectedCorrectionModes); + + int[] actualCorrectionModes = cameraProperties.getDistortionCorrectionAvailableModes(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + assertEquals(actualCorrectionModes, expectedCorrectionModes); + } + + @Test + public void getFlashInfoAvailableTest() { + boolean expectedAvailability = true; + when(mockCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)).thenReturn(expectedAvailability); + + boolean actualAvailability = cameraProperties.getFlashInfoAvailable(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + assertEquals(actualAvailability, expectedAvailability); + } + + @Test + public void getLensFacingTest() { + int expectedFacing = 42; + when(mockCharacteristics.get(CameraCharacteristics.LENS_FACING)).thenReturn(expectedFacing); + + int actualFacing = cameraProperties.getLensFacing(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.LENS_FACING); + assertEquals(actualFacing, expectedFacing); + } + + @Test + public void getLensInfoMinimumFocusDistanceTest() { + Float expectedFocusDistance = new Float(3.14); + when(mockCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)).thenReturn(expectedFocusDistance); + + Float actualFocusDistance = cameraProperties.getLensInfoMinimumFocusDistance(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); + assertEquals(actualFocusDistance, expectedFocusDistance); + } + + @Test + public void getScalerAvailableMaxDigitalZoomTest() { + Float expectedDigitalZoom = new Float(3.14); + when(mockCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)).thenReturn(expectedDigitalZoom); + + Float actualDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); + assertEquals(actualDigitalZoom, expectedDigitalZoom); + } + + @Test + public void getSensorInfoActiveArraySizeTest() { + Rect expectedArraySize = mock(Rect.class); + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)).thenReturn(expectedArraySize); + + Rect actualArraySize = cameraProperties.getSensorInfoActiveArraySize(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + assertEquals(actualArraySize, expectedArraySize); + } + + @Test + public void getSensorInfoPixelArraySizeTest() { + Size expectedArraySize = mock(Size.class); + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)).thenReturn(expectedArraySize); + + Size actualArraySize = cameraProperties.getSensorInfoPixelArraySize(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); + assertEquals(actualArraySize, expectedArraySize); + } + + @Test + public void getSensorInfoPreCorrectionActiveArraySize() { + Rect expectedArraySize = mock(Rect.class); + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE)).thenReturn(expectedArraySize); + + Rect actualArraySize = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); + assertEquals(actualArraySize, expectedArraySize); + } + + @Test + public void getSensorOrientationTest() { + int expectedOrientation = 42; + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)).thenReturn(expectedOrientation); + + int actualOrientation = cameraProperties.getSensorOrientation(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_ORIENTATION); + assertEquals(actualOrientation, expectedOrientation); + } + + @Test + public void getHardwareLevelTest() { + int expectedLevel = 42; + when(mockCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)).thenReturn(expectedLevel); + + int actualLevel = cameraProperties.getHardwareLevel(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + assertEquals(actualLevel, expectedLevel); + } + + @Test + public void getAvailableNoiseReductionModesTest() { + int[] expectedReductionModes = new int[] {0, 1, 2}; + when(mockCharacteristics.get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES)).thenReturn(expectedReductionModes); + + int[] actualReductionModes = cameraProperties.getAvailableNoiseReductionModes(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); + assertEquals(actualReductionModes, expectedReductionModes); + } +} From f7807420ba1963eb937104a05d7cca1519774dea Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 8 Apr 2021 13:51:58 +0200 Subject: [PATCH 02/10] Fixed formatting --- .../plugins/camera/CameraProperties.java | 256 +++++----- .../camera/features/CameraFeature.java | 74 +-- .../camera/CameraPropertiesImplTest.java | 482 +++++++++--------- 3 files changed, 418 insertions(+), 394 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java index 1a8bb742d811..6ed550bc233e 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java @@ -14,155 +14,153 @@ import android.util.Size; import androidx.annotation.RequiresApi; -/** - * An interface allowing access to the different characteristics of the device's camera. - */ +/** An interface allowing access to the different characteristics of the device's camera. */ public interface CameraProperties { - String getCameraName(); + String getCameraName(); - Range[] getControlAutoExposureAvailableTargetFpsRanges(); + Range[] getControlAutoExposureAvailableTargetFpsRanges(); - Range getControlAutoExposureCompensationRange(); + Range getControlAutoExposureCompensationRange(); - double getControlAutoExposureCompensationStep(); + double getControlAutoExposureCompensationStep(); - int[] getControlAutoFocusAvailableModes(); + int[] getControlAutoFocusAvailableModes(); - Integer getControlMaxRegionsAutoExposure(); + Integer getControlMaxRegionsAutoExposure(); - Integer getControlMaxRegionsAutoFocus(); + Integer getControlMaxRegionsAutoFocus(); - int[] getDistortionCorrectionAvailableModes(); + int[] getDistortionCorrectionAvailableModes(); - Boolean getFlashInfoAvailable(); + Boolean getFlashInfoAvailable(); - int getLensFacing(); + int getLensFacing(); - Float getLensInfoMinimumFocusDistance(); + Float getLensInfoMinimumFocusDistance(); - Float getScalerAvailableMaxDigitalZoom(); + Float getScalerAvailableMaxDigitalZoom(); - Rect getSensorInfoActiveArraySize(); + Rect getSensorInfoActiveArraySize(); - Size getSensorInfoPixelArraySize(); + Size getSensorInfoPixelArraySize(); - Rect getSensorInfoPreCorrectionActiveArraySize(); + Rect getSensorInfoPreCorrectionActiveArraySize(); - int getSensorOrientation(); + int getSensorOrientation(); - int getHardwareLevel(); + int getHardwareLevel(); - int[] getAvailableNoiseReductionModes(); + int[] getAvailableNoiseReductionModes(); } /** - * Implementation of the @see CameraProperties interface using the @see android.hardware.camera2.CameraCharacteristics - * class to access the different characteristics. + * Implementation of the @see CameraProperties interface using the @see + * android.hardware.camera2.CameraCharacteristics class to access the different characteristics. */ class CameraPropertiesImpl implements CameraProperties { - private final CameraCharacteristics cameraCharacteristics; - private final String cameraName; - - public CameraPropertiesImpl(String cameraName, CameraManager cameraManager) - throws CameraAccessException { - this.cameraName = cameraName; - this.cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName); - } - - @Override - public String getCameraName() { - return cameraName; - } - - @Override - public Range[] getControlAutoExposureAvailableTargetFpsRanges() { - return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); - } - - @Override - public Range getControlAutoExposureCompensationRange() { - return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); - } - - @Override - public double getControlAutoExposureCompensationStep() { - Rational rational = - cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); - - return rational == null ? 0.0 : rational.doubleValue(); - } - - @Override - public int[] getControlAutoFocusAvailableModes() { - return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); - } - - @Override - public Integer getControlMaxRegionsAutoExposure() { - return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); - } - - @Override - public Integer getControlMaxRegionsAutoFocus() { - return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); - } - - @RequiresApi(api = VERSION_CODES.P) - @Override - public int[] getDistortionCorrectionAvailableModes() { - return cameraCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); - } - - @Override - public Boolean getFlashInfoAvailable() { - return cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - } - - @Override - public int getLensFacing() { - return cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); - } - - @Override - public Float getLensInfoMinimumFocusDistance() { - return cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); - } - - @Override - public Float getScalerAvailableMaxDigitalZoom() { - return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); - } - - @Override - public Rect getSensorInfoActiveArraySize() { - return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - } - - @Override - public Size getSensorInfoPixelArraySize() { - return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); - } - - @RequiresApi(api = VERSION_CODES.M) - @Override - public Rect getSensorInfoPreCorrectionActiveArraySize() { - return cameraCharacteristics.get( - CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); - } - - @Override - public int getSensorOrientation() { - return cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - } - - @Override - public int getHardwareLevel() { - return cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); - } - - @Override - public int[] getAvailableNoiseReductionModes() { - return cameraCharacteristics.get( - CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); - } -} \ No newline at end of file + private final CameraCharacteristics cameraCharacteristics; + private final String cameraName; + + public CameraPropertiesImpl(String cameraName, CameraManager cameraManager) + throws CameraAccessException { + this.cameraName = cameraName; + this.cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName); + } + + @Override + public String getCameraName() { + return cameraName; + } + + @Override + public Range[] getControlAutoExposureAvailableTargetFpsRanges() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + } + + @Override + public Range getControlAutoExposureCompensationRange() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + } + + @Override + public double getControlAutoExposureCompensationStep() { + Rational rational = + cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + + return rational == null ? 0.0 : rational.doubleValue(); + } + + @Override + public int[] getControlAutoFocusAvailableModes() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + } + + @Override + public Integer getControlMaxRegionsAutoExposure() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + } + + @Override + public Integer getControlMaxRegionsAutoFocus() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + } + + @RequiresApi(api = VERSION_CODES.P) + @Override + public int[] getDistortionCorrectionAvailableModes() { + return cameraCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + } + + @Override + public Boolean getFlashInfoAvailable() { + return cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + } + + @Override + public int getLensFacing() { + return cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); + } + + @Override + public Float getLensInfoMinimumFocusDistance() { + return cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); + } + + @Override + public Float getScalerAvailableMaxDigitalZoom() { + return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); + } + + @Override + public Rect getSensorInfoActiveArraySize() { + return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + } + + @Override + public Size getSensorInfoPixelArraySize() { + return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); + } + + @RequiresApi(api = VERSION_CODES.M) + @Override + public Rect getSensorInfoPreCorrectionActiveArraySize() { + return cameraCharacteristics.get( + CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); + } + + @Override + public int getSensorOrientation() { + return cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + } + + @Override + public int getHardwareLevel() { + return cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + } + + @Override + public int[] getAvailableNoiseReductionModes() { + return cameraCharacteristics.get( + CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java index 618c637f7589..39ecc8f92a39 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java @@ -17,40 +17,40 @@ * @param */ public abstract class CameraFeature { - protected final CameraProperties cameraProperties; - - protected CameraFeature(@NonNull CameraProperties cameraProperties) { - this.cameraProperties = cameraProperties; - } - - /** Debug name for this feature. */ - public abstract String getDebugName(); - - /** - * Get the current value of this feature's setting. - * - * @return - */ - public abstract T getValue(); - - /** - * Set a new value for this feature's setting. - * - * @param value - */ - public abstract void setValue(T value); - - /** - * Returns whether or not this feature is supported. - * - * @return - */ - public abstract boolean checkIsSupported(); - - /** - * Update the setting in a provided request builder. - * - * @param requestBuilder - */ - public abstract void updateBuilder(CaptureRequest.Builder requestBuilder); -} \ No newline at end of file + protected final CameraProperties cameraProperties; + + protected CameraFeature(@NonNull CameraProperties cameraProperties) { + this.cameraProperties = cameraProperties; + } + + /** Debug name for this feature. */ + public abstract String getDebugName(); + + /** + * Get the current value of this feature's setting. + * + * @return + */ + public abstract T getValue(); + + /** + * Set a new value for this feature's setting. + * + * @param value + */ + public abstract void setValue(T value); + + /** + * Returns whether or not this feature is supported. + * + * @return + */ + public abstract boolean checkIsSupported(); + + /** + * Update the setting in a provided request builder. + * + * @param requestBuilder + */ + public abstract void updateBuilder(CaptureRequest.Builder requestBuilder); +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java index 189bb2cd61fb..2c0381744191 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java @@ -4,6 +4,15 @@ package io.flutter.plugins.camera; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +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.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; @@ -11,244 +20,261 @@ import android.util.Range; import android.util.Rational; import android.util.Size; - import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - public class CameraPropertiesImplTest { - private static final String CAMERA_NAME = "test_camera"; - private final CameraCharacteristics mockCharacteristics = mock(CameraCharacteristics.class); - private final CameraManager mockCameraManager = mock(CameraManager.class); - - private CameraPropertiesImpl cameraProperties; - - @Before - public void before() { - try { - when(mockCameraManager.getCameraCharacteristics(CAMERA_NAME)).thenReturn(mockCharacteristics); - cameraProperties = new CameraPropertiesImpl(CAMERA_NAME, mockCameraManager); - } catch (CameraAccessException e) { - fail(); - } - } - - @Test - public void ctor_Should_return_valid_instance() throws CameraAccessException { - verify(mockCameraManager, times(1)).getCameraCharacteristics(CAMERA_NAME); - assertNotNull(cameraProperties); - } - - @Test - @SuppressWarnings("unchecked") - public void getControlAutoExposureAvailableTargetFpsRangesTest() { - Range mockRange = mock(Range.class); - Range[] mockRanges = new Range[] { mockRange }; - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)).thenReturn(mockRanges); - - Range[] actualRanges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); - assertArrayEquals(actualRanges, mockRanges); - } - - @Test - @SuppressWarnings("unchecked") - public void getControlAutoExposureCompensationRangeTest() { - Range mockRange = mock(Range.class); - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)).thenReturn(mockRange); - - Range actualRange = cameraProperties.getControlAutoExposureCompensationRange(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); - assertEquals(actualRange, mockRange); - } - - @Test - public void getControlAutoExposureCompensationStep_Should_return_double_When_rational_is_not_null() { - double expectedStep = 3.1415926535; - Rational mockRational = mock(Rational.class); - - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)).thenReturn(mockRational); - when(mockRational.doubleValue()).thenReturn(expectedStep); - - double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); - assertEquals(actualSteps, expectedStep, 0); - } - - @Test - public void getControlAutoExposureCompensationStep_Should_return_zero_When_rational_is_null() { - double expectedStep = 0.0; - - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)).thenReturn(null); - - double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); - assertEquals(actualSteps, expectedStep, 0); - } - - @Test - public void getControlAutoFocusAvailableModesTest() { - int[] expectedAutoFocusModes = new int[] {0, 1, 2}; - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)).thenReturn(expectedAutoFocusModes); - - int[] actualAutoFocusModes = cameraProperties.getControlAutoFocusAvailableModes(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); - assertEquals(actualAutoFocusModes, expectedAutoFocusModes); - } - - @Test - public void getControlMaxRegionsAutoExposureTest() { - int expectedRegions = 42; - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)).thenReturn(expectedRegions); - - int actualRegions = cameraProperties.getControlMaxRegionsAutoExposure(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); - assertEquals(actualRegions, expectedRegions); - } - - @Test - public void getControlMaxRegionsAutoFocusTest() { - int expectedRegions = 42; - when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)).thenReturn(expectedRegions); - - int actualRegions = cameraProperties.getControlMaxRegionsAutoFocus(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); - assertEquals(actualRegions, expectedRegions); - } - - @Test - public void getDistortionCorrectionAvailableModesTest() { - int[] expectedCorrectionModes = new int[] {0, 1, 2}; - when(mockCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES)).thenReturn(expectedCorrectionModes); - - int[] actualCorrectionModes = cameraProperties.getDistortionCorrectionAvailableModes(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); - assertEquals(actualCorrectionModes, expectedCorrectionModes); - } - - @Test - public void getFlashInfoAvailableTest() { - boolean expectedAvailability = true; - when(mockCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)).thenReturn(expectedAvailability); - - boolean actualAvailability = cameraProperties.getFlashInfoAvailable(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - assertEquals(actualAvailability, expectedAvailability); - } - - @Test - public void getLensFacingTest() { - int expectedFacing = 42; - when(mockCharacteristics.get(CameraCharacteristics.LENS_FACING)).thenReturn(expectedFacing); - - int actualFacing = cameraProperties.getLensFacing(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.LENS_FACING); - assertEquals(actualFacing, expectedFacing); - } - - @Test - public void getLensInfoMinimumFocusDistanceTest() { - Float expectedFocusDistance = new Float(3.14); - when(mockCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)).thenReturn(expectedFocusDistance); - - Float actualFocusDistance = cameraProperties.getLensInfoMinimumFocusDistance(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); - assertEquals(actualFocusDistance, expectedFocusDistance); - } - - @Test - public void getScalerAvailableMaxDigitalZoomTest() { - Float expectedDigitalZoom = new Float(3.14); - when(mockCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)).thenReturn(expectedDigitalZoom); - - Float actualDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); - assertEquals(actualDigitalZoom, expectedDigitalZoom); - } - - @Test - public void getSensorInfoActiveArraySizeTest() { - Rect expectedArraySize = mock(Rect.class); - when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)).thenReturn(expectedArraySize); - - Rect actualArraySize = cameraProperties.getSensorInfoActiveArraySize(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - assertEquals(actualArraySize, expectedArraySize); - } - - @Test - public void getSensorInfoPixelArraySizeTest() { - Size expectedArraySize = mock(Size.class); - when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)).thenReturn(expectedArraySize); - - Size actualArraySize = cameraProperties.getSensorInfoPixelArraySize(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); - assertEquals(actualArraySize, expectedArraySize); + private static final String CAMERA_NAME = "test_camera"; + private final CameraCharacteristics mockCharacteristics = mock(CameraCharacteristics.class); + private final CameraManager mockCameraManager = mock(CameraManager.class); + + private CameraPropertiesImpl cameraProperties; + + @Before + public void before() { + try { + when(mockCameraManager.getCameraCharacteristics(CAMERA_NAME)).thenReturn(mockCharacteristics); + cameraProperties = new CameraPropertiesImpl(CAMERA_NAME, mockCameraManager); + } catch (CameraAccessException e) { + fail(); } + } - @Test - public void getSensorInfoPreCorrectionActiveArraySize() { - Rect expectedArraySize = mock(Rect.class); - when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE)).thenReturn(expectedArraySize); - - Rect actualArraySize = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); + @Test + public void ctor_Should_return_valid_instance() throws CameraAccessException { + verify(mockCameraManager, times(1)).getCameraCharacteristics(CAMERA_NAME); + assertNotNull(cameraProperties); + } + + @Test + @SuppressWarnings("unchecked") + public void getControlAutoExposureAvailableTargetFpsRangesTest() { + Range mockRange = mock(Range.class); + Range[] mockRanges = new Range[] {mockRange}; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)) + .thenReturn(mockRanges); + + Range[] actualRanges = + cameraProperties.getControlAutoExposureAvailableTargetFpsRanges(); + + verify(mockCharacteristics, times(1)) + .get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + assertArrayEquals(actualRanges, mockRanges); + } + + @Test + @SuppressWarnings("unchecked") + public void getControlAutoExposureCompensationRangeTest() { + Range mockRange = mock(Range.class); + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)) + .thenReturn(mockRange); + + Range actualRange = cameraProperties.getControlAutoExposureCompensationRange(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + assertEquals(actualRange, mockRange); + } + + @Test + public void + getControlAutoExposureCompensationStep_Should_return_double_When_rational_is_not_null() { + double expectedStep = 3.1415926535; + Rational mockRational = mock(Rational.class); + + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)) + .thenReturn(mockRational); + when(mockRational.doubleValue()).thenReturn(expectedStep); - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); - assertEquals(actualArraySize, expectedArraySize); - } - - @Test - public void getSensorOrientationTest() { - int expectedOrientation = 42; - when(mockCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)).thenReturn(expectedOrientation); + double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + assertEquals(actualSteps, expectedStep, 0); + } + + @Test + public void getControlAutoExposureCompensationStep_Should_return_zero_When_rational_is_null() { + double expectedStep = 0.0; + + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)) + .thenReturn(null); + + double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + assertEquals(actualSteps, expectedStep, 0); + } + + @Test + public void getControlAutoFocusAvailableModesTest() { + int[] expectedAutoFocusModes = new int[] {0, 1, 2}; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)) + .thenReturn(expectedAutoFocusModes); + + int[] actualAutoFocusModes = cameraProperties.getControlAutoFocusAvailableModes(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + assertEquals(actualAutoFocusModes, expectedAutoFocusModes); + } + + @Test + public void getControlMaxRegionsAutoExposureTest() { + int expectedRegions = 42; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) + .thenReturn(expectedRegions); + + int actualRegions = cameraProperties.getControlMaxRegionsAutoExposure(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + assertEquals(actualRegions, expectedRegions); + } + + @Test + public void getControlMaxRegionsAutoFocusTest() { + int expectedRegions = 42; + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) + .thenReturn(expectedRegions); + + int actualRegions = cameraProperties.getControlMaxRegionsAutoFocus(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + assertEquals(actualRegions, expectedRegions); + } + + @Test + public void getDistortionCorrectionAvailableModesTest() { + int[] expectedCorrectionModes = new int[] {0, 1, 2}; + when(mockCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES)) + .thenReturn(expectedCorrectionModes); + + int[] actualCorrectionModes = cameraProperties.getDistortionCorrectionAvailableModes(); + + verify(mockCharacteristics, times(1)) + .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + assertEquals(actualCorrectionModes, expectedCorrectionModes); + } - int actualOrientation = cameraProperties.getSensorOrientation(); + @Test + public void getFlashInfoAvailableTest() { + boolean expectedAvailability = true; + when(mockCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) + .thenReturn(expectedAvailability); + + boolean actualAvailability = cameraProperties.getFlashInfoAvailable(); - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_ORIENTATION); - assertEquals(actualOrientation, expectedOrientation); - } + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + assertEquals(actualAvailability, expectedAvailability); + } - @Test - public void getHardwareLevelTest() { - int expectedLevel = 42; - when(mockCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)).thenReturn(expectedLevel); + @Test + public void getLensFacingTest() { + int expectedFacing = 42; + when(mockCharacteristics.get(CameraCharacteristics.LENS_FACING)).thenReturn(expectedFacing); + + int actualFacing = cameraProperties.getLensFacing(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.LENS_FACING); + assertEquals(actualFacing, expectedFacing); + } + + @Test + public void getLensInfoMinimumFocusDistanceTest() { + Float expectedFocusDistance = new Float(3.14); + when(mockCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)) + .thenReturn(expectedFocusDistance); + + Float actualFocusDistance = cameraProperties.getLensInfoMinimumFocusDistance(); - int actualLevel = cameraProperties.getHardwareLevel(); + verify(mockCharacteristics, times(1)) + .get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); + assertEquals(actualFocusDistance, expectedFocusDistance); + } + + @Test + public void getScalerAvailableMaxDigitalZoomTest() { + Float expectedDigitalZoom = new Float(3.14); + when(mockCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)) + .thenReturn(expectedDigitalZoom); - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); - assertEquals(actualLevel, expectedLevel); - } - - @Test - public void getAvailableNoiseReductionModesTest() { - int[] expectedReductionModes = new int[] {0, 1, 2}; - when(mockCharacteristics.get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES)).thenReturn(expectedReductionModes); - - int[] actualReductionModes = cameraProperties.getAvailableNoiseReductionModes(); - - verify(mockCharacteristics, times(1)).get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); - assertEquals(actualReductionModes, expectedReductionModes); - } + Float actualDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom(); + + verify(mockCharacteristics, times(1)) + .get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); + assertEquals(actualDigitalZoom, expectedDigitalZoom); + } + + @Test + public void getSensorInfoActiveArraySizeTest() { + Rect expectedArraySize = mock(Rect.class); + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)) + .thenReturn(expectedArraySize); + + Rect actualArraySize = cameraProperties.getSensorInfoActiveArraySize(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + assertEquals(actualArraySize, expectedArraySize); + } + + @Test + public void getSensorInfoPixelArraySizeTest() { + Size expectedArraySize = mock(Size.class); + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)) + .thenReturn(expectedArraySize); + + Size actualArraySize = cameraProperties.getSensorInfoPixelArraySize(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); + assertEquals(actualArraySize, expectedArraySize); + } + + @Test + public void getSensorInfoPreCorrectionActiveArraySize() { + Rect expectedArraySize = mock(Rect.class); + when(mockCharacteristics.get( + CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE)) + .thenReturn(expectedArraySize); + + Rect actualArraySize = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); + + verify(mockCharacteristics, times(1)) + .get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); + assertEquals(actualArraySize, expectedArraySize); + } + + @Test + public void getSensorOrientationTest() { + int expectedOrientation = 42; + when(mockCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)) + .thenReturn(expectedOrientation); + + int actualOrientation = cameraProperties.getSensorOrientation(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_ORIENTATION); + assertEquals(actualOrientation, expectedOrientation); + } + + @Test + public void getHardwareLevelTest() { + int expectedLevel = 42; + when(mockCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)) + .thenReturn(expectedLevel); + + int actualLevel = cameraProperties.getHardwareLevel(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + assertEquals(actualLevel, expectedLevel); + } + + @Test + public void getAvailableNoiseReductionModesTest() { + int[] expectedReductionModes = new int[] {0, 1, 2}; + when(mockCharacteristics.get( + CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES)) + .thenReturn(expectedReductionModes); + + int[] actualReductionModes = cameraProperties.getAvailableNoiseReductionModes(); + + verify(mockCharacteristics, times(1)) + .get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); + assertEquals(actualReductionModes, expectedReductionModes); + } } From 76bc5bd2febb87319c678aa4c96cbcfe69ca32ed Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 20 Apr 2021 13:21:38 +0200 Subject: [PATCH 03/10] Applied feedback from PR --- .../plugins/camera/CameraProperties.java | 184 ++++++++++++++++++ .../camera/features/CameraFeature.java | 17 +- 2 files changed, 194 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java index 6ed550bc233e..a69ddd0410d4 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java @@ -16,40 +16,224 @@ /** An interface allowing access to the different characteristics of the device's camera. */ public interface CameraProperties { + + /** + * Returns the name (or identifier) of the camera device. + * + * @return String The name of the camera device. + */ String getCameraName(); + /** + * Returns the list of frame rate ranges for @see android.control.aeTargetFpsRange supported by + * this camera device. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_AE_TARGET_FPS_RANGE key. + * + * @return android.util.Range[] List of frame rate ranges supported by this camera + * device. + */ Range[] getControlAutoExposureAvailableTargetFpsRanges(); + /** + * Returns the maximum and minimum exposure compensation values for @see + * android.control.aeExposureCompensation, in counts of @see android.control.aeCompensationStep, + * that are supported by this camera device. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_RANGE key. + * + * @return android.util.Range Maximum and minimum exposure compensation supported by this + * camera device. + */ Range getControlAutoExposureCompensationRange(); + /** + * Returns the smallest step by which the exposure compensation can be changed. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP key. + * + * @return double Smallest step by which the exposure compensation can be changed. + */ double getControlAutoExposureCompensationStep(); + /** + * Returns a list of auto-focus modes for @see android.control.afMode that are supported by this + * camera device. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES key. + * + * @return int[] List of auto-focus modes supported by this camera device. + */ int[] getControlAutoFocusAvailableModes(); + /** + * Returns the maximum number of metering regions that can be used by the auto-exposure routine. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_MAX_REGIONS_AE key. + * + * @return Integer Maximum number of metering regions that can be used by the auto-exposure + * routine. + */ Integer getControlMaxRegionsAutoExposure(); + /** + * Returns the maximum number of metering regions that can be used by the auto-focus routine. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_MAX_REGIONS_AF key. + * + * @return Integer Maximum number of metering regions that can be used by the auto-focus routine. + */ Integer getControlMaxRegionsAutoFocus(); + /** + * Returns a list of distortion correction modes for @see android.distortionCorrection.mode that + * are supported by this camera device. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES key. + * + * @return int[] List of distortion correction modes supported by this camera device. + */ + @RequiresApi(api = VERSION_CODES.P) int[] getDistortionCorrectionAvailableModes(); + /** + * Returns whether this camera device has a flash unit. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#FLASH_INFO_AVAILABLE key. + * + * @return Boolean Whether this camera device has a flash unit. + */ Boolean getFlashInfoAvailable(); + /** + * Returns the direction the camera faces relative to device screen. + * + *

Possible values: + * + *

    + *
  • @see android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT + *
  • @see android.hardware.camera2.CameraMetadata.LENS_FACING_BACK + *
  • @see android.hardware.camera2.CameraMetadata.LENS_FACING_EXTERNAL + *
+ * + * By default maps to the @see android.hardware.camera2.CameraCharacteristics.LENS_FACING key. + * + * @return int Direction the camera faces relative to device screen. + */ int getLensFacing(); + /** + * Returns the shortest distance from front most surface of the lens that can be brought into + * sharp focus. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE key. + * + * @return Float Shortest distance from front most surface of the lens that can be brought into + * sharp focus. + */ Float getLensInfoMinimumFocusDistance(); + /** + * Returns the maximum ratio between both active area width and crop region width, and active area + * height and crop region height, for @see android.scaler.cropRegion. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key. + * + * @return Float Maximum ratio between both active area width and crop region width, and active + * area height and crop region height + */ Float getScalerAvailableMaxDigitalZoom(); + /** + * Returns the area of the image sensor which corresponds to active pixels after any geometric + * distortion correction has been applied. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE key. + * + * @return android.graphics.Rect area of the image sensor which corresponds to active pixels after + * any geometric distortion correction has been applied. + */ Rect getSensorInfoActiveArraySize(); + /** + * Returns the dimensions of the full pixel array, possibly including black calibration pixels. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE key. + * + * @return android.util.Size Dimensions of the full pixel array, possibly including black + * calibration pixels. + */ Size getSensorInfoPixelArraySize(); + /** + * Returns the area of the image sensor which corresponds to active pixels prior to the + * application of any geometric distortion correction. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * key. + * + * @return android.graphics.Rect Area of the image sensor which corresponds to active pixels prior + * to the application of any geometric distortion correction. + */ + @RequiresApi(api = VERSION_CODES.M) Rect getSensorInfoPreCorrectionActiveArraySize(); + /** + * Returns the clockwise angle through which the output image needs to be rotated to be upright on + * the device screen in its native orientation. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION key. + * + * @return int Clockwise angle through which the output image needs to be rotated to be upright on + * the device screen in its native orientation. + */ int getSensorOrientation(); + /** + * Returns a level which generally classifies the overall set of the camera device functionality. + * + *

Possible values: + * + *

    + *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY + *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED + *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL + *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEVEL_3 + *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL + *
+ * + * By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL key. + * + * @return int Level which generally classifies the overall set of the camera device + * functionality. + */ int getHardwareLevel(); + /** + * Returns a list of noise reduction modes for @see android.noiseReduction.mode that are supported + * by this camera device. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES + * key. + * + * @return int[] List of noise reduction modes that are supported by this camera device. + */ int[] getAvailableNoiseReductionModes(); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java index 39ecc8f92a39..ad800f5e1163 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java @@ -27,30 +27,33 @@ protected CameraFeature(@NonNull CameraProperties cameraProperties) { public abstract String getDebugName(); /** - * Get the current value of this feature's setting. + * Gets the current value of this feature's setting. * - * @return + * @return Current value of this feature's setting. */ public abstract T getValue(); /** - * Set a new value for this feature's setting. + * Sets a new value for this feature's setting. * - * @param value + * @param value New value for this feature's setting. */ public abstract void setValue(T value); /** * Returns whether or not this feature is supported. * - * @return + *

When the feature is not supported any {@see #value} is simply ignored by the camera plugin. + * + * @return boolean Whether or not this feature is supported. */ public abstract boolean checkIsSupported(); /** - * Update the setting in a provided request builder. + * Updates the setting in a provided {@see android.hardware.camera2.CaptureRequest.Builder}. * - * @param requestBuilder + * @param requestBuilder A {@see android.hardware.camera2.CaptureRequest.Builder} instance used to + * configure the settings and outputs needed to capture a single image from the camera device. */ public abstract void updateBuilder(CaptureRequest.Builder requestBuilder); } From 461309a8fbdbedf3e1dcb54b9b5b0b8ec81ed7c5 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 8 Apr 2021 15:06:04 +0200 Subject: [PATCH 04/10] Added Android exposure related features Co-authored-by: Andrew Coutts --- packages/camera/camera/android/build.gradle | 2 +- .../plugins/camera/features/Point.java | 16 ++ .../exposurelock/ExposureLockFeature.java | 58 +++++ .../features/exposurelock/ExposureMode.java | 29 +++ .../exposureoffset/ExposureOffsetFeature.java | 95 +++++++ .../exposureoffset/ExposureOffsetValue.java | 25 ++ .../exposurepoint/ExposurePointFeature.java | 77 ++++++ .../regionboundaries/CameraRegions.java | 77 ++++++ .../RegionBoundariesFeature.java | 94 +++++++ .../exposurelock/ExposureLockFeatureTest.java | 79 ++++++ .../exposurelock/ExposureModeTest.java | 37 +++ .../ExposureOffsetFeatureTest.java | 91 +++++++ .../ExposurePointFeatureTest.java | 195 ++++++++++++++ .../RegionBoundariesFeatureTest.java | 241 ++++++++++++++++++ .../plugins/camera/utils/TestUtils.java | 26 ++ 15 files changed, 1141 insertions(+), 1 deletion(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/Point.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java diff --git a/packages/camera/camera/android/build.gradle b/packages/camera/camera/android/build.gradle index 0b88fd10fb71..fa981d738015 100644 --- a/packages/camera/camera/android/build.gradle +++ b/packages/camera/camera/android/build.gradle @@ -49,7 +49,7 @@ android { 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/Point.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/Point.java new file mode 100644 index 000000000000..b6b64f92d987 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/Point.java @@ -0,0 +1,16 @@ +// 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; + +/** Represents a point on an x/y axis. */ +public class Point { + public final Double x; + public final Double y; + + public Point(Double x, Double y) { + this.x = x; + this.y = y; + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java new file mode 100644 index 000000000000..299fbdf0665c --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java @@ -0,0 +1,58 @@ +// 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.exposurelock; + +import android.hardware.camera2.CaptureRequest; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.features.CameraFeature; + +/** + * Exposure lock controls whether or not exposure mode is currenty locked or automatically metering. + */ +public class ExposureLockFeature extends CameraFeature { + private ExposureMode currentSetting = ExposureMode.auto; + + public ExposureLockFeature(CameraProperties cameraProperties) { + super(cameraProperties); + } + + @Override + public String getDebugName() { + return "ExposureLockFeature"; + } + + @Override + public ExposureMode getValue() { + return currentSetting; + } + + @Override + public void setValue(ExposureMode value) { + this.currentSetting = value; + } + + // Available on all devices. + @Override + public boolean checkIsSupported() { + return true; + } + + @Override + public void updateBuilder(CaptureRequest.Builder requestBuilder) { + if (!checkIsSupported()) { + return; + } + + switch (currentSetting) { + case locked: + requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); + break; + case auto: + default: + requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); + break; + } + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java new file mode 100644 index 000000000000..07e50121a0af --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.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.exposurelock; + +// Mirrors exposure_mode.dart +public enum ExposureMode { + auto("auto"), + locked("locked"); + + private final String strValue; + + ExposureMode(String strValue) { + this.strValue = strValue; + } + + public static ExposureMode getValueForString(String modeStr) { + for (ExposureMode 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/exposureoffset/ExposureOffsetFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java new file mode 100644 index 000000000000..8a2137e01bd4 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java @@ -0,0 +1,95 @@ +// 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.exposureoffset; + +import android.hardware.camera2.CaptureRequest; +import android.util.Range; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.features.CameraFeature; + +/** Exposure offset makes the image brighter or darker. */ +public class ExposureOffsetFeature extends CameraFeature { + private ExposureOffsetValue currentSetting; + private final double min; + private final double max; + + public ExposureOffsetFeature(CameraProperties cameraProperties) { + super(cameraProperties); + + this.min = getMinExposureOffset(); + this.max = getMaxExposureOffset(); + + // Initial offset of 0 + this.currentSetting = new ExposureOffsetValue(this.min, this.max, 0); + } + + @Override + public String getDebugName() { + return "ExposureOffsetFeature"; + } + + @Override + public ExposureOffsetValue getValue() { + return currentSetting; + } + + @Override + public void setValue(ExposureOffsetValue value) { + double stepSize = getExposureOffsetStepSize(); + this.currentSetting = new ExposureOffsetValue(min, max, (value.value / stepSize)); + } + + // Available on all devices. + @Override + public boolean checkIsSupported() { + return true; + } + + @Override + public void updateBuilder(CaptureRequest.Builder requestBuilder) { + if (!checkIsSupported()) { + return; + } + + requestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, (int) currentSetting.value); + } + + /** + * Return the minimum exposure offset double value. + * + * @return + */ + private double getMinExposureOffset() { + Range range = cameraProperties.getControlAutoExposureCompensationRange(); + double minStepped = range == null ? 0 : range.getLower(); + double stepSize = getExposureOffsetStepSize(); + return minStepped * stepSize; + } + + /** + * Return the max exposure offset double value. + * + * @return + */ + private double getMaxExposureOffset() { + Range range = cameraProperties.getControlAutoExposureCompensationRange(); + double maxStepped = range == null ? 0 : range.getUpper(); + double stepSize = getExposureOffsetStepSize(); + return maxStepped * stepSize; + } + + /** + * Returns the exposure offset step size. This is the smallest amount which the exposure offset + * can be changed. + * + *

Example: if this has a value of 0.5, then an aeExposureCompensation setting of -2 means that + * the actual AE offset is -1. + * + * @return + */ + public double getExposureOffsetStepSize() { + return cameraProperties.getControlAutoExposureCompensationStep(); + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java new file mode 100644 index 000000000000..fb08c58eeefd --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java @@ -0,0 +1,25 @@ +// 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.exposureoffset; + +/** + * This represents the exposure offset value. It holds the minimum and maximum values, as well as + * the current setting value. + */ +public class ExposureOffsetValue { + public final double min; + public final double max; + public final double value; + + public ExposureOffsetValue(double min, double max, double value) { + this.min = min; + this.max = max; + this.value = value; + } + + public ExposureOffsetValue(double value) { + this(0, 0, value); + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java new file mode 100644 index 000000000000..101413ec656f --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java @@ -0,0 +1,77 @@ +// 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.exposurepoint; + +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Log; +import androidx.annotation.NonNull; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.features.CameraFeature; +import io.flutter.plugins.camera.features.Point; +import io.flutter.plugins.camera.features.regionboundaries.CameraRegions; +import java.util.concurrent.Callable; + +/** Exposure point controls where in the frame exposure metering will come from. */ +public class ExposurePointFeature extends CameraFeature { + // Used later to always get the correct camera regions instance. + private final Callable getCameraRegions; + private Point currentSetting = new Point(0.0, 0.0); + + public ExposurePointFeature( + CameraProperties cameraProperties, Callable getCameraRegions) { + super(cameraProperties); + this.getCameraRegions = getCameraRegions; + } + + @Override + public String getDebugName() { + return "ExposurePointFeature"; + } + + @Override + public Point getValue() { + return currentSetting; + } + + @Override + public void setValue(@NonNull Point value) { + this.currentSetting = value; + + try { + if (value.x == null || value.y == null) { + getCameraRegions.call().resetAutoExposureMeteringRectangle(); + } else { + getCameraRegions.call().setAutoExposureMeteringRectangleFromPoint(value.x, value.y); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Whether or not this camera can set the exposure point. + @Override + public boolean checkIsSupported() { + Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoExposure(); + return supportedRegions != null && supportedRegions > 0; + } + + @Override + public void updateBuilder(CaptureRequest.Builder requestBuilder) { + if (!checkIsSupported()) { + return; + } + + MeteringRectangle aeRect; + try { + aeRect = getCameraRegions.call().getAEMeteringRectangle(); + requestBuilder.set( + CaptureRequest.CONTROL_AE_REGIONS, + aeRect == null ? null : new MeteringRectangle[] {aeRect}); + } catch (Exception e) { + Log.w("Camera", "Unable to retrieve the Auto Exposure metering rectangle.", e); + } + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java new file mode 100644 index 000000000000..f2c2b2e6a0d8 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java @@ -0,0 +1,77 @@ +// 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.regionboundaries; + +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Size; + +public final class CameraRegions { + private final Size maxBoundaries; + + private MeteringRectangle aeMeteringRectangle; + private MeteringRectangle afMeteringRectangle; + + public CameraRegions(Size maxBoundaries) { + assert (maxBoundaries == null || maxBoundaries.getWidth() > 0); + assert (maxBoundaries == null || maxBoundaries.getHeight() > 0); + this.maxBoundaries = maxBoundaries; + } + + public MeteringRectangle getAEMeteringRectangle() { + return aeMeteringRectangle; + } + + public MeteringRectangle getAFMeteringRectangle() { + return afMeteringRectangle; + } + + public Size getMaxBoundaries() { + return this.maxBoundaries; + } + + public void resetAutoExposureMeteringRectangle() { + this.aeMeteringRectangle = null; + } + + public void setAutoExposureMeteringRectangleFromPoint(double x, double y) { + this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); + } + + public void resetAutoFocusMeteringRectangle() { + this.afMeteringRectangle = null; + } + + public void setAutoFocusMeteringRectangleFromPoint(double x, double y) { + this.afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); + } + + public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) { + assert (x >= 0 && x <= 1); + assert (y >= 0 && y <= 1); + if (maxBoundaries == null) + throw new IllegalStateException( + "Functionality for managing metering rectangles is unavailable as this CameraRegions instance was initialized with null boundaries."); + + // Interpolate the target coordinate + int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); + int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); + // Determine the dimensions of the metering triangle (10th of the viewport) + int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); + int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); + // Adjust target coordinate to represent top-left corner of metering rectangle + targetX -= targetWidth / 2; + targetY -= targetHeight / 2; + // Adjust target coordinate as to not fall out of bounds + if (targetX < 0) targetX = 0; + if (targetY < 0) targetY = 0; + int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; + int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; + if (targetX > maxTargetX) targetX = maxTargetX; + if (targetY > maxTargetY) targetY = maxTargetY; + + // Build the metering rectangle + return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java new file mode 100644 index 000000000000..06b58a323f35 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.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.regionboundaries; + +import android.annotation.TargetApi; +import android.hardware.camera2.CaptureRequest; +import android.os.Build; +import android.util.Size; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.features.CameraFeature; +import java.util.Arrays; + +/** + * Holds the current region boundaries. When this is created, you must provide a + * CaptureRequestBuilder for which we can read the distortion correction settings from. + */ +public class RegionBoundariesFeature extends CameraFeature { + private final CameraRegions cameraRegions; + private Size currentSetting; + + public RegionBoundariesFeature( + CameraProperties cameraProperties, CaptureRequest.Builder requestBuilder) { + super(cameraProperties); + + // No distortion correction support + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P + || !supportsDistortionCorrection()) { + setValue(cameraProperties.getSensorInfoPixelArraySize()); + } else { + // Get the current distortion correction mode + Integer distortionCorrectionMode = + requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); + + // Return the correct boundaries depending on the mode + android.graphics.Rect rect; + if (distortionCorrectionMode == null + || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { + rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); + } else { + rect = cameraProperties.getSensorInfoActiveArraySize(); + } + + // Set new region size + setValue(rect == null ? null : new Size(rect.width(), rect.height())); + } + + // Create new camera regions using new size + cameraRegions = new CameraRegions(currentSetting); + } + + @Override + public String getDebugName() { + return "RegionBoundariesFeature"; + } + + @Override + public Size getValue() { + return currentSetting; + } + + @Override + public void setValue(Size value) { + this.currentSetting = value; + } + + // Available on all devices. + @Override + public boolean checkIsSupported() { + return true; + } + + @Override + public void updateBuilder(CaptureRequest.Builder requestBuilder) { + // Noop: when setting a region boundaries there is no need to update the request builder. + } + + @TargetApi(Build.VERSION_CODES.P) + private boolean supportsDistortionCorrection() { + int[] availableDistortionCorrectionModes = + cameraProperties.getDistortionCorrectionAvailableModes(); + if (availableDistortionCorrectionModes == null) availableDistortionCorrectionModes = new int[0]; + long nonOffModesSupported = + Arrays.stream(availableDistortionCorrectionModes) + .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) + .count(); + return nonOffModesSupported > 0; + } + + public CameraRegions getCameraRegions() { + return this.cameraRegions; + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java new file mode 100644 index 000000000000..d9e0a8d69c96 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java @@ -0,0 +1,79 @@ +// 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.exposurelock; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.hardware.camera2.CaptureRequest; +import io.flutter.plugins.camera.CameraProperties; +import org.junit.Test; + +public class ExposureLockFeatureTest { + @Test + public void getDebugName_should_return_the_name_of_the_feature() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); + + assertEquals("ExposureLockFeature", exposureLockFeature.getDebugName()); + } + + @Test + public void getValue_should_return_auto_if_not_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); + + assertEquals(ExposureMode.auto, exposureLockFeature.getValue()); + } + + @Test + public void getValue_should_echo_the_set_value() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); + ExposureMode expectedValue = ExposureMode.locked; + + exposureLockFeature.setValue(expectedValue); + ExposureMode actualValue = exposureLockFeature.getValue(); + + assertEquals(expectedValue, actualValue); + } + + @Test + public void checkIsSupported_should_return_true() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); + + assertTrue(exposureLockFeature.checkIsSupported()); + } + + @Test + public void + updateBuilder_should_set_control_ae_lock_to_false_when_auto_exposure_is_set_to_auto() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); + + exposureLockFeature.setValue(ExposureMode.auto); + exposureLockFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_LOCK, false); + } + + @Test + public void + updateBuilder_should_set_control_ae_lock_to_false_when_auto_exposure_is_set_to_locked() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); + + exposureLockFeature.setValue(ExposureMode.locked); + exposureLockFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_LOCK, true); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java new file mode 100644 index 000000000000..ad1d3d98f295 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java @@ -0,0 +1,37 @@ +// 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.exposurelock; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ExposureModeTest { + + @Test + public void getValueForString_returns_correct_values() { + assertEquals( + "Returns ExposureMode.auto for 'auto'", + ExposureMode.getValueForString("auto"), + ExposureMode.auto); + assertEquals( + "Returns ExposureMode.locked for 'locked'", + ExposureMode.getValueForString("locked"), + ExposureMode.locked); + } + + @Test + public void getValueForString_returns_null_for_nonexistant_value() { + assertEquals( + "Returns null for 'nonexistant'", ExposureMode.getValueForString("nonexistant"), null); + } + + @Test + public void toString_returns_correct_value() { + assertEquals("Returns 'auto' for ExposureMode.auto", ExposureMode.auto.toString(), "auto"); + assertEquals( + "Returns 'locked' for ExposureMode.locked", ExposureMode.locked.toString(), "locked"); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java new file mode 100644 index 000000000000..84787861119c --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java @@ -0,0 +1,91 @@ +// 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.exposureoffset; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +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 ExposureOffsetFeatureTest { + private static final ExposureOffsetValue DEFAULT_EXPOSURE_OFFSET_VALUE = + new ExposureOffsetValue(0, 0, 0); + + @Test + public void getDebugName_should_return_the_name_of_the_feature() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + + assertEquals("ExposureOffsetFeature", exposureOffsetFeature.getDebugName()); + } + + @Test + public void getValue_should_return_auto_if_not_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + + final ExposureOffsetValue actualValue = exposureOffsetFeature.getValue(); + + assertEquals(DEFAULT_EXPOSURE_OFFSET_VALUE.min, actualValue.min, 0); + assertEquals(DEFAULT_EXPOSURE_OFFSET_VALUE.max, actualValue.max, 0); + assertEquals(DEFAULT_EXPOSURE_OFFSET_VALUE.value, actualValue.value, 0); + } + + @Test + public void getValue_should_echo_the_set_value() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + ExposureOffsetValue expectedValue = new ExposureOffsetValue(1, 2, 4); + + when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); + + exposureOffsetFeature.setValue(expectedValue); + ExposureOffsetValue actualValue = exposureOffsetFeature.getValue(); + + assertEquals(expectedValue.min, actualValue.min, 1); + assertEquals(expectedValue.max, actualValue.max, 2); + assertEquals(expectedValue.value, actualValue.value, 8); + } + + @Test + public void + getExposureOffsetStepSize_should_return_the_control_exposure_compensation_step_value() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + + when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); + + assertEquals(0.5, exposureOffsetFeature.getExposureOffsetStepSize(), 0); + } + + @Test + public void checkIsSupported_should_return_true() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + + assertTrue(exposureOffsetFeature.checkIsSupported()); + } + + @Test + public void updateBuilder_should_set_control_ae_exposure_compensation_to_offset() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + ExposureOffsetValue expectedValue = new ExposureOffsetValue(1, 2, 4); + + when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); + + exposureOffsetFeature.setValue(expectedValue); + exposureOffsetFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 8); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java new file mode 100644 index 000000000000..ac7ccbf8e2de --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java @@ -0,0 +1,195 @@ +// 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.exposurepoint; + +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.ArgumentMatchers.eq; +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 android.hardware.camera2.params.MeteringRectangle; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.features.Point; +import io.flutter.plugins.camera.features.regionboundaries.CameraRegions; +import org.junit.Test; + +public class ExposurePointFeatureTest { + @Test + public void getDebugName_should_return_the_name_of_the_feature() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> null); + + assertEquals("ExposurePointFeature", exposurePointFeature.getDebugName()); + } + + @Test + public void getValue_should_return_default_point_if_not_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> null); + Point expectedPoint = new Point(0.0, 0.0); + Point actualPoint = exposurePointFeature.getValue(); + + assertEquals(expectedPoint.x, actualPoint.x); + assertEquals(expectedPoint.y, actualPoint.y); + } + + @Test + public void getValue_should_echo_the_set_value() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + Point expectedPoint = new Point(0.0, 0.0); + + exposurePointFeature.setValue(expectedPoint); + Point actualPoint = exposurePointFeature.getValue(); + + assertEquals(expectedPoint, actualPoint); + } + + @Test + public void setValue_should_reset_point_when_x_coord_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + + exposurePointFeature.setValue(new Point(null, 0.0)); + + verify(mockCameraRegions, times(1)).resetAutoExposureMeteringRectangle(); + } + + @Test + public void setValue_should_reset_point_when_y_coord_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + + exposurePointFeature.setValue(new Point(0.0, null)); + + verify(mockCameraRegions, times(1)).resetAutoExposureMeteringRectangle(); + } + + @Test + public void setValue_should_reset_point_when_valid_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + Point point = new Point(0.0, 0.0); + + exposurePointFeature.setValue(point); + + verify(mockCameraRegions, times(1)).setAutoExposureMeteringRectangleFromPoint(point.x, point.y); + } + + @Test + public void checkIsSupported_should_return_false_when_max_regions_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> null); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(null); + + assertFalse(exposurePointFeature.checkIsSupported()); + } + + @Test + public void checkIsSupported_should_return_false_when_max_regions_is_zero() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> null); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); + + assertFalse(exposurePointFeature.checkIsSupported()); + } + + @Test + public void checkIsSupported_should_return_true_when_max_regions_is_bigger_then_zero() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> null); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + + assertTrue(exposurePointFeature.checkIsSupported()); + } + + @Test + public void updateBuilder_should_return_when_checkIsSupported_is_false() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); + + exposurePointFeature.updateBuilder(null); + + verify(mockCameraRegions, never()).getAEMeteringRectangle(); + } + + @Test + public void updateBuilder_should_set_ae_regions_to_null_when_ae_metering_rectangle_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + when(mockCameraRegions.getAEMeteringRectangle()).thenReturn(null); + + exposurePointFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_REGIONS, null); + } + + @Test + public void updateBuilder_should_set_ae_regions_with_metering_rectangle() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + MeteringRectangle meteringRectangle = new MeteringRectangle(0, 0, 0, 0, 0); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + when(mockCameraRegions.getAEMeteringRectangle()).thenReturn(meteringRectangle); + + exposurePointFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)) + .set(eq(CaptureRequest.CONTROL_AE_REGIONS), any(MeteringRectangle[].class)); + } + + @Test + public void updateBuilder_should_silently_fail_when_exception_occurs() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = + new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + MeteringRectangle meteringRectangle = new MeteringRectangle(0, 0, 0, 0, 0); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + when(mockCameraRegions.getAEMeteringRectangle()).thenThrow(new IllegalArgumentException()); + + exposurePointFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, never()).set(any(), any()); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java new file mode 100644 index 000000000000..46c285ce4dc9 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java @@ -0,0 +1,241 @@ +// 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.regionboundaries; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.camera2.CaptureRequest; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.Size; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.utils.TestUtils; +import org.junit.Before; +import org.junit.Test; + +public class RegionBoundariesFeatureTest { + private Size mockSize; + + @Before + public void before() { + mockSize = mock(Size.class); + + when(mockSize.getHeight()).thenReturn(640); + when(mockSize.getWidth()).thenReturn(480); + } + + @Test + public void + ctor_should_initialize_with_sensor_info_pixel_array_size_when_running_pre_android_p() { + updateSdkVersion(VERSION_CODES.O_MR1); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); + + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertEquals(mockSize, regionBoundariesFeature.getValue()); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + ctor_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_null() { + updateSdkVersion(VERSION_CODES.P); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + when(mockCameraProperties.getDistortionCorrectionAvailableModes()).thenReturn(null); + when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); + + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertEquals(mockSize, regionBoundariesFeature.getValue()); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + ctor_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_off() { + updateSdkVersion(VERSION_CODES.P); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + when(mockCameraProperties.getDistortionCorrectionAvailableModes()) + .thenReturn(new int[] {CaptureRequest.DISTORTION_CORRECTION_MODE_OFF}); + when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); + + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertEquals(mockSize, regionBoundariesFeature.getValue()); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + ctor_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() { + updateSdkVersion(VERSION_CODES.P); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + when(mockCameraProperties.getDistortionCorrectionAvailableModes()) + .thenReturn( + new int[] { + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + }); + + when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)).thenReturn(null); + when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); + + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertNull(regionBoundariesFeature.getValue()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + ctor_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_off() { + updateSdkVersion(VERSION_CODES.P); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + when(mockCameraProperties.getDistortionCorrectionAvailableModes()) + .thenReturn( + new int[] { + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + }); + + when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) + .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_OFF); + when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); + + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertNull(regionBoundariesFeature.getValue()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + ctor_should_initialize_with_sensor_info_active_array_size_when_distortion_correction_mode_is_set() { + updateSdkVersion(VERSION_CODES.P); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + when(mockCameraProperties.getDistortionCorrectionAvailableModes()) + .thenReturn( + new int[] { + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + }); + + when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) + .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_FAST); + when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null); + + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertNull(regionBoundariesFeature.getValue()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void getDebugName_should_return_the_name_of_the_feature() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertEquals("RegionBoundariesFeature", regionBoundariesFeature.getDebugName()); + } + + @Test + public void getValue_should_return_null_if_not_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertNull(regionBoundariesFeature.getValue()); + } + + @Test + public void getValue_should_echo_setValue() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + regionBoundariesFeature.setValue(mockSize); + + assertEquals(mockSize, regionBoundariesFeature.getValue()); + } + + @Test + public void checkIsSupport_returns_true() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + RegionBoundariesFeature regionBoundariesFeature = + new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + + assertTrue(regionBoundariesFeature.checkIsSupported()); + } + + private static void updateSdkVersion(int version) { + TestUtils.setFinalStatic(VERSION.class, "SDK_INT", version); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java new file mode 100644 index 000000000000..9fc669527bfa --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java @@ -0,0 +1,26 @@ +// 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.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import org.junit.Assert; + +public class TestUtils { + public static void setFinalStatic(Class classToModify, String fieldName, Object newValue) { + try { + Field field = classToModify.getField(fieldName); + field.setAccessible(true); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + field.set(null, newValue); + } catch (Exception e) { + Assert.fail("Unable to mock static field: " + fieldName); + } + } +} From 7052b457bf9a12b06409e6087c5e5bdb627f5b22 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 21 Apr 2021 17:27:45 +0200 Subject: [PATCH 05/10] Added partial feedback from PR --- .../exposurelock/ExposureLockFeature.java | 17 +++---- .../features/exposurelock/ExposureMode.java | 9 ++++ .../exposureoffset/ExposureOffsetFeature.java | 50 +++++++++---------- .../exposureoffset/ExposureOffsetValue.java | 25 ---------- .../exposurepoint/ExposurePointFeature.java | 9 ++-- .../RegionBoundariesFeature.java | 2 +- .../ExposureOffsetFeatureTest.java | 26 ++++------ .../ExposurePointFeatureTest.java | 4 +- 8 files changed, 58 insertions(+), 84 deletions(-) delete mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java index 299fbdf0665c..e9ec884e132c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java @@ -9,11 +9,16 @@ import io.flutter.plugins.camera.features.CameraFeature; /** - * Exposure lock controls whether or not exposure mode is currenty locked or automatically metering. + * Controls whether or not the exposure mode is currently locked or automatically metering. */ public class ExposureLockFeature extends CameraFeature { private ExposureMode currentSetting = ExposureMode.auto; + /** + * Creates a new instance of the {@see ExposureLockFeature}. + * + * @param cameraProperties Collection of the characteristics for the current camera device. + */ public ExposureLockFeature(CameraProperties cameraProperties) { super(cameraProperties); } @@ -45,14 +50,6 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { return; } - switch (currentSetting) { - case locked: - requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); - break; - case auto: - default: - requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); - break; - } + requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, currentSetting == ExposureMode.locked); } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java index 07e50121a0af..427af684810a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java @@ -15,6 +15,15 @@ public enum ExposureMode { this.strValue = strValue; } + /** + * Tries to convert the supplied string into an {@see ExposureMode} enum value. + * + * When the supplied string doesn't match a valid {@see ExposureMode} enum value, null is + * returned. + * + * @param modeStr String value to convert into an {@see ExposureMode} enum value. + * @return Matching {@see ExposureMode} enum value, or null if no match is found. + */ public static ExposureMode getValueForString(String modeStr) { for (ExposureMode value : values()) { if (value.strValue.equals(modeStr)) return value; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java index 8a2137e01bd4..37d42e303ea6 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java @@ -6,23 +6,23 @@ import android.hardware.camera2.CaptureRequest; import android.util.Range; +import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; -/** Exposure offset makes the image brighter or darker. */ -public class ExposureOffsetFeature extends CameraFeature { - private ExposureOffsetValue currentSetting; - private final double min; - private final double max; +/** + * Controls the exposure offset making the resulting image brighter or darker. + */ +public class ExposureOffsetFeature extends CameraFeature { + private double currentSetting = 0; + /** + * Creates a new instance of the {@see ExposureOffsetFeature}. + * + * @param cameraProperties Collection of the characteristics for the current camera device. + */ public ExposureOffsetFeature(CameraProperties cameraProperties) { super(cameraProperties); - - this.min = getMinExposureOffset(); - this.max = getMaxExposureOffset(); - - // Initial offset of 0 - this.currentSetting = new ExposureOffsetValue(this.min, this.max, 0); } @Override @@ -31,14 +31,14 @@ public String getDebugName() { } @Override - public ExposureOffsetValue getValue() { + public Double getValue() { return currentSetting; } @Override - public void setValue(ExposureOffsetValue value) { + public void setValue(@NonNull Double value) { double stepSize = getExposureOffsetStepSize(); - this.currentSetting = new ExposureOffsetValue(min, max, (value.value / stepSize)); + this.currentSetting = value / stepSize; } // Available on all devices. @@ -53,15 +53,15 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { return; } - requestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, (int) currentSetting.value); + requestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, (int) currentSetting); } /** - * Return the minimum exposure offset double value. + * Returns the minimum exposure offset value, in counts of {@see #getExposureOffsetStepSize}. * - * @return + * @return double Minimum exposure offset value. */ - private double getMinExposureOffset() { + public double getMinExposureOffset() { Range range = cameraProperties.getControlAutoExposureCompensationRange(); double minStepped = range == null ? 0 : range.getLower(); double stepSize = getExposureOffsetStepSize(); @@ -69,11 +69,11 @@ private double getMinExposureOffset() { } /** - * Return the max exposure offset double value. + * Returns the maximum exposure offset value, in counts of {@see #getExposureOffsetStepSize}. * - * @return + * @return double Maximum exposure offset value. */ - private double getMaxExposureOffset() { + public double getMaxExposureOffset() { Range range = cameraProperties.getControlAutoExposureCompensationRange(); double maxStepped = range == null ? 0 : range.getUpper(); double stepSize = getExposureOffsetStepSize(); @@ -81,13 +81,13 @@ private double getMaxExposureOffset() { } /** - * Returns the exposure offset step size. This is the smallest amount which the exposure offset - * can be changed. + * Returns the smallest step by which the exposure compensation can be changed. * *

Example: if this has a value of 0.5, then an aeExposureCompensation setting of -2 means that - * the actual AE offset is -1. + * the actual AE offset is -1. More details can be found in the official Android documentation: + * https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#CONTROL_AE_COMPENSATION_STEP * - * @return + * @return double Smallest step by which the exposure compensation can be changed. */ public double getExposureOffsetStepSize() { return cameraProperties.getControlAutoExposureCompensationStep(); diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java deleted file mode 100644 index fb08c58eeefd..000000000000 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetValue.java +++ /dev/null @@ -1,25 +0,0 @@ -// 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.exposureoffset; - -/** - * This represents the exposure offset value. It holds the minimum and maximum values, as well as - * the current setting value. - */ -public class ExposureOffsetValue { - public final double min; - public final double max; - public final double value; - - public ExposureOffsetValue(double min, double max, double value) { - this.min = min; - this.max = max; - this.value = value; - } - - public ExposureOffsetValue(double value) { - this(0, 0, value); - } -} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java index 101413ec656f..88614db73642 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java @@ -64,14 +64,15 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { return; } - MeteringRectangle aeRect; + MeteringRectangle aeRect = null; try { aeRect = getCameraRegions.call().getAEMeteringRectangle(); - requestBuilder.set( - CaptureRequest.CONTROL_AE_REGIONS, - aeRect == null ? null : new MeteringRectangle[] {aeRect}); } catch (Exception e) { Log.w("Camera", "Unable to retrieve the Auto Exposure metering rectangle.", e); } + + requestBuilder.set( + CaptureRequest.CONTROL_AE_REGIONS, + aeRect == null ? null : new MeteringRectangle[] {aeRect}); } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java index 06b58a323f35..19339843885d 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java @@ -91,4 +91,4 @@ private boolean supportsDistortionCorrection() { public CameraRegions getCameraRegions() { return this.cameraRegions; } -} +} \ No newline at end of file diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java index 84787861119c..40d17fdc496e 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java @@ -16,9 +16,6 @@ import org.junit.Test; public class ExposureOffsetFeatureTest { - private static final ExposureOffsetValue DEFAULT_EXPOSURE_OFFSET_VALUE = - new ExposureOffsetValue(0, 0, 0); - @Test public void getDebugName_should_return_the_name_of_the_feature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); @@ -28,31 +25,27 @@ public void getDebugName_should_return_the_name_of_the_feature() { } @Test - public void getValue_should_return_auto_if_not_set() { + public void getValue_should_return_zero_if_not_set() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); - final ExposureOffsetValue actualValue = exposureOffsetFeature.getValue(); + final double actualValue = exposureOffsetFeature.getValue(); - assertEquals(DEFAULT_EXPOSURE_OFFSET_VALUE.min, actualValue.min, 0); - assertEquals(DEFAULT_EXPOSURE_OFFSET_VALUE.max, actualValue.max, 0); - assertEquals(DEFAULT_EXPOSURE_OFFSET_VALUE.value, actualValue.value, 0); + assertEquals(0.0, actualValue, 0); } @Test public void getValue_should_echo_the_set_value() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); - ExposureOffsetValue expectedValue = new ExposureOffsetValue(1, 2, 4); + double expectedValue = 4.0; when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); - exposureOffsetFeature.setValue(expectedValue); - ExposureOffsetValue actualValue = exposureOffsetFeature.getValue(); + exposureOffsetFeature.setValue(2.0); + double actualValue = exposureOffsetFeature.getValue(); - assertEquals(expectedValue.min, actualValue.min, 1); - assertEquals(expectedValue.max, actualValue.max, 2); - assertEquals(expectedValue.value, actualValue.value, 8); + assertEquals(expectedValue, actualValue, 0); } @Test @@ -79,13 +72,12 @@ public void updateBuilder_should_set_control_ae_exposure_compensation_to_offset( CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); - ExposureOffsetValue expectedValue = new ExposureOffsetValue(1, 2, 4); when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); - exposureOffsetFeature.setValue(expectedValue); + exposureOffsetFeature.setValue(2.0); exposureOffsetFeature.updateBuilder(mockBuilder); - verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 8); + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 4); } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java index ac7ccbf8e2de..2f9b8dd1b1b4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java @@ -183,13 +183,13 @@ public void updateBuilder_should_silently_fail_when_exception_occurs() { CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); - MeteringRectangle meteringRectangle = new MeteringRectangle(0, 0, 0, 0, 0); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); when(mockCameraRegions.getAEMeteringRectangle()).thenThrow(new IllegalArgumentException()); exposurePointFeature.updateBuilder(mockBuilder); - verify(mockBuilder, never()).set(any(), any()); + verify(mockBuilder, times(1)) + .set(CaptureRequest.CONTROL_AE_REGIONS, null); } } From 7a0b41e789836ec188271ecb24095907d7b1b35e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 18 May 2021 13:26:41 +0200 Subject: [PATCH 06/10] Updated feedback from PR --- .../exposureoffset/ExposureOffsetFeature.java | 6 +- .../exposurepoint/ExposurePointFeature.java | 26 ++- .../regionboundaries/CameraRegions.java | 77 ------- .../RegionBoundariesFeature.java | 94 -------- .../plugins/camera/types/CameraRegions.java | 204 ++++++++++++++++++ .../ExposurePointFeatureTest.java | 30 +-- .../CameraRegionsFactoryTest.java} | 109 ++++------ .../camera/{ => types}/CameraRegionsTest.java | 63 +++--- 8 files changed, 299 insertions(+), 310 deletions(-) delete mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java delete mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java rename packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/{features/regionboundaries/RegionBoundariesFeatureTest.java => types/CameraRegionsFactoryTest.java} (56%) rename packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/{ => types}/CameraRegionsTest.java (53%) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java index 37d42e303ea6..e7261ad2ad46 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java @@ -17,7 +17,7 @@ public class ExposureOffsetFeature extends CameraFeature { private double currentSetting = 0; /** - * Creates a new instance of the {@see ExposureOffsetFeature}. + * Creates a new instance of the {@link ExposureOffsetFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. */ @@ -57,7 +57,7 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { } /** - * Returns the minimum exposure offset value, in counts of {@see #getExposureOffsetStepSize}. + * Returns the minimum exposure offset value, in counts of {@link #getExposureOffsetStepSize}. * * @return double Minimum exposure offset value. */ @@ -69,7 +69,7 @@ public double getMinExposureOffset() { } /** - * Returns the maximum exposure offset value, in counts of {@see #getExposureOffsetStepSize}. + * Returns the maximum exposure offset value, in counts of {@link #getExposureOffsetStepSize}. * * @return double Maximum exposure offset value. */ diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java index 88614db73642..f2aa43715b10 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java @@ -11,19 +11,25 @@ import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; import io.flutter.plugins.camera.features.Point; -import io.flutter.plugins.camera.features.regionboundaries.CameraRegions; -import java.util.concurrent.Callable; +import io.flutter.plugins.camera.types.CameraRegions; -/** Exposure point controls where in the frame exposure metering will come from. */ +/** + * Exposure point controls where in the frame exposure metering will come from. + */ public class ExposurePointFeature extends CameraFeature { - // Used later to always get the correct camera regions instance. - private final Callable getCameraRegions; + private final CameraRegions cameraRegions; private Point currentSetting = new Point(0.0, 0.0); + /** + * Creates a new instance of the {@link ExposurePointFeature}. + * + * @param cameraProperties Collection of the characteristics for the current camera device. + * @param cameraRegions Utility class to assist in calculating exposure boundaries. + */ public ExposurePointFeature( - CameraProperties cameraProperties, Callable getCameraRegions) { + CameraProperties cameraProperties, CameraRegions cameraRegions) { super(cameraProperties); - this.getCameraRegions = getCameraRegions; + this.cameraRegions = cameraRegions; } @Override @@ -42,9 +48,9 @@ public void setValue(@NonNull Point value) { try { if (value.x == null || value.y == null) { - getCameraRegions.call().resetAutoExposureMeteringRectangle(); + cameraRegions.resetAutoExposureMeteringRectangle(); } else { - getCameraRegions.call().setAutoExposureMeteringRectangleFromPoint(value.x, value.y); + cameraRegions.setAutoExposureMeteringRectangleFromPoint(value.x, value.y); } } catch (Exception e) { e.printStackTrace(); @@ -66,7 +72,7 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { MeteringRectangle aeRect = null; try { - aeRect = getCameraRegions.call().getAEMeteringRectangle(); + aeRect = cameraRegions.getAEMeteringRectangle(); } catch (Exception e) { Log.w("Camera", "Unable to retrieve the Auto Exposure metering rectangle.", e); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java deleted file mode 100644 index f2c2b2e6a0d8..000000000000 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/CameraRegions.java +++ /dev/null @@ -1,77 +0,0 @@ -// 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.regionboundaries; - -import android.hardware.camera2.params.MeteringRectangle; -import android.util.Size; - -public final class CameraRegions { - private final Size maxBoundaries; - - private MeteringRectangle aeMeteringRectangle; - private MeteringRectangle afMeteringRectangle; - - public CameraRegions(Size maxBoundaries) { - assert (maxBoundaries == null || maxBoundaries.getWidth() > 0); - assert (maxBoundaries == null || maxBoundaries.getHeight() > 0); - this.maxBoundaries = maxBoundaries; - } - - public MeteringRectangle getAEMeteringRectangle() { - return aeMeteringRectangle; - } - - public MeteringRectangle getAFMeteringRectangle() { - return afMeteringRectangle; - } - - public Size getMaxBoundaries() { - return this.maxBoundaries; - } - - public void resetAutoExposureMeteringRectangle() { - this.aeMeteringRectangle = null; - } - - public void setAutoExposureMeteringRectangleFromPoint(double x, double y) { - this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); - } - - public void resetAutoFocusMeteringRectangle() { - this.afMeteringRectangle = null; - } - - public void setAutoFocusMeteringRectangleFromPoint(double x, double y) { - this.afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); - } - - public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) { - assert (x >= 0 && x <= 1); - assert (y >= 0 && y <= 1); - if (maxBoundaries == null) - throw new IllegalStateException( - "Functionality for managing metering rectangles is unavailable as this CameraRegions instance was initialized with null boundaries."); - - // Interpolate the target coordinate - int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); - int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); - // Determine the dimensions of the metering triangle (10th of the viewport) - int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); - int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); - // Adjust target coordinate to represent top-left corner of metering rectangle - targetX -= targetWidth / 2; - targetY -= targetHeight / 2; - // Adjust target coordinate as to not fall out of bounds - if (targetX < 0) targetX = 0; - if (targetY < 0) targetY = 0; - int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; - int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; - if (targetX > maxTargetX) targetX = maxTargetX; - if (targetY > maxTargetY) targetY = maxTargetY; - - // Build the metering rectangle - return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); - } -} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java deleted file mode 100644 index 19339843885d..000000000000 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeature.java +++ /dev/null @@ -1,94 +0,0 @@ -// 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.regionboundaries; - -import android.annotation.TargetApi; -import android.hardware.camera2.CaptureRequest; -import android.os.Build; -import android.util.Size; -import io.flutter.plugins.camera.CameraProperties; -import io.flutter.plugins.camera.features.CameraFeature; -import java.util.Arrays; - -/** - * Holds the current region boundaries. When this is created, you must provide a - * CaptureRequestBuilder for which we can read the distortion correction settings from. - */ -public class RegionBoundariesFeature extends CameraFeature { - private final CameraRegions cameraRegions; - private Size currentSetting; - - public RegionBoundariesFeature( - CameraProperties cameraProperties, CaptureRequest.Builder requestBuilder) { - super(cameraProperties); - - // No distortion correction support - if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P - || !supportsDistortionCorrection()) { - setValue(cameraProperties.getSensorInfoPixelArraySize()); - } else { - // Get the current distortion correction mode - Integer distortionCorrectionMode = - requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); - - // Return the correct boundaries depending on the mode - android.graphics.Rect rect; - if (distortionCorrectionMode == null - || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { - rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); - } else { - rect = cameraProperties.getSensorInfoActiveArraySize(); - } - - // Set new region size - setValue(rect == null ? null : new Size(rect.width(), rect.height())); - } - - // Create new camera regions using new size - cameraRegions = new CameraRegions(currentSetting); - } - - @Override - public String getDebugName() { - return "RegionBoundariesFeature"; - } - - @Override - public Size getValue() { - return currentSetting; - } - - @Override - public void setValue(Size value) { - this.currentSetting = value; - } - - // Available on all devices. - @Override - public boolean checkIsSupported() { - return true; - } - - @Override - public void updateBuilder(CaptureRequest.Builder requestBuilder) { - // Noop: when setting a region boundaries there is no need to update the request builder. - } - - @TargetApi(Build.VERSION_CODES.P) - private boolean supportsDistortionCorrection() { - int[] availableDistortionCorrectionModes = - cameraProperties.getDistortionCorrectionAvailableModes(); - if (availableDistortionCorrectionModes == null) availableDistortionCorrectionModes = new int[0]; - long nonOffModesSupported = - Arrays.stream(availableDistortionCorrectionModes) - .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) - .count(); - return nonOffModesSupported > 0; - } - - public CameraRegions getCameraRegions() { - return this.cameraRegions; - } -} \ No newline at end of file diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java new file mode 100644 index 000000000000..3e7995bf9e51 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java @@ -0,0 +1,204 @@ +// 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.types; + +import android.annotation.TargetApi; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.MeteringRectangle; +import android.os.Build; +import android.util.Size; +import androidx.annotation.NonNull; +import io.flutter.plugins.camera.CameraProperties; +import java.util.Arrays; + +/** + * Utility class that contains information regarding the camera's regions. + * + * The regions information is used to calculate focus and exposure settings. + */ +public final class CameraRegions { + + /** + * Factory class that assists in creating a {@link CameraRegions} instance. + */ + public static class Factory { + /** + * Creates a new instance of the {@link CameraRegions} class. + * + * The {@link CameraProperties} and {@link CaptureRequest.Builder} classed are used to determine + * if the device's camera supports distortion correction mode and calculate the correct boundaries + * based on the outcome. + * + * @param cameraProperties Collection of the characteristics for the current camera device. + * @param requestBuilder CaptureRequest builder containing current target and surface settings. + * @return new instance of the {@link CameraRegions} class. + */ + public static CameraRegions create( + @NonNull CameraProperties cameraProperties, + @NonNull CaptureRequest.Builder requestBuilder) { + Size boundaries; + + // No distortion correction support + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P || + !supportsDistortionCorrection(cameraProperties)) { + boundaries = cameraProperties.getSensorInfoPixelArraySize(); + } else { + // Get the current distortion correction mode + Integer distortionCorrectionMode = + requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); + + // Return the correct boundaries depending on the mode + android.graphics.Rect rect; + if (distortionCorrectionMode == null + || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { + rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); + } else { + rect = cameraProperties.getSensorInfoActiveArraySize(); + } + + // Set new region size + boundaries = rect == null ? null : new Size(rect.width(), rect.height()); + } + + // Create new camera regions using new size + return new CameraRegions(boundaries); + } + + @TargetApi(Build.VERSION_CODES.P) + private static boolean supportsDistortionCorrection(CameraProperties cameraProperties) { + int[] availableDistortionCorrectionModes = + cameraProperties.getDistortionCorrectionAvailableModes(); + if (availableDistortionCorrectionModes == null) { + availableDistortionCorrectionModes = new int[0]; + } + long nonOffModesSupported = + Arrays.stream(availableDistortionCorrectionModes) + .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) + .count(); + return nonOffModesSupported > 0; + } + } + + private final Size boundaries; + + private MeteringRectangle aeMeteringRectangle; + private MeteringRectangle afMeteringRectangle; + + /** + * Creates a new instance of the {@link CameraRegions} class. + * + * @param boundaries The area of the image sensor. + */ + CameraRegions(Size boundaries) { + assert (boundaries == null || boundaries.getWidth() > 0); + assert (boundaries == null || boundaries.getHeight() > 0); + + this.boundaries = boundaries; + } + + /** + * Gets the {@link MeteringRectangle} on which the auto exposure will be applied. + * + * @return The {@link MeteringRectangle} on which the auto exposure will be applied. + */ + public MeteringRectangle getAEMeteringRectangle() { + return aeMeteringRectangle; + } + + /** + * Gets the {@link MeteringRectangle} on which the auto focus will be applied. + * + * @return The {@link MeteringRectangle} on which the auto focus will be applied. + */ + public MeteringRectangle getAFMeteringRectangle() { + return afMeteringRectangle; + } + + /** + * Gets the area of the image sensor. + * + * If distortion correction is supported the size corresponds to the active pixels after any + * geometric distortion correction has been applied. If distortion correction is not supported + * the dimensions include the full pixel array, possibly including black calibration pixels. + * + * @return The area of the image sensor. + */ + public Size getBoundaries() { + return this.boundaries; + } + + /** + * Resets the {@link MeteringRectangle} on which the auto exposure will be applied. + */ + public void resetAutoExposureMeteringRectangle() { + this.aeMeteringRectangle = null; + } + + /** + * Sets the coordinates which will form the centre of the exposure rectangle. + * + * @param x x – coordinate >= 0 + * @param y y – coordinate >= 0 + */ + public void setAutoExposureMeteringRectangleFromPoint(double x, double y) { + this.aeMeteringRectangle = convertPointToMeteringRectangle(x, y); + } + + /** + * Resets the {@link MeteringRectangle} on which the auto focus will be applied. + */ + public void resetAutoFocusMeteringRectangle() { + this.afMeteringRectangle = null; + } + + /** + * Sets the coordinates which will form the centre of the focus rectangle. + * + * @param x x – coordinate >= 0 + * @param y y – coordinate >= 0 + */ + public void setAutoFocusMeteringRectangleFromPoint(double x, double y) { + this.afMeteringRectangle = convertPointToMeteringRectangle(x, y); + } + + /** + * Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the centre + * point. + * + * Since the Camera API (due to cross-platform constraints) only accepts a point when configuring + * a specific focus or exposure area and Android requires a rectangle to configure these settings + * there is a need to convert the point into a rectangle. This method will create the required + * rectangle with an arbitrarily size that is a 10th of the current viewport and the coordinates + * as the centre point. + * + * @param x x - coordinate >= 0 + * @param y y - coordinate >= 0 + * @return The dimensions of the metering rectangle based on the supplied coordinates. + */ + MeteringRectangle convertPointToMeteringRectangle(double x, double y) { + assert (x >= 0 && x <= 1); + assert (y >= 0 && y <= 1); + + // Interpolate the target coordinate + int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1))); + int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1))); + // Since the Camera API only allows Determine the dimensions of the metering rectangle (10th of the viewport) + int targetWidth = (int) Math.round(((double) boundaries.getWidth()) / 10d); + int targetHeight = (int) Math.round(((double) boundaries.getHeight()) / 10d); + // Adjust target coordinate to represent top-left corner of metering rectangle + targetX -= targetWidth / 2; + targetY -= targetHeight / 2; + // Adjust target coordinate as to not fall out of bounds + if (targetX < 0) targetX = 0; + if (targetY < 0) targetY = 0; + int maxTargetX = boundaries.getWidth() - 1 - targetWidth; + int maxTargetY = boundaries.getHeight() - 1 - targetHeight; + if (targetX > maxTargetX) targetX = maxTargetX; + if (targetY > maxTargetY) targetY = maxTargetY; + + // Build the metering rectangle + return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java index 2f9b8dd1b1b4..e3df5e56570e 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java @@ -19,15 +19,16 @@ import android.hardware.camera2.params.MeteringRectangle; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.Point; -import io.flutter.plugins.camera.features.regionboundaries.CameraRegions; +import io.flutter.plugins.camera.types.CameraRegions; import org.junit.Test; public class ExposurePointFeatureTest { @Test public void getDebugName_should_return_the_name_of_the_feature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> null); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); assertEquals("ExposurePointFeature", exposurePointFeature.getDebugName()); } @@ -35,8 +36,9 @@ public void getDebugName_should_return_the_name_of_the_feature() { @Test public void getValue_should_return_default_point_if_not_set() { CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> null); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); Point expectedPoint = new Point(0.0, 0.0); Point actualPoint = exposurePointFeature.getValue(); @@ -49,7 +51,7 @@ public void getValue_should_echo_the_set_value() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); Point expectedPoint = new Point(0.0, 0.0); exposurePointFeature.setValue(expectedPoint); @@ -63,7 +65,7 @@ public void setValue_should_reset_point_when_x_coord_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); exposurePointFeature.setValue(new Point(null, 0.0)); @@ -75,7 +77,7 @@ public void setValue_should_reset_point_when_y_coord_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); exposurePointFeature.setValue(new Point(0.0, null)); @@ -87,7 +89,7 @@ public void setValue_should_reset_point_when_valid_coords_are_supplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); Point point = new Point(0.0, 0.0); exposurePointFeature.setValue(point); @@ -99,7 +101,7 @@ public void setValue_should_reset_point_when_valid_coords_are_supplied() { public void checkIsSupported_should_return_false_when_max_regions_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> null); + new ExposurePointFeature(mockCameraProperties, null); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(null); @@ -110,7 +112,7 @@ public void checkIsSupported_should_return_false_when_max_regions_is_null() { public void checkIsSupported_should_return_false_when_max_regions_is_zero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> null); + new ExposurePointFeature(mockCameraProperties, null); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); @@ -121,7 +123,7 @@ public void checkIsSupported_should_return_false_when_max_regions_is_zero() { public void checkIsSupported_should_return_true_when_max_regions_is_bigger_then_zero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> null); + new ExposurePointFeature(mockCameraProperties, null); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); @@ -133,7 +135,7 @@ public void updateBuilder_should_return_when_checkIsSupported_is_false() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CameraRegions mockCameraRegions = mock(CameraRegions.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); @@ -148,7 +150,7 @@ public void updateBuilder_should_set_ae_regions_to_null_when_ae_metering_rectang CameraRegions mockCameraRegions = mock(CameraRegions.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); when(mockCameraRegions.getAEMeteringRectangle()).thenReturn(null); @@ -164,7 +166,7 @@ public void updateBuilder_should_set_ae_regions_with_metering_rectangle() { CameraRegions mockCameraRegions = mock(CameraRegions.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); MeteringRectangle meteringRectangle = new MeteringRectangle(0, 0, 0, 0, 0); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); @@ -182,7 +184,7 @@ public void updateBuilder_should_silently_fail_when_exception_occurs() { CameraRegions mockCameraRegions = mock(CameraRegions.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, () -> mockCameraRegions); + new ExposurePointFeature(mockCameraProperties, mockCameraRegions); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); when(mockCameraRegions.getAEMeteringRectangle()).thenThrow(new IllegalArgumentException()); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java similarity index 56% rename from packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java rename to packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java index 46c285ce4dc9..5a2af7921733 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/regionboundaries/RegionBoundariesFeatureTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java @@ -1,12 +1,7 @@ -// 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.regionboundaries; +package io.flutter.plugins.camera.types; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -21,7 +16,7 @@ import org.junit.Before; import org.junit.Test; -public class RegionBoundariesFeatureTest { +public class CameraRegionsFactoryTest { private Size mockSize; @Before @@ -34,7 +29,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_pixel_array_size_when_running_pre_android_p() { + create_should_initialize_with_sensor_info_pixel_array_size_when_running_pre_android_p() { updateSdkVersion(VERSION_CODES.O_MR1); try { @@ -43,10 +38,10 @@ public void before() { when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertEquals(mockSize, regionBoundariesFeature.getValue()); + assertEquals(mockSize, cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { @@ -56,7 +51,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_null() { + create_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_null() { updateSdkVersion(VERSION_CODES.P); try { @@ -66,10 +61,10 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()).thenReturn(null); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertEquals(mockSize, regionBoundariesFeature.getValue()); + assertEquals(mockSize, cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { @@ -79,7 +74,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_off() { + create_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_off() { updateSdkVersion(VERSION_CODES.P); try { @@ -90,10 +85,10 @@ public void before() { .thenReturn(new int[] {CaptureRequest.DISTORTION_CORRECTION_MODE_OFF}); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertEquals(mockSize, regionBoundariesFeature.getValue()); + assertEquals(mockSize, cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { @@ -103,7 +98,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() { + create_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() { updateSdkVersion(VERSION_CODES.P); try { @@ -113,17 +108,17 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { - CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, - CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)).thenReturn(null); when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertNull(regionBoundariesFeature.getValue()); + assertNull(cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { @@ -133,7 +128,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_off() { + create_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_off() { updateSdkVersion(VERSION_CODES.P); try { @@ -143,18 +138,18 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { - CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, - CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_OFF); when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertNull(regionBoundariesFeature.getValue()); + assertNull(cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { @@ -164,7 +159,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_active_array_size_when_distortion_correction_mode_is_set() { + ctor_should_initialize_with_sensor_info_active_array_size_when_distortion_correction_mode_is_set() { updateSdkVersion(VERSION_CODES.P); try { @@ -174,18 +169,18 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { - CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, - CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_FAST); when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertNull(regionBoundariesFeature.getValue()); + assertNull(cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); } finally { @@ -194,45 +189,13 @@ public void before() { } @Test - public void getDebugName_should_return_the_name_of_the_feature() { - CameraProperties mockCameraProperties = mock(CameraProperties.class); - CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); - - assertEquals("RegionBoundariesFeature", regionBoundariesFeature.getDebugName()); - } - - @Test - public void getValue_should_return_null_if_not_set() { - CameraProperties mockCameraProperties = mock(CameraProperties.class); - CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); - - assertNull(regionBoundariesFeature.getValue()); - } - - @Test - public void getValue_should_echo_setValue() { - CameraProperties mockCameraProperties = mock(CameraProperties.class); - CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); - - regionBoundariesFeature.setValue(mockSize); - - assertEquals(mockSize, regionBoundariesFeature.getValue()); - } - - @Test - public void checkIsSupport_returns_true() { + public void getBoundaries_should_return_null_if_not_set() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); - RegionBoundariesFeature regionBoundariesFeature = - new RegionBoundariesFeature(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = + CameraRegions.Factory.create(mockCameraProperties, mockBuilder); - assertTrue(regionBoundariesFeature.checkIsSupported()); + assertNull(cameraRegions.getBoundaries()); } private static void updateSdkVersion(int version) { diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java similarity index 53% rename from packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java rename to packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java index 6a04b14fe21e..1610b8420a59 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java @@ -2,7 +2,7 @@ // 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; +package io.flutter.plugins.camera.types; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -17,91 +17,76 @@ @RunWith(RobolectricTestRunner.class) public class CameraRegionsTest { - - CameraRegions cameraRegions; + io.flutter.plugins.camera.types.CameraRegions cameraRegions; @Before public void setUp() { - this.cameraRegions = new CameraRegions(new Size(100, 50)); + this.cameraRegions = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 100)); } @Test(expected = AssertionError.class) public void getMeteringRectangleForPoint_should_throw_for_x_upper_bound() { - cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 1.5, 0); + cameraRegions.convertPointToMeteringRectangle(1.5, 0); } @Test(expected = AssertionError.class) public void getMeteringRectangleForPoint_should_throw_for_x_lower_bound() { - cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), -0.5, 0); + cameraRegions.convertPointToMeteringRectangle(-0.5, 0); } @Test(expected = AssertionError.class) public void getMeteringRectangleForPoint_should_throw_for_y_upper_bound() { - cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, 1.5); + cameraRegions.convertPointToMeteringRectangle(0, 1.5); } @Test(expected = AssertionError.class) public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() { - cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, -0.5); - } - - @Test(expected = IllegalStateException.class) - public void getMeteringRectangleForPoint_should_throw_for_null_boundaries() { - cameraRegions.getMeteringRectangleForPoint(null, 0, -0); + cameraRegions.convertPointToMeteringRectangle(0, -0.5); } @Test public void getMeteringRectangleForPoint_should_return_valid_MeteringRectangle() { MeteringRectangle r; // Center - r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.5, 0.5); - assertEquals(new MeteringRectangle(45, 23, 10, 5, 1), r); + r = cameraRegions.convertPointToMeteringRectangle(0.5, 0.5); + assertEquals(new MeteringRectangle(45, 45, 10, 10, 1), r); // Top left - r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.0, 0.0); - assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), r); + r = cameraRegions.convertPointToMeteringRectangle(0.0, 0.0); + assertEquals(new MeteringRectangle(0, 0, 10, 10, 1), r); // Bottom right - r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 1.0, 1.0); - assertEquals(new MeteringRectangle(89, 44, 10, 5, 1), r); + r = cameraRegions.convertPointToMeteringRectangle(1.0, 1.0); + assertEquals(new MeteringRectangle(89, 89, 10, 10, 1), r); // Top left - r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.0, 1.0); - assertEquals(new MeteringRectangle(0, 44, 10, 5, 1), r); + r = cameraRegions.convertPointToMeteringRectangle(0.0, 1.0); + assertEquals(new MeteringRectangle(0, 89, 10, 10, 1), r); // Top right - r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 1.0, 0.0); - assertEquals(new MeteringRectangle(89, 0, 10, 5, 1), r); + r = cameraRegions.convertPointToMeteringRectangle(1.0, 0.0); + assertEquals(new MeteringRectangle(89, 0, 10, 10, 1), r); } @Test(expected = AssertionError.class) public void constructor_should_throw_for_0_width_boundary() { - new CameraRegions(new Size(0, 50)); + new io.flutter.plugins.camera.CameraRegions(new Size(0, 50)); } @Test(expected = AssertionError.class) public void constructor_should_throw_for_0_height_boundary() { - new CameraRegions(new Size(100, 0)); - } - - @Test - public void constructor_should_initialize() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); - assertEquals(new Size(100, 50), cr.getMaxBoundaries()); - assertNull(cr.getAEMeteringRectangle()); - assertNull(cr.getAFMeteringRectangle()); + new io.flutter.plugins.camera.CameraRegions(new Size(100, 0)); } @Test public void setAutoExposureMeteringRectangleFromPoint_should_set_aeMeteringRectangle_for_point() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); - cr.setAutoExposureMeteringRectangleFromPoint(0, 0); - assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAEMeteringRectangle()); + cameraRegions.setAutoExposureMeteringRectangleFromPoint(0, 0); + assertEquals(new MeteringRectangle(0, 0, 10, 10, 1), cameraRegions.getAEMeteringRectangle()); } @Test public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); + io.flutter.plugins.camera.types.CameraRegions cr = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); cr.setAutoExposureMeteringRectangleFromPoint(0, 0); assertNotNull(cr.getAEMeteringRectangle()); cr.resetAutoExposureMeteringRectangle(); @@ -110,14 +95,14 @@ public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle( @Test public void setAutoFocusMeteringRectangleFromPoint_should_set_afMeteringRectangle_for_point() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); + io.flutter.plugins.camera.types.CameraRegions cr = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); cr.setAutoFocusMeteringRectangleFromPoint(0, 0); assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAFMeteringRectangle()); } @Test public void resetAutoFocusMeteringRectangle_should_reset_afMeteringRectangle() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); + io.flutter.plugins.camera.types.CameraRegions cr = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); cr.setAutoFocusMeteringRectangleFromPoint(0, 0); assertNotNull(cr.getAFMeteringRectangle()); cr.resetAutoFocusMeteringRectangle(); From 6a50e991df5a4063452bc31c97e3b318a2eee149 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 18 May 2021 13:47:52 +0200 Subject: [PATCH 07/10] Fix formatting --- .../camera/features/CameraFeature.java | 1 + .../camera/features/autofocus/FocusMode.java | 4 +- .../exposurelock/ExposureLockFeature.java | 5 +- .../features/exposurelock/ExposureMode.java | 6 ++- .../exposureoffset/ExposureOffsetFeature.java | 5 +- .../exposurepoint/ExposurePointFeature.java | 20 +++----- .../plugins/camera/types/CameraRegions.java | 43 +++++++--------- .../ExposurePointFeatureTest.java | 3 +- .../types/CameraRegionsFactoryTest.java | 49 +++++++++---------- .../camera/types/CameraRegionsTest.java | 9 ++-- 10 files changed, 68 insertions(+), 77 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java index ad800f5e1163..92cfd548cd06 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java @@ -17,6 +17,7 @@ * @param */ public abstract class CameraFeature { + protected final CameraProperties cameraProperties; protected CameraFeature(@NonNull CameraProperties cameraProperties) { 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 index 98434bc69f47..56331b4fab8c 100644 --- 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 @@ -17,7 +17,9 @@ public enum FocusMode { public static FocusMode getValueForString(String modeStr) { for (FocusMode value : values()) { - if (value.strValue.equals(modeStr)) return value; + if (value.strValue.equals(modeStr)) { + return value; + } } return null; } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java index e9ec884e132c..df08cd9a3c77 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java @@ -8,10 +8,9 @@ import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; -/** - * Controls whether or not the exposure mode is currently locked or automatically metering. - */ +/** Controls whether or not the exposure mode is currently locked or automatically metering. */ public class ExposureLockFeature extends CameraFeature { + private ExposureMode currentSetting = ExposureMode.auto; /** diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java index 427af684810a..2971fb23727a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java @@ -18,7 +18,7 @@ public enum ExposureMode { /** * Tries to convert the supplied string into an {@see ExposureMode} enum value. * - * When the supplied string doesn't match a valid {@see ExposureMode} enum value, null is + *

When the supplied string doesn't match a valid {@see ExposureMode} enum value, null is * returned. * * @param modeStr String value to convert into an {@see ExposureMode} enum value. @@ -26,7 +26,9 @@ public enum ExposureMode { */ public static ExposureMode getValueForString(String modeStr) { for (ExposureMode value : values()) { - if (value.strValue.equals(modeStr)) return value; + if (value.strValue.equals(modeStr)) { + return value; + } } return null; } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java index e7261ad2ad46..2c6db8fa365c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java @@ -10,10 +10,9 @@ import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; -/** - * Controls the exposure offset making the resulting image brighter or darker. - */ +/** Controls the exposure offset making the resulting image brighter or darker. */ public class ExposureOffsetFeature extends CameraFeature { + private double currentSetting = 0; /** diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java index f2aa43715b10..f729d33c8528 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java @@ -13,10 +13,9 @@ import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.types.CameraRegions; -/** - * Exposure point controls where in the frame exposure metering will come from. - */ +/** Exposure point controls where in the frame exposure metering will come from. */ public class ExposurePointFeature extends CameraFeature { + private final CameraRegions cameraRegions; private Point currentSetting = new Point(0.0, 0.0); @@ -26,8 +25,7 @@ public class ExposurePointFeature extends CameraFeature { * @param cameraProperties Collection of the characteristics for the current camera device. * @param cameraRegions Utility class to assist in calculating exposure boundaries. */ - public ExposurePointFeature( - CameraProperties cameraProperties, CameraRegions cameraRegions) { + public ExposurePointFeature(CameraProperties cameraProperties, CameraRegions cameraRegions) { super(cameraProperties); this.cameraRegions = cameraRegions; } @@ -46,14 +44,10 @@ public Point getValue() { public void setValue(@NonNull Point value) { this.currentSetting = value; - try { - if (value.x == null || value.y == null) { - cameraRegions.resetAutoExposureMeteringRectangle(); - } else { - cameraRegions.setAutoExposureMeteringRectangleFromPoint(value.x, value.y); - } - } catch (Exception e) { - e.printStackTrace(); + if (value.x == null || value.y == null) { + cameraRegions.resetAutoExposureMeteringRectangle(); + } else { + cameraRegions.setAutoExposureMeteringRectangleFromPoint(value.x, value.y); } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java index 3e7995bf9e51..3dcf2b48519e 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java @@ -16,20 +16,18 @@ /** * Utility class that contains information regarding the camera's regions. * - * The regions information is used to calculate focus and exposure settings. + *

The regions information is used to calculate focus and exposure settings. */ public final class CameraRegions { - /** - * Factory class that assists in creating a {@link CameraRegions} instance. - */ + /** Factory class that assists in creating a {@link CameraRegions} instance. */ public static class Factory { /** * Creates a new instance of the {@link CameraRegions} class. * - * The {@link CameraProperties} and {@link CaptureRequest.Builder} classed are used to determine - * if the device's camera supports distortion correction mode and calculate the correct boundaries - * based on the outcome. + *

The {@link CameraProperties} and {@link CaptureRequest.Builder} classed are used to + * determine if the device's camera supports distortion correction mode and calculate the + * correct boundaries based on the outcome. * * @param cameraProperties Collection of the characteristics for the current camera device. * @param requestBuilder CaptureRequest builder containing current target and surface settings. @@ -41,8 +39,8 @@ public static CameraRegions create( Size boundaries; // No distortion correction support - if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P || - !supportsDistortionCorrection(cameraProperties)) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P + || !supportsDistortionCorrection(cameraProperties)) { boundaries = cameraProperties.getSensorInfoPixelArraySize(); } else { // Get the current distortion correction mode @@ -119,9 +117,9 @@ public MeteringRectangle getAFMeteringRectangle() { /** * Gets the area of the image sensor. * - * If distortion correction is supported the size corresponds to the active pixels after any - * geometric distortion correction has been applied. If distortion correction is not supported - * the dimensions include the full pixel array, possibly including black calibration pixels. + *

If distortion correction is supported the size corresponds to the active pixels after any + * geometric distortion correction has been applied. If distortion correction is not supported the + * dimensions include the full pixel array, possibly including black calibration pixels. * * @return The area of the image sensor. */ @@ -129,9 +127,7 @@ public Size getBoundaries() { return this.boundaries; } - /** - * Resets the {@link MeteringRectangle} on which the auto exposure will be applied. - */ + /** Resets the {@link MeteringRectangle} on which the auto exposure will be applied. */ public void resetAutoExposureMeteringRectangle() { this.aeMeteringRectangle = null; } @@ -146,9 +142,7 @@ public void setAutoExposureMeteringRectangleFromPoint(double x, double y) { this.aeMeteringRectangle = convertPointToMeteringRectangle(x, y); } - /** - * Resets the {@link MeteringRectangle} on which the auto focus will be applied. - */ + /** Resets the {@link MeteringRectangle} on which the auto focus will be applied. */ public void resetAutoFocusMeteringRectangle() { this.afMeteringRectangle = null; } @@ -167,11 +161,11 @@ public void setAutoFocusMeteringRectangleFromPoint(double x, double y) { * Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the centre * point. * - * Since the Camera API (due to cross-platform constraints) only accepts a point when configuring - * a specific focus or exposure area and Android requires a rectangle to configure these settings - * there is a need to convert the point into a rectangle. This method will create the required - * rectangle with an arbitrarily size that is a 10th of the current viewport and the coordinates - * as the centre point. + *

Since the Camera API (due to cross-platform constraints) only accepts a point when + * configuring a specific focus or exposure area and Android requires a rectangle to configure + * these settings there is a need to convert the point into a rectangle. This method will create + * the required rectangle with an arbitrarily size that is a 10th of the current viewport and the + * coordinates as the centre point. * * @param x x - coordinate >= 0 * @param y y - coordinate >= 0 @@ -184,7 +178,8 @@ MeteringRectangle convertPointToMeteringRectangle(double x, double y) { // Interpolate the target coordinate int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1))); int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1))); - // Since the Camera API only allows Determine the dimensions of the metering rectangle (10th of the viewport) + // Since the Camera API only allows Determine the dimensions of the metering rectangle (10th of + // the viewport) int targetWidth = (int) Math.round(((double) boundaries.getWidth()) / 10d); int targetHeight = (int) Math.round(((double) boundaries.getHeight()) / 10d); // Adjust target coordinate to represent top-left corner of metering rectangle diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java index e3df5e56570e..0aedc59ef635 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java @@ -191,7 +191,6 @@ public void updateBuilder_should_silently_fail_when_exception_occurs() { exposurePointFeature.updateBuilder(mockBuilder); - verify(mockBuilder, times(1)) - .set(CaptureRequest.CONTROL_AE_REGIONS, null); + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_REGIONS, null); } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java index 5a2af7921733..5fa0c2c4a2a4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java @@ -1,3 +1,7 @@ +// 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.types; import static org.junit.Assert.assertEquals; @@ -29,7 +33,7 @@ public void before() { @Test public void - create_should_initialize_with_sensor_info_pixel_array_size_when_running_pre_android_p() { + create_should_initialize_with_sensor_info_pixel_array_size_when_running_pre_android_p() { updateSdkVersion(VERSION_CODES.O_MR1); try { @@ -38,8 +42,7 @@ public void before() { when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertEquals(mockSize, cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); @@ -51,7 +54,7 @@ public void before() { @Test public void - create_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_null() { + create_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_null() { updateSdkVersion(VERSION_CODES.P); try { @@ -61,8 +64,7 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()).thenReturn(null); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertEquals(mockSize, cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); @@ -74,7 +76,7 @@ public void before() { @Test public void - create_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_off() { + create_should_initialize_with_sensor_info_pixel_array_size_when_distortion_correction_is_off() { updateSdkVersion(VERSION_CODES.P); try { @@ -85,8 +87,7 @@ public void before() { .thenReturn(new int[] {CaptureRequest.DISTORTION_CORRECTION_MODE_OFF}); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockSize); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertEquals(mockSize, cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); @@ -98,7 +99,7 @@ public void before() { @Test public void - create_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() { + create_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() { updateSdkVersion(VERSION_CODES.P); try { @@ -108,15 +109,14 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { - CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, - CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)).thenReturn(null); when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertNull(cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); @@ -128,7 +128,7 @@ public void before() { @Test public void - create_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_off() { + create_should_initialize_with_sensor_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_off() { updateSdkVersion(VERSION_CODES.P); try { @@ -138,16 +138,15 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { - CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, - CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_OFF); when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertNull(cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); @@ -159,7 +158,7 @@ public void before() { @Test public void - ctor_should_initialize_with_sensor_info_active_array_size_when_distortion_correction_mode_is_set() { + ctor_should_initialize_with_sensor_info_active_array_size_when_distortion_correction_mode_is_set() { updateSdkVersion(VERSION_CODES.P); try { @@ -169,16 +168,15 @@ public void before() { when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { - CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, - CaptureRequest.DISTORTION_CORRECTION_MODE_FAST + CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, + CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_FAST); when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertNull(cameraRegions.getBoundaries()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); @@ -192,8 +190,7 @@ public void before() { public void getBoundaries_should_return_null_if_not_set() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); - CameraRegions cameraRegions = - CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); assertNull(cameraRegions.getBoundaries()); } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java index 1610b8420a59..b760e1e9ca29 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java @@ -86,7 +86,8 @@ public void setAutoExposureMeteringRectangleFromPoint_should_set_aeMeteringRecta @Test public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle() { - io.flutter.plugins.camera.types.CameraRegions cr = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); + io.flutter.plugins.camera.types.CameraRegions cr = + new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); cr.setAutoExposureMeteringRectangleFromPoint(0, 0); assertNotNull(cr.getAEMeteringRectangle()); cr.resetAutoExposureMeteringRectangle(); @@ -95,14 +96,16 @@ public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle( @Test public void setAutoFocusMeteringRectangleFromPoint_should_set_afMeteringRectangle_for_point() { - io.flutter.plugins.camera.types.CameraRegions cr = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); + io.flutter.plugins.camera.types.CameraRegions cr = + new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); cr.setAutoFocusMeteringRectangleFromPoint(0, 0); assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAFMeteringRectangle()); } @Test public void resetAutoFocusMeteringRectangle_should_reset_afMeteringRectangle() { - io.flutter.plugins.camera.types.CameraRegions cr = new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); + io.flutter.plugins.camera.types.CameraRegions cr = + new io.flutter.plugins.camera.types.CameraRegions(new Size(100, 50)); cr.setAutoFocusMeteringRectangleFromPoint(0, 0); assertNotNull(cr.getAFMeteringRectangle()); cr.resetAutoFocusMeteringRectangle(); From 989ecfaca72bd1f079c523f195cf82739e11c3fb Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 25 May 2021 20:09:17 +0200 Subject: [PATCH 08/10] Apply feedback from PR --- .../io/flutter/plugins/camera/types/CameraRegions.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java index 3dcf2b48519e..c955ba55bad3 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java @@ -39,10 +39,8 @@ public static CameraRegions create( Size boundaries; // No distortion correction support - if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P - || !supportsDistortionCorrection(cameraProperties)) { - boundaries = cameraProperties.getSensorInfoPixelArraySize(); - } else { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + && supportsDistortionCorrection(cameraProperties)) { // Get the current distortion correction mode Integer distortionCorrectionMode = requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); @@ -58,6 +56,8 @@ public static CameraRegions create( // Set new region size boundaries = rect == null ? null : new Size(rect.width(), rect.height()); + } else { + boundaries = cameraProperties.getSensorInfoPixelArraySize(); } // Create new camera regions using new size From ebae3de59a2cf84e50f9a7bb614763f128bb9181 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 25 May 2021 20:58:01 +0200 Subject: [PATCH 09/10] Correct documentation of min and max exposure offset --- .../features/exposureoffset/ExposureOffsetFeature.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java index 2c6db8fa365c..d5a9fcd4a38a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java @@ -56,9 +56,9 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { } /** - * Returns the minimum exposure offset value, in counts of {@link #getExposureOffsetStepSize}. + * Returns the minimum exposure offset. * - * @return double Minimum exposure offset value. + * @return double Minimum exposure offset. */ public double getMinExposureOffset() { Range range = cameraProperties.getControlAutoExposureCompensationRange(); @@ -68,9 +68,9 @@ public double getMinExposureOffset() { } /** - * Returns the maximum exposure offset value, in counts of {@link #getExposureOffsetStepSize}. + * Returns the maximum exposure offset. * - * @return double Maximum exposure offset value. + * @return double Maximum exposure offset. */ public double getMaxExposureOffset() { Range range = cameraProperties.getControlAutoExposureCompensationRange(); From 81dafb35167f7e22f6be32c1b9ecc8497b186d8f Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 26 May 2021 17:20:07 +0200 Subject: [PATCH 10/10] Fix formatting --- .../java/io/flutter/plugins/camera/types/CameraRegions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java index c955ba55bad3..b86241e78d29 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java @@ -56,7 +56,7 @@ && supportsDistortionCorrection(cameraProperties)) { // Set new region size boundaries = rect == null ? null : new Size(rect.width(), rect.height()); - } else { + } else { boundaries = cameraProperties.getSensorInfoPixelArraySize(); }