diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 851d98da4d68..678bacf3396c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -127,6 +127,7 @@ class Camera implements CameraCaptureCallback.CameraCaptureStateListener { private final Context applicationContext; private final DartMessenger dartMessenger; private final CameraProperties cameraProperties; + private final CameraFeatureFactory cameraFeatureFactory; private final Activity activity; /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */ private final CameraCaptureCallback cameraCaptureCallback; @@ -186,6 +187,7 @@ public Camera( this.dartMessenger = dartMessenger; this.applicationContext = activity.getApplicationContext(); this.cameraProperties = cameraProperties; + this.cameraFeatureFactory = cameraFeatureFactory; // Setup camera features this.cameraFeatures = @@ -409,7 +411,8 @@ private void createCaptureSession( // Update camera regions cameraFeatures.put( CameraFeatures.regionBoundaries, - new RegionBoundariesFeature(cameraProperties, previewRequestBuilder)); + cameraFeatureFactory.createRegionBoundariesFeature( + cameraProperties, previewRequestBuilder)); // Prepare the callback CameraCaptureSession.StateCallback callback = @@ -878,12 +881,10 @@ public CameraRegions getCameraRegions() { * Set new exposure point from dart. * * @param result Flutter result. - * @param x new x. - * @param y new y. + * @param point The exposure point. */ - public void setExposurePoint(@NonNull final Result result, Double x, Double y) { - ((ExposurePointFeature) cameraFeatures.get(CameraFeatures.exposurePoint)) - .setValue(new Point(x, y)); + public void setExposurePoint(@NonNull final Result result, Point point) { + ((ExposurePointFeature) cameraFeatures.get(CameraFeatures.exposurePoint)).setValue(point); cameraFeatures.get(CameraFeatures.exposurePoint).updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( @@ -961,8 +962,8 @@ public void setFocusMode(@NonNull final Result result, FocusMode newMode) { * @param x new x. * @param y new y. */ - public void setFocusPoint(@NonNull final Result result, Double x, Double y) { - ((FocusPointFeature) cameraFeatures.get(CameraFeatures.focusPoint)).setValue(new Point(x, y)); + public void setFocusPoint(@NonNull final Result result, Point point) { + ((FocusPointFeature) cameraFeatures.get(CameraFeatures.focusPoint)).setValue(point); cameraFeatures.get(CameraFeatures.focusPoint).updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index e54c786149cc..9d6e6f762ceb 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -16,6 +16,7 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.plugins.camera.features.CameraFeatureFactoryImpl; +import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; import io.flutter.plugins.camera.features.flash.FlashMode; @@ -172,7 +173,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) y = call.argument("y"); } try { - camera.setExposurePoint(result, x, y); + camera.setExposurePoint(result, new Point(x, y)); } catch (Exception e) { handleException(e, result); } @@ -239,7 +240,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) y = call.argument("y"); } try { - camera.setFocusPoint(result, x, y); + camera.setFocusPoint(result, new Point(x, y)); } catch (Exception e) { handleException(e, result); } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java index c5e30cc79494..57e35b1fc0f0 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java @@ -4,6 +4,7 @@ package io.flutter.plugins.camera; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -14,22 +15,77 @@ import static org.mockito.Mockito.when; import android.app.Activity; +import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CaptureRequest.Builder; +import android.media.CamcorderProfile; +import android.util.Size; +import androidx.annotation.NonNull; +import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.camera.features.CameraFeatureFactory; +import io.flutter.plugins.camera.features.Point; +import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; +import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; +import io.flutter.plugins.camera.features.exposurelock.ExposureMode; +import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; +import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetValue; +import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; +import io.flutter.plugins.camera.features.flash.FlashFeature; +import io.flutter.plugins.camera.features.flash.FlashMode; +import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; +import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; +import io.flutter.plugins.camera.features.regionboundaries.CameraRegions; +import io.flutter.plugins.camera.features.regionboundaries.RegionBoundariesFeature; +import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; +import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager; +import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; +import io.flutter.plugins.camera.features.zoomlevel.CameraZoom; +import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; import io.flutter.view.TextureRegistry; +import java.util.concurrent.Callable; +import org.junit.Before; import org.junit.Test; public class CameraTest { + private CameraProperties mockCameraProperties; + private CameraFeatureFactory mockCameraFeatureFactory; + private DartMessenger mockDartMessenger; + private Camera camera; + + @Before + public void before() { + mockCameraProperties = mock(CameraProperties.class); + mockCameraFeatureFactory = new TestCameraFeatureFactory(); + mockDartMessenger = mock(DartMessenger.class); + + final Activity mockActivity = mock(Activity.class); + final TextureRegistry.SurfaceTextureEntry mockFlutterTexture = + mock(TextureRegistry.SurfaceTextureEntry.class); + final String cameraName = "1"; + final ResolutionPreset resolutionPreset = ResolutionPreset.high; + final boolean enableAudio = false; + + when(mockCameraProperties.getCameraName()).thenReturn(cameraName); + + camera = + new Camera( + mockActivity, + mockFlutterTexture, + mockCameraFeatureFactory, + mockDartMessenger, + mockCameraProperties, + resolutionPreset, + enableAudio); + } @Test - public void should_create_camera_plugin() throws CameraAccessException { + public void should_create_camera_plugin_and_set_all_features() { final Activity mockActivity = mock(Activity.class); final TextureRegistry.SurfaceTextureEntry mockFlutterTexture = mock(TextureRegistry.SurfaceTextureEntry.class); final CameraFeatureFactory mockCameraFeatureFactory = mock(CameraFeatureFactory.class); - final DartMessenger mockDartMessenger = mock(DartMessenger.class); - final CameraProperties mockCameraProperties = mock(CameraProperties.class); final String cameraName = "1"; final ResolutionPreset resolutionPreset = ResolutionPreset.high; final boolean enableAudio = false; @@ -64,4 +120,303 @@ public void should_create_camera_plugin() throws CameraAccessException { verify(mockCameraFeatureFactory, never()).createRegionBoundariesFeature(any(), any()); assertNotNull("should create a camera", camera); } + + @Test + public void getCaptureSize() { + ResolutionFeature mockResolutionFeature = + mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null); + Size mockSize = mock(Size.class); + + when(mockResolutionFeature.getCaptureSize()).thenReturn(mockSize); + + Size actualSize = camera.getCaptureSize(); + + verify(mockResolutionFeature, times(1)).getCaptureSize(); + assertEquals(mockSize, actualSize); + } + + @Test + public void getPreviewSize() { + ResolutionFeature mockResolutionFeature = + mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null); + Size mockSize = mock(Size.class); + + when(mockResolutionFeature.getPreviewSize()).thenReturn(mockSize); + + Size actualSize = camera.getPreviewSize(); + + verify(mockResolutionFeature, times(1)).getPreviewSize(); + assertEquals(mockSize, actualSize); + } + + @Test + public void getDeviceOrientationManager() { + SensorOrientationFeature mockSensorOrientationFeature = + mockCameraFeatureFactory.createSensorOrientationFeature(mockCameraProperties, null, null); + DeviceOrientationManager mockDeviceOrientationManager = mock(DeviceOrientationManager.class); + + when(mockSensorOrientationFeature.getDeviceOrientationManager()) + .thenReturn(mockDeviceOrientationManager); + + DeviceOrientationManager actualDeviceOrientationManager = camera.getDeviceOrientationManager(); + + verify(mockSensorOrientationFeature, times(1)).getDeviceOrientationManager(); + assertEquals(mockDeviceOrientationManager, actualDeviceOrientationManager); + } + + @Test + public void getExposureOffsetStepSize() { + ExposureOffsetFeature mockExposureOffsetFeature = + mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); + double stepSize = 2.3; + + when(mockExposureOffsetFeature.getExposureOffsetStepSize()).thenReturn(stepSize); + + double actualSize = camera.getExposureOffsetStepSize(); + + verify(mockExposureOffsetFeature, times(1)).getExposureOffsetStepSize(); + assertEquals(stepSize, actualSize, 0); + } + + @Test + public void getMaxExposureOffset() { + ExposureOffsetFeature mockExposureOffsetFeature = + mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); + double expectedMaxOffset = 42.0; + ExposureOffsetValue exposureOffsetValue = new ExposureOffsetValue(21.5, 42.0, 0.0); + + when(mockExposureOffsetFeature.getValue()).thenReturn(exposureOffsetValue); + + double actualMaxOffset = camera.getMaxExposureOffset(); + + verify(mockExposureOffsetFeature, times(1)).getValue(); + assertEquals(expectedMaxOffset, actualMaxOffset, 0); + } + + @Test + public void getMinExposureOffset() { + ExposureOffsetFeature mockExposureOffsetFeature = + mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); + double expectedMinOffset = 21.5; + ExposureOffsetValue exposureOffsetValue = new ExposureOffsetValue(21.5, 42.0, 0.0); + + when(mockExposureOffsetFeature.getValue()).thenReturn(exposureOffsetValue); + + double actualMinOffset = camera.getMinExposureOffset(); + + verify(mockExposureOffsetFeature, times(1)).getValue(); + assertEquals(expectedMinOffset, actualMinOffset, 0); + } + + @Test + public void getMaxZoomLevel() { + ZoomLevelFeature mockZoomLevelFeature = + mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); + Rect sensorRect = mock(Rect.class); + float expectedMaxZoomLevel = 4.2f; + CameraZoom cameraZoom = CameraZoom.create(sensorRect, expectedMaxZoomLevel); + + when(mockZoomLevelFeature.getCameraZoom()).thenReturn(cameraZoom); + + float actualMaxZoomLevel = camera.getMaxZoomLevel(); + + verify(mockZoomLevelFeature, times(1)).getCameraZoom(); + assertEquals(expectedMaxZoomLevel, actualMaxZoomLevel, 0); + } + + @Test + public void getMinZoomLevel() { + float expectedMinZoomLevel = CameraZoom.DEFAULT_ZOOM_FACTOR; + float actualMinZoomLevel = camera.getMinZoomLevel(); + + assertEquals(expectedMinZoomLevel, actualMinZoomLevel, 0); + } + + @Test + public void getRecordingProfile() { + ResolutionFeature mockResolutionFeature = + mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null); + CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class); + + when(mockResolutionFeature.getRecordingProfile()).thenReturn(mockCamcorderProfile); + + CamcorderProfile actualRecordingProfile = camera.getRecordingProfile(); + + verify(mockResolutionFeature, times(1)).getRecordingProfile(); + assertEquals(mockCamcorderProfile, actualRecordingProfile); + } + + @Test + public void setExposureMode_Should_update_exposure_lock_feature_and_update_builder() { + ExposureLockFeature mockExposureLockFeature = + mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + ExposureMode exposureMode = ExposureMode.locked; + + camera.setExposureMode(mockResult, exposureMode); + + verify(mockExposureLockFeature, times(1)).setValue(exposureMode); + verify(mockExposureLockFeature, times(1)).updateBuilder(null); + } + + @Test + public void setExposurePoint_Should_update_exposure_point_feature_and_update_builder() { + ExposurePointFeature mockExposurePointFeature = + mockCameraFeatureFactory.createExposurePointFeature(mockCameraProperties, null); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Point point = new Point(42d, 42d); + + camera.setExposurePoint(mockResult, point); + + verify(mockExposurePointFeature, times(1)).setValue(point); + verify(mockExposurePointFeature, times(1)).updateBuilder(null); + } + + @Test + public void setFlashMode_Should_update_flash_feature_and_update_builder() { + FlashFeature mockFlashFeature = + mockCameraFeatureFactory.createFlashFeature(mockCameraProperties); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + FlashMode flashMode = FlashMode.always; + + camera.setFlashMode(mockResult, flashMode); + + verify(mockFlashFeature, times(1)).setValue(flashMode); + verify(mockFlashFeature, times(1)).updateBuilder(null); + } + + @Test + public void setFocusPoint_Should_update_focus_point_feature_and_update_builder() { + FocusPointFeature mockFocusPointFeature = + mockCameraFeatureFactory.createFocusPointFeature(mockCameraProperties, null); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Point point = new Point(42d, 42d); + + camera.setFocusPoint(mockResult, point); + + verify(mockFocusPointFeature, times(1)).setValue(point); + verify(mockFocusPointFeature, times(1)).updateBuilder(null); + } + + @Test + public void setZoomLevel_Should_update_zoom_level_feature_and_update_builder() + throws CameraAccessException { + ZoomLevelFeature mockZoomLevelFeature = + mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Rect sensorRect = mock(Rect.class); + CameraZoom cameraZoom = CameraZoom.create(sensorRect, 5.0f); + float zoomLevel = 4.2f; + + when(mockZoomLevelFeature.getCameraZoom()).thenReturn(cameraZoom); + + camera.setZoomLevel(mockResult, zoomLevel); + + verify(mockZoomLevelFeature, times(1)).setValue(zoomLevel); + verify(mockZoomLevelFeature, times(1)).updateBuilder(null); + } + + private static class TestCameraFeatureFactory implements CameraFeatureFactory { + private final AutoFocusFeature mockAutoFocusFeature; + private final ExposureLockFeature mockExposureLockFeature; + private final ExposureOffsetFeature mockExposureOffsetFeature; + private final ExposurePointFeature mockExposurePointFeature; + private final FlashFeature mockFlashFeature; + private final FocusPointFeature mockFocusPointFeature; + private final FpsRangeFeature mockFpsRangeFeature; + private final NoiseReductionFeature mockNoiseReductionFeature; + private final RegionBoundariesFeature mockRegionBoundariesFeature; + private final ResolutionFeature mockResolutionFeature; + private final SensorOrientationFeature mockSensorOrientationFeature; + private final ZoomLevelFeature mockZoomLevelFeature; + + public TestCameraFeatureFactory() { + this.mockAutoFocusFeature = mock(AutoFocusFeature.class); + this.mockExposureLockFeature = mock(ExposureLockFeature.class); + this.mockExposureOffsetFeature = mock(ExposureOffsetFeature.class); + this.mockExposurePointFeature = mock(ExposurePointFeature.class); + this.mockFlashFeature = mock(FlashFeature.class); + this.mockFocusPointFeature = mock(FocusPointFeature.class); + this.mockFpsRangeFeature = mock(FpsRangeFeature.class); + this.mockNoiseReductionFeature = mock(NoiseReductionFeature.class); + this.mockRegionBoundariesFeature = mock(RegionBoundariesFeature.class); + this.mockResolutionFeature = mock(ResolutionFeature.class); + this.mockSensorOrientationFeature = mock(SensorOrientationFeature.class); + this.mockZoomLevelFeature = mock(ZoomLevelFeature.class); + } + + @Override + public AutoFocusFeature createAutoFocusFeature( + @NonNull CameraProperties cameraProperties, boolean recordingVideo) { + return mockAutoFocusFeature; + } + + @Override + public ExposureLockFeature createExposureLockFeature( + @NonNull CameraProperties cameraProperties) { + return mockExposureLockFeature; + } + + @Override + public ExposureOffsetFeature createExposureOffsetFeature( + @NonNull CameraProperties cameraProperties) { + return mockExposureOffsetFeature; + } + + @Override + public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) { + return mockFlashFeature; + } + + @Override + public ResolutionFeature createResolutionFeature( + @NonNull CameraProperties cameraProperties, + ResolutionPreset initialSetting, + String cameraName) { + return mockResolutionFeature; + } + + @Override + public FocusPointFeature createFocusPointFeature( + @NonNull CameraProperties cameraProperties, Callable getCameraRegions) { + return mockFocusPointFeature; + } + + @Override + public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) { + return mockFpsRangeFeature; + } + + @Override + public SensorOrientationFeature createSensorOrientationFeature( + @NonNull CameraProperties cameraProperties, + @NonNull Activity activity, + @NonNull DartMessenger dartMessenger) { + return mockSensorOrientationFeature; + } + + @Override + public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) { + return mockZoomLevelFeature; + } + + @Override + public RegionBoundariesFeature createRegionBoundariesFeature( + @NonNull CameraProperties cameraProperties, @NonNull Builder requestBuilder) { + return mockRegionBoundariesFeature; + } + + @Override + public ExposurePointFeature createExposurePointFeature( + @NonNull CameraProperties cameraProperties, + @NonNull Callable getCameraRegions) { + return mockExposurePointFeature; + } + + @Override + public NoiseReductionFeature createNoiseReductionFeature( + @NonNull CameraProperties cameraProperties) { + return mockNoiseReductionFeature; + } + } }