From baae5f6bc22b72f99d1535e448186d354d44f535 Mon Sep 17 00:00:00 2001 From: BeMacized Date: Thu, 10 Jun 2021 14:59:00 +0200 Subject: [PATCH 1/4] Add feature classes for exposure- and focus point functionality. --- packages/camera/camera/android/build.gradle | 2 +- .../plugins/camera/CameraRegionUtils.java | 138 ++++++++ .../exposurepoint/ExposurePointFeature.java | 61 ++-- .../focuspoint/FocusPointFeature.java | 87 ++++++ .../plugins/camera/types/CameraRegions.java | 199 ------------ .../plugins/camera/CameraRegionUtilsTest.java | 294 ++++++++++++++++++ .../ExposurePointFeatureTest.java | 220 ++++++++----- .../focuspoint/FocusPointFeatureTest.java | 270 ++++++++++++++++ .../types/CameraRegionsFactoryTest.java | 201 ------------ .../camera/types/CameraRegionsTest.java | 114 ------- 10 files changed, 972 insertions(+), 614 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java delete mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java delete mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java delete mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java diff --git a/packages/camera/camera/android/build.gradle b/packages/camera/camera/android/build.gradle index 0907c1eeecc9..21b25ad66dea 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-inline:3.5.13' + testImplementation 'org.mockito:mockito-inline:3.11.0' 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/CameraRegionUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java new file mode 100644 index 000000000000..bc03aae27a03 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java @@ -0,0 +1,138 @@ +// 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.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 androidx.annotation.VisibleForTesting; +import java.util.Arrays; + +/** + * Utility class offering functions to calculate values regarding the camera boundaries. + * + *

The functions are used to calculate focus and exposure settings. + */ +public final class CameraRegionUtils { + + /** + * Obtains the boundaries for the currently active camera, that can be used for calculating + * MeteringRectangle instances required for setting focus or exposure settings. + * + * @param cameraProperties - Collection of the characteristics for the current camera device. + * @param requestBuilder - The request builder for the current capture request. + * @return The boundaries for the current camera device. + */ + public static Size getCameraBoundaries( + @NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) { + // 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(); + } + + // Return camera boundaries + return rect == null ? null : new Size(rect.width(), rect.height()); + } else { + // Return camera boundaries + return cameraProperties.getSensorInfoPixelArraySize(); + } + } + + /** + * 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 boundaries - The camera boundaries to calculate the metering rectangle for. + * @param x x - 1 >= coordinate >= 0 + * @param y y - 1 >= coordinate >= 0 + * @return The dimensions of the metering rectangle based on the supplied coordinates and + * boundaries. + */ + public static MeteringRectangle convertPointToMeteringRectangle( + @NonNull Size boundaries, double x, double y) { + assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0); + 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 MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1); + } + + @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; + } + + /** Factory class that assists in creating a {@link MeteringRectangle} instance. */ + static class MeteringRectangleFactory { + /** + * Creates a new instance of the {@link MeteringRectangle} class. + * + *

This method is visible for testing purposes only and should never be used outside this * + * class. + * + * @param x coordinate >= 0 + * @param y coordinate >= 0 + * @param width width >= 0 + * @param height height >= 0 + * @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and + * {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively + * @return new instance of the {@link MeteringRectangle} class. + * @throws IllegalArgumentException if any of the parameters were negative + */ + @VisibleForTesting + public static MeteringRectangle create( + int x, int y, int width, int height, int meteringWeight) { + return new MeteringRectangle(x, y, width, height, meteringWeight); + } + } +} 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 f729d33c8528..905c9922f600 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 @@ -6,28 +6,36 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; -import android.util.Log; -import androidx.annotation.NonNull; +import android.util.Size; import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.CameraRegionUtils; 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); + private Size cameraBoundaries; + private Point exposurePoint; + private MeteringRectangle exposureRectangle; /** * 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) { + public ExposurePointFeature(CameraProperties cameraProperties) { super(cameraProperties); - this.cameraRegions = cameraRegions; + } + + /** + * Sets the camera boundaries that are required for the exposure point feature to function. + * + * @param cameraBoundaries - The camera boundaries to set. + */ + public void setCameraBoundaries(Size cameraBoundaries) { + this.cameraBoundaries = cameraBoundaries; + this.buildExposureRectangle(); } @Override @@ -37,23 +45,19 @@ public String getDebugName() { @Override public Point getValue() { - return currentSetting; + return exposurePoint; } @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); - } + public void setValue(Point value) { + this.exposurePoint = value == null || value.x == null || value.y == null ? null : value; + this.buildExposureRectangle(); } // Whether or not this camera can set the exposure point. @Override public boolean checkIsSupported() { + if (cameraBoundaries == null) return false; Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoExposure(); return supportedRegions != null && supportedRegions > 0; } @@ -63,16 +67,21 @@ 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}); + exposureRectangle == null ? null : new MeteringRectangle[] {exposureRectangle}); + } + + private void buildExposureRectangle() { + if (!checkIsSupported()) { + return; + } + if (this.exposurePoint == null) { + this.exposureRectangle = null; + } else { + this.exposureRectangle = + CameraRegionUtils.convertPointToMeteringRectangle( + this.cameraBoundaries, this.exposurePoint.x, this.exposurePoint.y); + } } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java new file mode 100644 index 000000000000..8f2941646b4a --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java @@ -0,0 +1,87 @@ +// Copyright 2019 The Chromium 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.focuspoint; + +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Size; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.CameraRegionUtils; +import io.flutter.plugins.camera.features.CameraFeature; +import io.flutter.plugins.camera.features.Point; + +/** Focus point controls where in the frame focus will come from. */ +public class FocusPointFeature extends CameraFeature { + + private Size cameraBoundaries; + private Point focusPoint; + private MeteringRectangle focusRectangle; + + /** + * Creates a new instance of the {@link FocusPointFeature}. + * + * @param cameraProperties Collection of the characteristics for the current camera device. + */ + public FocusPointFeature(CameraProperties cameraProperties) { + super(cameraProperties); + } + + /** + * Sets the camera boundaries that are required for the focus point feature to function. + * + * @param cameraBoundaries - The camera boundaries to set. + */ + public void setCameraBoundaries(Size cameraBoundaries) { + this.cameraBoundaries = cameraBoundaries; + this.buildFocusRectangle(); + } + + @Override + public String getDebugName() { + return "FocusPointFeature"; + } + + @Override + public Point getValue() { + return focusPoint; + } + + @Override + public void setValue(Point value) { + this.focusPoint = value == null || value.x == null || value.y == null ? null : value; + this.buildFocusRectangle(); + } + + // Whether or not this camera can set the exposure point. + @Override + public boolean checkIsSupported() { + if (cameraBoundaries == null) return false; + Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoFocus(); + return supportedRegions != null && supportedRegions > 0; + } + + @Override + public void updateBuilder(CaptureRequest.Builder requestBuilder) { + if (!checkIsSupported()) { + return; + } + requestBuilder.set( + CaptureRequest.CONTROL_AF_REGIONS, + focusRectangle == null ? null : new MeteringRectangle[] {focusRectangle}); + } + + private void buildFocusRectangle() { + if (!checkIsSupported()) { + return; + } + if (this.focusPoint == null) { + this.focusRectangle = null; + } else { + this.focusRectangle = + CameraRegionUtils.convertPointToMeteringRectangle( + this.cameraBoundaries, this.focusPoint.x, this.focusPoint.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 deleted file mode 100644 index b86241e78d29..000000000000 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/CameraRegions.java +++ /dev/null @@ -1,199 +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.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/CameraRegionUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java new file mode 100644 index 000000000000..e2255f2c665c --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java @@ -0,0 +1,294 @@ +package io.flutter.plugins.camera; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +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.hardware.camera2.params.MeteringRectangle; +import android.os.Build; +import android.util.Size; +import io.flutter.plugins.camera.utils.TestUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class CameraRegionUtilsTest { + + Size mockCameraBoundaries; + + @Before + public void setUp() { + this.mockCameraBoundaries = mock(Size.class); + when(this.mockCameraBoundaries.getWidth()).thenReturn(100); + when(this.mockCameraBoundaries.getHeight()).thenReturn(100); + } + + @Test + public void + getCameraBoundaries_should_return_sensor_info_pixel_array_size_when_running_pre_android_p() { + updateSdkVersion(Build.VERSION_CODES.O_MR1); + + try { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockCameraBoundaries); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertEquals(mockCameraBoundaries, result); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + getCameraBoundaries_should_return_sensor_info_pixel_array_size_when_distortion_correction_is_null() { + updateSdkVersion(Build.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(mockCameraBoundaries); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertEquals(mockCameraBoundaries, result); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + getCameraBoundaries_should_return_sensor_info_pixel_array_size_when_distortion_correction_is_off() { + updateSdkVersion(Build.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(mockCameraBoundaries); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertEquals(mockCameraBoundaries, result); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + getCameraBoundaries_should_return_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_null() { + updateSdkVersion(Build.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); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertNull(result); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + getCameraBoundaries_should_return_info_pre_correction_active_array_size_when_distortion_correction_mode_is_set_to_off() { + updateSdkVersion(Build.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); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertNull(result); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test + public void + getCameraBoundaries_should_return_sensor_info_active_array_size_when_distortion_correction_mode_is_set() { + updateSdkVersion(Build.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); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertNull(result); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + } finally { + updateSdkVersion(0); + } + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_x_upper_bound() { + CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 1.5, 0); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_x_lower_bound() { + CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, -0.5, 0); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_y_upper_bound() { + CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0, 1.5); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() { + CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0, -0.5); + } + + @Test + public void getMeteringRectangleForPoint_should_return_valid_MeteringRectangle() { + try (MockedStatic mockedMeteringRectangleFactory = + mockStatic(CameraRegionUtils.MeteringRectangleFactory.class)) { + + mockedMeteringRectangleFactory + .when( + () -> + CameraRegionUtils.MeteringRectangleFactory.create( + anyInt(), anyInt(), anyInt(), anyInt(), anyInt())) + .thenAnswer( + new Answer() { + @Override + public MeteringRectangle answer(InvocationOnMock createInvocation) + throws Throwable { + MeteringRectangle mockMeteringRectangle = mock(MeteringRectangle.class); + when(mockMeteringRectangle.getX()).thenReturn(createInvocation.getArgument(0)); + when(mockMeteringRectangle.getY()).thenReturn(createInvocation.getArgument(1)); + when(mockMeteringRectangle.getWidth()) + .thenReturn(createInvocation.getArgument(2)); + when(mockMeteringRectangle.getHeight()) + .thenReturn(createInvocation.getArgument(3)); + when(mockMeteringRectangle.getMeteringWeight()) + .thenReturn(createInvocation.getArgument(4)); + when(mockMeteringRectangle.equals(any())) + .thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock equalsInvocation) + throws Throwable { + MeteringRectangle otherMockMeteringRectangle = + equalsInvocation.getArgument(0); + return mockMeteringRectangle.getX() + == otherMockMeteringRectangle.getX() + && mockMeteringRectangle.getY() + == otherMockMeteringRectangle.getY() + && mockMeteringRectangle.getWidth() + == otherMockMeteringRectangle.getWidth() + && mockMeteringRectangle.getHeight() + == otherMockMeteringRectangle.getHeight() + && mockMeteringRectangle.getMeteringWeight() + == otherMockMeteringRectangle.getMeteringWeight(); + } + }); + return mockMeteringRectangle; + } + }); + + MeteringRectangle r; + // Center + r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0.5, 0.5); + assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(45, 45, 10, 10, 1).equals(r)); + + // Top left + r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0.0, 0.0); + assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 0, 10, 10, 1).equals(r)); + + // Bottom right + r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 1.0, 1.0); + assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 89, 10, 10, 1).equals(r)); + + // Top left + r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 0.0, 1.0); + assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 89, 10, 10, 1).equals(r)); + + // Top right + r = CameraRegionUtils.convertPointToMeteringRectangle(this.mockCameraBoundaries, 1.0, 0.0); + assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 0, 10, 10, 1).equals(r)); + } + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_0_width_boundary() { + new io.flutter.plugins.camera.CameraRegions(new Size(0, 50)); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_0_height_boundary() { + new io.flutter.plugins.camera.CameraRegions(new Size(100, 0)); + } + + private static void updateSdkVersion(int version) { + TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", version); + } +} 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 0aedc59ef635..1c38affa28cc 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 @@ -6,9 +6,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; 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; @@ -17,41 +17,36 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; +import android.util.Size; import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.Point; -import io.flutter.plugins.camera.types.CameraRegions; import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; 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); + CameraRegionUtils mockCameraRegions = mock(CameraRegionUtils.class); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); assertEquals("ExposurePointFeature", exposurePointFeature.getDebugName()); } @Test - public void getValue_should_return_default_point_if_not_set() { + public void getValue_should_return_null_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); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); Point actualPoint = exposurePointFeature.getValue(); - - assertEquals(expectedPoint.x, actualPoint.x); - assertEquals(expectedPoint.y, actualPoint.y); + assertNull(exposurePointFeature.getValue()); } @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); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); Point expectedPoint = new Point(0.0, 0.0); exposurePointFeature.setValue(expectedPoint); @@ -63,45 +58,113 @@ public void getValue_should_echo_the_set_value() { @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 exposurePointFeature = new ExposurePointFeature(mockCameraProperties); exposurePointFeature.setValue(new Point(null, 0.0)); - verify(mockCameraRegions, times(1)).resetAutoExposureMeteringRectangle(); + assertNull(exposurePointFeature.getValue()); } @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 exposurePointFeature = new ExposurePointFeature(mockCameraProperties); exposurePointFeature.setValue(new Point(0.0, null)); - verify(mockCameraRegions, times(1)).resetAutoExposureMeteringRectangle(); + assertNull(exposurePointFeature.getValue()); } @Test - public void setValue_should_reset_point_when_valid_coords_are_supplied() { + public void setValue_should_set_point_when_valid_coords_are_supplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); - CameraRegions mockCameraRegions = mock(CameraRegions.class); - ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, mockCameraRegions); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); Point point = new Point(0.0, 0.0); exposurePointFeature.setValue(point); - verify(mockCameraRegions, times(1)).setAutoExposureMeteringRectangleFromPoint(point.x, point.y); + assertEquals(point, exposurePointFeature.getValue()); + } + + @Test + public void + setValue_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + Size mockedCameraBoundaries = mock(Size.class); + exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + exposurePointFeature.setValue(new Point(0.5, 0.5)); + + mockedCameraRegionUtils.verify( + () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5), + times(1)); + } + } + + @Test + public void setValue_should_not_determine_metering_rectangle_when_no_valid_boundaries_are_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + exposurePointFeature.setValue(new Point(0.5, 0.5)); + + mockedCameraRegionUtils.verifyNoInteractions(); + } + } + + @Test + public void setValue_should_not_determine_metering_rectangle_when_null_coords_are_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + Size mockedCameraBoundaries = mock(Size.class); + exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + exposurePointFeature.setValue(null); + exposurePointFeature.setValue(new Point(null, 0.5)); + exposurePointFeature.setValue(new Point(0.5, null)); + + mockedCameraRegionUtils.verifyNoInteractions(); + } + } + + @Test + public void + setCameraBoundaries_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setValue(new Point(0.5, 0.5)); + Size mockedCameraBoundaries = mock(Size.class); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); + + mockedCameraRegionUtils.verify( + () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5), + times(1)); + } } @Test public void checkIsSupported_should_return_false_when_max_regions_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); - ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, null); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(null); @@ -111,8 +174,8 @@ public void checkIsSupported_should_return_false_when_max_regions_is_null() { @Test public void checkIsSupported_should_return_false_when_max_regions_is_zero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); - ExposurePointFeature exposurePointFeature = - new ExposurePointFeature(mockCameraProperties, null); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); @@ -122,8 +185,8 @@ public void checkIsSupported_should_return_false_when_max_regions_is_zero() { @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); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); @@ -133,64 +196,75 @@ public void checkIsSupported_should_return_true_when_max_regions_is_bigger_then_ @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); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); - exposurePointFeature.updateBuilder(null); + exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCameraRegions, never()).getAEMeteringRectangle(); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); } @Test - public void updateBuilder_should_set_ae_regions_to_null_when_ae_metering_rectangle_is_null() { + public void + updateBuilder_should_set_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() { 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); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + Size mockedCameraBoundaries = mock(Size.class); + MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + mockedCameraRegionUtils + .when( + () -> + CameraRegionUtils.convertPointToMeteringRectangle( + mockedCameraBoundaries, 0.5, 0.5)) + .thenReturn(mockedMeteringRectangle); + exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); + exposurePointFeature.setValue(new Point(0.5, 0.5)); + + exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); + } + + verify(mockCaptureRequestBuilder, times(1)) + .set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {mockedMeteringRectangle}); } @Test - public void updateBuilder_should_set_ae_regions_with_metering_rectangle() { + public void + updateBuilder_should_not_set_metering_rectangle_when_no_valid_boundaries_are_supplied() { 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); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); + + exposurePointFeature.setValue(new Point(0.5, 0.5)); - exposurePointFeature.updateBuilder(mockBuilder); + exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockBuilder, times(1)) - .set(eq(CaptureRequest.CONTROL_AE_REGIONS), any(MeteringRectangle[].class)); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); } @Test - public void updateBuilder_should_silently_fail_when_exception_occurs() { + public void dateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() { 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); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + + exposurePointFeature.setValue(null); + exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + exposurePointFeature.setValue(new Point(0d, null)); + exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + exposurePointFeature.setValue(new Point(null, 0d)); + exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java new file mode 100644 index 000000000000..ec733e64aaba --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java @@ -0,0 +1,270 @@ +// 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.focuspoint; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Size; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.CameraRegionUtils; +import io.flutter.plugins.camera.features.Point; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +public class FocusPointFeatureTest { + @Test + public void getDebugName_should_return_the_name_of_the_feature() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CameraRegionUtils mockCameraRegions = mock(CameraRegionUtils.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + + assertEquals("FocusPointFeature", focusPointFeature.getDebugName()); + } + + @Test + public void getValue_should_return_null_if_not_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + Point actualPoint = focusPointFeature.getValue(); + assertNull(focusPointFeature.getValue()); + } + + @Test + public void getValue_should_echo_the_set_value() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + Point expectedPoint = new Point(0.0, 0.0); + + focusPointFeature.setValue(expectedPoint); + Point actualPoint = focusPointFeature.getValue(); + + assertEquals(expectedPoint, actualPoint); + } + + @Test + public void setValue_should_reset_point_when_x_coord_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + + focusPointFeature.setValue(new Point(null, 0.0)); + + assertNull(focusPointFeature.getValue()); + } + + @Test + public void setValue_should_reset_point_when_y_coord_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + + focusPointFeature.setValue(new Point(0.0, null)); + + assertNull(focusPointFeature.getValue()); + } + + @Test + public void setValue_should_set_point_when_valid_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + Point point = new Point(0.0, 0.0); + + focusPointFeature.setValue(point); + + assertEquals(point, focusPointFeature.getValue()); + } + + @Test + public void + setValue_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + Size mockedCameraBoundaries = mock(Size.class); + focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + focusPointFeature.setValue(new Point(0.5, 0.5)); + + mockedCameraRegionUtils.verify( + () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5), + times(1)); + } + } + + @Test + public void setValue_should_not_determine_metering_rectangle_when_no_valid_boundaries_are_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + focusPointFeature.setValue(new Point(0.5, 0.5)); + + mockedCameraRegionUtils.verifyNoInteractions(); + } + } + + @Test + public void setValue_should_not_determine_metering_rectangle_when_null_coords_are_set() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + Size mockedCameraBoundaries = mock(Size.class); + focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + focusPointFeature.setValue(null); + focusPointFeature.setValue(new Point(null, 0.5)); + focusPointFeature.setValue(new Point(0.5, null)); + + mockedCameraRegionUtils.verifyNoInteractions(); + } + } + + @Test + public void + setCameraBoundaries_should_determine_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setValue(new Point(0.5, 0.5)); + Size mockedCameraBoundaries = mock(Size.class); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + + focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); + + mockedCameraRegionUtils.verify( + () -> CameraRegionUtils.convertPointToMeteringRectangle(mockedCameraBoundaries, 0.5, 0.5), + times(1)); + } + } + + @Test + public void checkIsSupported_should_return_false_when_max_regions_is_null() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(new Size(100, 100)); + + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(null); + + assertFalse(focusPointFeature.checkIsSupported()); + } + + @Test + public void checkIsSupported_should_return_false_when_max_regions_is_zero() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(new Size(100, 100)); + + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(0); + + assertFalse(focusPointFeature.checkIsSupported()); + } + + @Test + public void checkIsSupported_should_return_true_when_max_regions_is_bigger_then_zero() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(new Size(100, 100)); + + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + + assertTrue(focusPointFeature.checkIsSupported()); + } + + @Test + public void updateBuilder_should_return_when_checkIsSupported_is_false() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(0); + + focusPointFeature.updateBuilder(mockCaptureRequestBuilder); + + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + } + + @Test + public void + updateBuilder_should_set_metering_rectangle_when_valid_boundaries_and_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + Size mockedCameraBoundaries = mock(Size.class); + MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); + + try (MockedStatic mockedCameraRegionUtils = + Mockito.mockStatic(CameraRegionUtils.class)) { + mockedCameraRegionUtils + .when( + () -> + CameraRegionUtils.convertPointToMeteringRectangle( + mockedCameraBoundaries, 0.5, 0.5)) + .thenReturn(mockedMeteringRectangle); + focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); + focusPointFeature.setValue(new Point(0.5, 0.5)); + + focusPointFeature.updateBuilder(mockCaptureRequestBuilder); + } + + verify(mockCaptureRequestBuilder, times(1)) + .set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {mockedMeteringRectangle}); + } + + @Test + public void + updateBuilder_should_not_set_metering_rectangle_when_no_valid_boundaries_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); + + focusPointFeature.setValue(new Point(0.5, 0.5)); + + focusPointFeature.updateBuilder(mockCaptureRequestBuilder); + + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + } + + @Test + public void dateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); + CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); + FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + + focusPointFeature.setValue(null); + focusPointFeature.updateBuilder(mockCaptureRequestBuilder); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + focusPointFeature.setValue(new Point(0d, null)); + focusPointFeature.updateBuilder(mockCaptureRequestBuilder); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + focusPointFeature.setValue(new Point(null, 0d)); + focusPointFeature.updateBuilder(mockCaptureRequestBuilder); + verify(mockCaptureRequestBuilder, never()).set(any(), any()); + } +} 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 deleted file mode 100644 index 5fa0c2c4a2a4..000000000000 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsFactoryTest.java +++ /dev/null @@ -1,201 +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.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/types/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java deleted file mode 100644 index b760e1e9ca29..000000000000 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/CameraRegionsTest.java +++ /dev/null @@ -1,114 +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.types; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import android.hardware.camera2.params.MeteringRectangle; -import android.util.Size; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class CameraRegionsTest { - io.flutter.plugins.camera.types.CameraRegions cameraRegions; - - @Before - public void setUp() { - 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.convertPointToMeteringRectangle(1.5, 0); - } - - @Test(expected = AssertionError.class) - public void getMeteringRectangleForPoint_should_throw_for_x_lower_bound() { - cameraRegions.convertPointToMeteringRectangle(-0.5, 0); - } - - @Test(expected = AssertionError.class) - public void getMeteringRectangleForPoint_should_throw_for_y_upper_bound() { - cameraRegions.convertPointToMeteringRectangle(0, 1.5); - } - - @Test(expected = AssertionError.class) - public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() { - cameraRegions.convertPointToMeteringRectangle(0, -0.5); - } - - @Test - public void getMeteringRectangleForPoint_should_return_valid_MeteringRectangle() { - MeteringRectangle r; - // Center - r = cameraRegions.convertPointToMeteringRectangle(0.5, 0.5); - assertEquals(new MeteringRectangle(45, 45, 10, 10, 1), r); - - // Top left - r = cameraRegions.convertPointToMeteringRectangle(0.0, 0.0); - assertEquals(new MeteringRectangle(0, 0, 10, 10, 1), r); - - // Bottom right - r = cameraRegions.convertPointToMeteringRectangle(1.0, 1.0); - assertEquals(new MeteringRectangle(89, 89, 10, 10, 1), r); - - // Top left - r = cameraRegions.convertPointToMeteringRectangle(0.0, 1.0); - assertEquals(new MeteringRectangle(0, 89, 10, 10, 1), r); - - // Top right - 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 io.flutter.plugins.camera.CameraRegions(new Size(0, 50)); - } - - @Test(expected = AssertionError.class) - public void constructor_should_throw_for_0_height_boundary() { - new io.flutter.plugins.camera.CameraRegions(new Size(100, 0)); - } - - @Test - public void setAutoExposureMeteringRectangleFromPoint_should_set_aeMeteringRectangle_for_point() { - cameraRegions.setAutoExposureMeteringRectangleFromPoint(0, 0); - assertEquals(new MeteringRectangle(0, 0, 10, 10, 1), cameraRegions.getAEMeteringRectangle()); - } - - @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)); - cr.setAutoExposureMeteringRectangleFromPoint(0, 0); - assertNotNull(cr.getAEMeteringRectangle()); - cr.resetAutoExposureMeteringRectangle(); - assertNull(cr.getAEMeteringRectangle()); - } - - @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)); - 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)); - cr.setAutoFocusMeteringRectangleFromPoint(0, 0); - assertNotNull(cr.getAFMeteringRectangle()); - cr.resetAutoFocusMeteringRectangle(); - assertNull(cr.getAFMeteringRectangle()); - } -} From 4f169aba29756c7509143fcf7599739bd535ddb6 Mon Sep 17 00:00:00 2001 From: BeMacized Date: Tue, 15 Jun 2021 13:56:02 +0200 Subject: [PATCH 2/4] Implemented PR feedback --- packages/camera/camera/android/build.gradle | 2 +- .../exposurepoint/ExposurePointFeature.java | 9 ++--- .../focuspoint/FocusPointFeature.java | 13 +++---- .../plugins/camera/CameraRegionUtilsTest.java | 3 ++ .../ExposurePointFeatureTest.java | 35 ++++++++++++------- .../focuspoint/FocusPointFeatureTest.java | 35 ++++++++++++------- 6 files changed, 62 insertions(+), 35 deletions(-) diff --git a/packages/camera/camera/android/build.gradle b/packages/camera/camera/android/build.gradle index 21b25ad66dea..65c6d26edb49 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-inline:3.11.0' + testImplementation 'org.mockito:mockito-inline:3.11.1' 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/exposurepoint/ExposurePointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java index 905c9922f600..b98e2666bec2 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 @@ -7,6 +7,7 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; +import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.CameraFeature; @@ -33,7 +34,7 @@ public ExposurePointFeature(CameraProperties cameraProperties) { * * @param cameraBoundaries - The camera boundaries to set. */ - public void setCameraBoundaries(Size cameraBoundaries) { + public void setCameraBoundaries(@NonNull Size cameraBoundaries) { this.cameraBoundaries = cameraBoundaries; this.buildExposureRectangle(); } @@ -57,7 +58,6 @@ public void setValue(Point value) { // Whether or not this camera can set the exposure point. @Override public boolean checkIsSupported() { - if (cameraBoundaries == null) return false; Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoExposure(); return supportedRegions != null && supportedRegions > 0; } @@ -73,8 +73,9 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { } private void buildExposureRectangle() { - if (!checkIsSupported()) { - return; + if (this.cameraBoundaries == null) { + throw new AssertionError( + "The cameraBoundaries should be set (using `ExposurePointFeature.setCameraBoundaries(Size)`) before updating the exposure point."); } if (this.exposurePoint == null) { this.exposureRectangle = null; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java index 8f2941646b4a..92fcfa9f1132 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java @@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// 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. @@ -7,6 +7,7 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; +import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.CameraFeature; @@ -33,7 +34,7 @@ public FocusPointFeature(CameraProperties cameraProperties) { * * @param cameraBoundaries - The camera boundaries to set. */ - public void setCameraBoundaries(Size cameraBoundaries) { + public void setCameraBoundaries(@NonNull Size cameraBoundaries) { this.cameraBoundaries = cameraBoundaries; this.buildFocusRectangle(); } @@ -54,10 +55,9 @@ public void setValue(Point value) { this.buildFocusRectangle(); } - // Whether or not this camera can set the exposure point. + // Whether or not this camera can set the focus point. @Override public boolean checkIsSupported() { - if (cameraBoundaries == null) return false; Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoFocus(); return supportedRegions != null && supportedRegions > 0; } @@ -73,8 +73,9 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { } private void buildFocusRectangle() { - if (!checkIsSupported()) { - return; + if (this.cameraBoundaries == null) { + throw new AssertionError( + "The cameraBoundaries should be set (using `FocusPointFeature.setCameraBoundaries(Size)`) before updating the focus point."); } if (this.focusPoint == null) { this.focusRectangle = null; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java index e2255f2c665c..e248e8adfeb4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java @@ -1,3 +1,6 @@ +// 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 static org.junit.Assert.assertEquals; 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 1c38affa28cc..4a515c6fd0ec 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 @@ -9,6 +9,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -21,11 +22,22 @@ import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.Point; +import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; public class ExposurePointFeatureTest { + + Size mockCameraBoundaries; + + @Before + public void setUp() { + this.mockCameraBoundaries = mock(Size.class); + when(this.mockCameraBoundaries.getWidth()).thenReturn(100); + when(this.mockCameraBoundaries.getHeight()).thenReturn(100); + } + @Test public void getDebugName_should_return_the_name_of_the_feature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); @@ -47,6 +59,7 @@ public void getValue_should_return_null_if_not_set() { public void getValue_should_echo_the_set_value() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point expectedPoint = new Point(0.0, 0.0); exposurePointFeature.setValue(expectedPoint); @@ -59,6 +72,7 @@ public void getValue_should_echo_the_set_value() { public void setValue_should_reset_point_when_x_coord_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(new Point(null, 0.0)); @@ -69,6 +83,7 @@ public void setValue_should_reset_point_when_x_coord_is_null() { public void setValue_should_reset_point_when_y_coord_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(new Point(0.0, null)); @@ -79,6 +94,7 @@ public void setValue_should_reset_point_when_y_coord_is_null() { public void setValue_should_set_point_when_valid_coords_are_supplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point point = new Point(0.0, 0.0); exposurePointFeature.setValue(point); @@ -106,18 +122,15 @@ public void setValue_should_set_point_when_valid_coords_are_supplied() { } } - @Test - public void setValue_should_not_determine_metering_rectangle_when_no_valid_boundaries_are_set() { + @Test(expected = AssertionError.class) + public void setValue_should_throw_assertion_error_when_no_valid_boundaries_are_set() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { - exposurePointFeature.setValue(new Point(0.5, 0.5)); - - mockedCameraRegionUtils.verifyNoInteractions(); } } @@ -146,6 +159,7 @@ public void setValue_should_not_determine_metering_rectangle_when_null_coords_ar CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(new Point(0.5, 0.5)); Size mockedCameraBoundaries = mock(Size.class); @@ -243,28 +257,25 @@ public void updateBuilder_should_return_when_checkIsSupported_is_false() { ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); - exposurePointFeature.setValue(new Point(0.5, 0.5)); - exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); + verify(mockCaptureRequestBuilder, times(1)).set(any(), isNull()); } @Test - public void dateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() { + public void updateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties); + exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(null); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); exposurePointFeature.setValue(new Point(0d, null)); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); exposurePointFeature.setValue(new Point(null, 0d)); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); + verify(mockCaptureRequestBuilder, times(3)).set(any(), isNull()); } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java index ec733e64aaba..d158336ef235 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -21,11 +22,22 @@ import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.Point; +import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; public class FocusPointFeatureTest { + + Size mockCameraBoundaries; + + @Before + public void setUp() { + this.mockCameraBoundaries = mock(Size.class); + when(this.mockCameraBoundaries.getWidth()).thenReturn(100); + when(this.mockCameraBoundaries.getHeight()).thenReturn(100); + } + @Test public void getDebugName_should_return_the_name_of_the_feature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); @@ -47,6 +59,7 @@ public void getValue_should_return_null_if_not_set() { public void getValue_should_echo_the_set_value() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point expectedPoint = new Point(0.0, 0.0); focusPointFeature.setValue(expectedPoint); @@ -59,6 +72,7 @@ public void getValue_should_echo_the_set_value() { public void setValue_should_reset_point_when_x_coord_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(new Point(null, 0.0)); @@ -69,6 +83,7 @@ public void setValue_should_reset_point_when_x_coord_is_null() { public void setValue_should_reset_point_when_y_coord_is_null() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(new Point(0.0, null)); @@ -79,6 +94,7 @@ public void setValue_should_reset_point_when_y_coord_is_null() { public void setValue_should_set_point_when_valid_coords_are_supplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point point = new Point(0.0, 0.0); focusPointFeature.setValue(point); @@ -106,18 +122,15 @@ public void setValue_should_set_point_when_valid_coords_are_supplied() { } } - @Test - public void setValue_should_not_determine_metering_rectangle_when_no_valid_boundaries_are_set() { + @Test(expected = AssertionError.class) + public void setValue_should_throw_assertion_error_when_no_valid_boundaries_are_set() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { - focusPointFeature.setValue(new Point(0.5, 0.5)); - - mockedCameraRegionUtils.verifyNoInteractions(); } } @@ -146,6 +159,7 @@ public void setValue_should_not_determine_metering_rectangle_when_null_coords_ar CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(new Point(0.5, 0.5)); Size mockedCameraBoundaries = mock(Size.class); @@ -243,28 +257,25 @@ public void updateBuilder_should_return_when_checkIsSupported_is_false() { FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); - focusPointFeature.setValue(new Point(0.5, 0.5)); - focusPointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); + verify(mockCaptureRequestBuilder, times(1)).set(any(), isNull()); } @Test - public void dateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() { + public void updateBuilder_should_not_set_metering_rectangle_when_no_valid_coords_are_supplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties); + focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(null); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); focusPointFeature.setValue(new Point(0d, null)); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); focusPointFeature.setValue(new Point(null, 0d)); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); - verify(mockCaptureRequestBuilder, never()).set(any(), any()); + verify(mockCaptureRequestBuilder, times(3)).set(any(), isNull()); } } From ba3e96b17c53cd843201915920709c97920b26b3 Mon Sep 17 00:00:00 2001 From: BeMacized Date: Tue, 22 Jun 2021 10:24:21 +0200 Subject: [PATCH 3/4] Implemented PR feedback --- .../plugins/camera/CameraRegionUtils.java | 55 ++++++++++--------- .../exposurepoint/ExposurePointFeature.java | 2 +- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java index bc03aae27a03..489c2e27ca35 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java @@ -30,14 +30,13 @@ public final class CameraRegionUtils { */ public static Size getCameraBoundaries( @NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) { - // No distortion correction support if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && supportsDistortionCorrection(cameraProperties)) { - // Get the current distortion correction mode + // Get the current distortion correction mode. Integer distortionCorrectionMode = requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); - // Return the correct boundaries depending on the mode + // Return the correct boundaries depending on the mode. android.graphics.Rect rect; if (distortionCorrectionMode == null || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { @@ -46,27 +45,26 @@ && supportsDistortionCorrection(cameraProperties)) { rect = cameraProperties.getSensorInfoActiveArraySize(); } - // Return camera boundaries - return rect == null ? null : new Size(rect.width(), rect.height()); + return new Size(rect.width(), rect.height()); } else { - // Return camera boundaries + // No distortion correction support. return cameraProperties.getSensorInfoPixelArraySize(); } } /** - * Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the centre + * Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the center * 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. + * coordinates as the center point. * * @param boundaries - The camera boundaries to calculate the metering rectangle for. - * @param x x - 1 >= coordinate >= 0 - * @param y y - 1 >= coordinate >= 0 + * @param x x - 1 >= coordinate >= 0. + * @param y y - 1 >= coordinate >= 0. * @return The dimensions of the metering rectangle based on the supplied coordinates and * boundaries. */ @@ -76,25 +74,32 @@ public static MeteringRectangle convertPointToMeteringRectangle( assert (x >= 0 && x <= 1); assert (y >= 0 && y <= 1); - // Interpolate the target coordinate + // 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) + // 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 + // 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; + // 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; + if (targetX > maxTargetX) { + targetX = maxTargetX; + } + if (targetY > maxTargetY) { + targetY = maxTargetY; + } - // Build the metering rectangle + // Build the metering rectangle. return MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1); } @@ -120,14 +125,14 @@ static class MeteringRectangleFactory { *

This method is visible for testing purposes only and should never be used outside this * * class. * - * @param x coordinate >= 0 - * @param y coordinate >= 0 - * @param width width >= 0 - * @param height height >= 0 + * @param x coordinate >= 0. + * @param y coordinate >= 0. + * @param width width >= 0. + * @param height height >= 0. * @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and * {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively * @return new instance of the {@link MeteringRectangle} class. - * @throws IllegalArgumentException if any of the parameters were negative + * @throws IllegalArgumentException if any of the parameters were negative. */ @VisibleForTesting public static MeteringRectangle create( 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 b98e2666bec2..8c2ee6167846 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 @@ -51,7 +51,7 @@ public Point getValue() { @Override public void setValue(Point value) { - this.exposurePoint = value == null || value.x == null || value.y == null ? null : value; + this.exposurePoint = (value == null || value.x == null || value.y == null) ? null : value; this.buildExposureRectangle(); } From 25a7bb005980d802faeb6e067434fc20c76939db Mon Sep 17 00:00:00 2001 From: BeMacized Date: Wed, 23 Jun 2021 20:06:14 +0200 Subject: [PATCH 4/4] [camera] Fix native android tests --- .../plugins/camera/CameraRegionUtils.java | 20 +++- .../plugins/camera/CameraRegionUtilsTest.java | 102 ++++++++++++++---- 2 files changed, 98 insertions(+), 24 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java index 489c2e27ca35..ff8a49f1d148 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java @@ -45,7 +45,7 @@ && supportsDistortionCorrection(cameraProperties)) { rect = cameraProperties.getSensorInfoActiveArraySize(); } - return new Size(rect.width(), rect.height()); + return SizeFactory.create(rect.width(), rect.height()); } else { // No distortion correction support. return cameraProperties.getSensorInfoPixelArraySize(); @@ -140,4 +140,22 @@ public static MeteringRectangle create( return new MeteringRectangle(x, y, width, height, meteringWeight); } } + + /** Factory class that assists in creating a {@link Size} instance. */ + static class SizeFactory { + /** + * Creates a new instance of the {@link Size} class. + * + *

This method is visible for testing purposes only and should never be used outside this * + * class. + * + * @param width width >= 0. + * @param height height >= 0. + * @return new instance of the {@link Size} class. + */ + @VisibleForTesting + public static Size create(int width, int height) { + return new Size(width, height); + } + } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java index e248e8adfeb4..2d65c4e0fc05 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtilsTest.java @@ -4,7 +4,6 @@ package io.flutter.plugins.camera; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -14,6 +13,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.Rect; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.os.Build; @@ -109,6 +109,9 @@ public void setUp() { try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + Rect mockSensorInfoPreCorrectionActiveArraySize = mock(Rect.class); + when(mockSensorInfoPreCorrectionActiveArraySize.width()).thenReturn(100); + when(mockSensorInfoPreCorrectionActiveArraySize.height()).thenReturn(100); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( @@ -116,15 +119,30 @@ public void setUp() { CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); - when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)).thenReturn(null); - when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); - - Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); - - assertNull(result); - verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); - verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()) + .thenReturn(mockSensorInfoPreCorrectionActiveArraySize); + + try (MockedStatic mockedSizeFactory = + mockStatic(CameraRegionUtils.SizeFactory.class)) { + mockedSizeFactory + .when(() -> CameraRegionUtils.SizeFactory.create(anyInt(), anyInt())) + .thenAnswer( + (Answer) + invocation -> { + Size mockSize = mock(Size.class); + when(mockSize.getWidth()).thenReturn(invocation.getArgument(0)); + when(mockSize.getHeight()).thenReturn(invocation.getArgument(1)); + return mockSize; + }); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertEquals(100, result.getWidth()); + assertEquals(100, result.getHeight()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } } finally { updateSdkVersion(0); } @@ -138,6 +156,9 @@ public void setUp() { try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + Rect mockSensorInfoPreCorrectionActiveArraySize = mock(Rect.class); + when(mockSensorInfoPreCorrectionActiveArraySize.width()).thenReturn(100); + when(mockSensorInfoPreCorrectionActiveArraySize.height()).thenReturn(100); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( @@ -148,13 +169,29 @@ public void setUp() { when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_OFF); - when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()).thenReturn(null); - - Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); - - assertNull(result); - verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); - verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()) + .thenReturn(mockSensorInfoPreCorrectionActiveArraySize); + + try (MockedStatic mockedSizeFactory = + mockStatic(CameraRegionUtils.SizeFactory.class)) { + mockedSizeFactory + .when(() -> CameraRegionUtils.SizeFactory.create(anyInt(), anyInt())) + .thenAnswer( + (Answer) + invocation -> { + Size mockSize = mock(Size.class); + when(mockSize.getWidth()).thenReturn(invocation.getArgument(0)); + when(mockSize.getHeight()).thenReturn(invocation.getArgument(1)); + return mockSize; + }); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertEquals(100, result.getWidth()); + assertEquals(100, result.getHeight()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); + } } finally { updateSdkVersion(0); } @@ -168,6 +205,9 @@ public void setUp() { try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + Rect mockSensorInfoActiveArraySize = mock(Rect.class); + when(mockSensorInfoActiveArraySize.width()).thenReturn(100); + when(mockSensorInfoActiveArraySize.height()).thenReturn(100); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( @@ -178,13 +218,29 @@ public void setUp() { when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_FAST); - when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null); - - Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); - - assertNull(result); - verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); - verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + when(mockCameraProperties.getSensorInfoActiveArraySize()) + .thenReturn(mockSensorInfoActiveArraySize); + + try (MockedStatic mockedSizeFactory = + mockStatic(CameraRegionUtils.SizeFactory.class)) { + mockedSizeFactory + .when(() -> CameraRegionUtils.SizeFactory.create(anyInt(), anyInt())) + .thenAnswer( + (Answer) + invocation -> { + Size mockSize = mock(Size.class); + when(mockSize.getWidth()).thenReturn(invocation.getArgument(0)); + when(mockSize.getHeight()).thenReturn(invocation.getArgument(1)); + return mockSize; + }); + + Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); + + assertEquals(100, result.getWidth()); + assertEquals(100, result.getHeight()); + verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); + verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); + } } finally { updateSdkVersion(0); }