diff --git a/packages/camera/camera/android/build.gradle b/packages/camera/camera/android/build.gradle index 7d7eb81e8aee..0907c1eeecc9 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/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/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/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 new file mode 100644 index 000000000000..df08cd9a3c77 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java @@ -0,0 +1,54 @@ +// 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; + +/** 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); + } + + @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; + } + + 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 new file mode 100644 index 000000000000..2971fb23727a --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camera.features.exposurelock; + +// Mirrors exposure_mode.dart +public enum ExposureMode { + auto("auto"), + locked("locked"); + + private final String strValue; + + ExposureMode(String strValue) { + 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; + } + } + 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..d5a9fcd4a38a --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.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.exposureoffset; + +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; + +/** 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 {@link ExposureOffsetFeature}. + * + * @param cameraProperties Collection of the characteristics for the current camera device. + */ + public ExposureOffsetFeature(CameraProperties cameraProperties) { + super(cameraProperties); + } + + @Override + public String getDebugName() { + return "ExposureOffsetFeature"; + } + + @Override + public Double getValue() { + return currentSetting; + } + + @Override + public void setValue(@NonNull Double value) { + double stepSize = getExposureOffsetStepSize(); + this.currentSetting = 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); + } + + /** + * Returns the minimum exposure offset. + * + * @return double Minimum exposure offset. + */ + public double getMinExposureOffset() { + Range range = cameraProperties.getControlAutoExposureCompensationRange(); + double minStepped = range == null ? 0 : range.getLower(); + double stepSize = getExposureOffsetStepSize(); + return minStepped * stepSize; + } + + /** + * Returns the maximum exposure offset. + * + * @return double Maximum exposure offset. + */ + public double getMaxExposureOffset() { + Range range = cameraProperties.getControlAutoExposureCompensationRange(); + double maxStepped = range == null ? 0 : range.getUpper(); + double stepSize = getExposureOffsetStepSize(); + return maxStepped * stepSize; + } + + /** + * 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. 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 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/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..f729d33c8528 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java @@ -0,0 +1,78 @@ +// 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.types.CameraRegions; + +/** 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); + + /** + * 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, CameraRegions cameraRegions) { + super(cameraProperties); + this.cameraRegions = cameraRegions; + } + + @Override + public String getDebugName() { + return "ExposurePointFeature"; + } + + @Override + public Point getValue() { + return currentSetting; + } + + @Override + public void setValue(@NonNull Point value) { + this.currentSetting = value; + + if (value.x == null || value.y == null) { + cameraRegions.resetAutoExposureMeteringRectangle(); + } else { + cameraRegions.setAutoExposureMeteringRectangleFromPoint(value.x, value.y); + } + } + + // 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 = null; + try { + aeRect = cameraRegions.getAEMeteringRectangle(); + } 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/types/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java new file mode 100644 index 000000000000..b86241e78d29 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java @@ -0,0 +1,199 @@ +// 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)) { + // 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()); + } else { + boundaries = cameraProperties.getSensorInfoPixelArraySize(); + } + + // 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/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..40d17fdc496e --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java @@ -0,0 +1,83 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camera.features.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 { + @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_zero_if_not_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + + final double actualValue = exposureOffsetFeature.getValue(); + + assertEquals(0.0, actualValue, 0); + } + + @Test + public void getValue_should_echo_the_set_value() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); + double expectedValue = 4.0; + + when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); + + exposureOffsetFeature.setValue(2.0); + double actualValue = exposureOffsetFeature.getValue(); + + assertEquals(expectedValue, actualValue, 0); + } + + @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); + + when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); + + exposureOffsetFeature.setValue(2.0); + exposureOffsetFeature.updateBuilder(mockBuilder); + + 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 new file mode 100644 index 000000000000..0aedc59ef635 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java @@ -0,0 +1,196 @@ +// 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.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, mockCameraRegions); + + assertEquals("ExposurePointFeature", exposurePointFeature.getDebugName()); + } + + @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, mockCameraRegions); + 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); + + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + when(mockCameraRegions.getAEMeteringRectangle()).thenThrow(new IllegalArgumentException()); + + exposurePointFeature.updateBuilder(mockBuilder); + + 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 new file mode 100644 index 000000000000..5fa0c2c4a2a4 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java @@ -0,0 +1,201 @@ +// 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; +import static org.junit.Assert.assertNull; +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 CameraRegionsFactoryTest { + private Size mockSize; + + @Before + public void before() { + mockSize = mock(Size.class); + + when(mockSize.getHeight()).thenReturn(640); + when(mockSize.getWidth()).thenReturn(480); + } + + @Test + public void + create_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); + + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + + assertEquals(mockSize, cameraRegions.getBoundaries()); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + create_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); + + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + + assertEquals(mockSize, cameraRegions.getBoundaries()); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + create_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); + + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + + assertEquals(mockSize, cameraRegions.getBoundaries()); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + create_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); + + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + + assertNull(cameraRegions.getBoundaries()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + create_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); + + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + + assertNull(cameraRegions.getBoundaries()); + 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); + + CameraRegions cameraRegions = CameraRegions.Factory.create(mockCameraProperties, mockBuilder); + + assertNull(cameraRegions.getBoundaries()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + 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); + + assertNull(cameraRegions.getBoundaries()); + } + + 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/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..b760e1e9ca29 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,77 @@ @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 +96,16 @@ 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(); 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); + } + } +}