From e7808a5fae47c3e6d8f65923f4aeec4c5aea423d Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 5 Mar 2021 16:46:09 +0100 Subject: [PATCH] Extract CaptureCallback implementation --- .../io/flutter/plugins/camera/Camera.java | 131 +++--------------- .../plugins/camera/CameraCaptureCallback.java | 120 ++++++++++++++++ .../flutter/plugins/camera/DartMessenger.java | 2 +- .../camera/DeviceOrientationManager.java | 14 +- .../plugins/camera/PictureCaptureRequest.java | 32 +++-- .../camera/media/MediaRecorderBuilder.java | 14 +- .../io/flutter/plugins/camera/CameraTest.java | 27 ++-- .../camera/PictureCaptureRequestTest.java | 114 +++++++++------ .../media/MediaRecorderBuilderTest.java | 69 +++++---- 9 files changed, 304 insertions(+), 219 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java 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 048f23162eb5..7b47c844d856 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 @@ -68,7 +68,7 @@ interface ErrorCallback { void onError(String errorCode, String errorMessage); } -public class Camera { +class Camera implements CameraCaptureCallback.CameraCaptureStateListener { private static final String TAG = "Camera"; /** Conversion from screen rotation to JPEG orientation. */ @@ -92,7 +92,6 @@ public class Camera { private final SurfaceTextureEntry flutterTexture; private final DeviceOrientationManager deviceOrientationListener; - private final boolean isFrontFacing; private final int sensorOrientation; private final Size captureSize; private final Size previewSize; @@ -166,88 +165,7 @@ public void onImageAvailable(ImageReader reader) { private CameraRegions cameraRegions; private int exposureOffset; /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */ - private final CameraCaptureSession.CaptureCallback mCaptureCallback = - new CameraCaptureSession.CaptureCallback() { - - private void process(CaptureResult result) { - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); - - if (cameraState != CameraState.STATE_PREVIEW) { - // Log.i(TAG, "mCaptureCallback | state: " + cameraState + " | afState: " + afState + " | aeState: " + aeState); - } - - switch (cameraState) { - case STATE_PREVIEW: - { - // We have nothing to do when the camera preview is working normally. - break; - } - - case STATE_WAITING_FOCUS: - { - if (afState == null) { - return; - } else if (afState == CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN - || afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED - || afState == CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { - // CONTROL_AE_STATE can be null on some devices - - if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { - takePictureAfterPrecapture(); - } else { - runPrecaptureSequence(); - } - } - break; - } - - case STATE_WAITING_PRECAPTURE_START: - { - // CONTROL_AE_STATE can be null on some devices - if (aeState == null - || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED - || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE - || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) { - cameraState = CameraState.STATE_WAITING_PRECAPTURE_DONE; - pictureCaptureRequest.setState( - PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE); - } - break; - } - - case STATE_WAITING_PRECAPTURE_DONE: - { - // CONTROL_AE_STATE can be null on some devices - if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { - takePictureAfterPrecapture(); - } else { - if (pictureCaptureRequest.hitPreCaptureTimeout()) { - // Log.i(TAG, "===> Hit precapture timeout"); - unlockAutoFocus(); - } - } - break; - } - } - } - - @Override - public void onCaptureProgressed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureResult partialResult) { - process(partialResult); - } - - @Override - public void onCaptureCompleted( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull TotalCaptureResult result) { - process(result); - } - }; + private final CameraCaptureCallback mCaptureCallback; private Range fpsRange; private PlatformChannel.DeviceOrientation lockedCaptureOrientation; @@ -259,24 +177,6 @@ public Camera( final CameraProperties cameraProperties, final ResolutionPreset resolutionPreset, final boolean enableAudio) { - this( - activity, - flutterTexture, - dartMessenger, - cameraProperties, - resolutionPreset, - enableAudio, - null); - } - - public Camera( - final Activity activity, - final SurfaceTextureEntry flutterTexture, - final DartMessenger dartMessenger, - final CameraProperties cameraProperties, - final ResolutionPreset resolutionPreset, - final boolean enableAudio, - @Nullable final DeviceOrientationManager deviceOrientationManager) { if (activity == null) { throw new IllegalStateException("No activity available!"); @@ -293,6 +193,8 @@ public Camera( this.currentFocusMode = FocusMode.auto; this.exposureOffset = 0; + mCaptureCallback = CameraCaptureCallback.create(this); + // Get camera characteristics and check for supported features getAvailableFpsRange(cameraProperties); mAutoFocusSupported = checkAutoFocusSupported(cameraProperties); @@ -300,12 +202,9 @@ public Camera( // Setup orientation sensorOrientation = cameraProperties.getSensorOrientation(); - isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT; + boolean isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT; - deviceOrientationListener = - deviceOrientationManager != null - ? deviceOrientationManager - : new DeviceOrientationManager( + deviceOrientationListener = DeviceOrientationManager.create( activity, dartMessenger, isFrontFacing, sensorOrientation); deviceOrientationListener.start(); @@ -330,6 +229,21 @@ public Camera( startBackgroundThread(); } + @Override + public void onConverged() { + takePictureAfterPrecapture(); + } + + @Override + public void onPrecapture() { + runPrecaptureSequence(); + } + + @Override + public void onPrecaptureTimeout() { + unlockAutoFocus(); + } + /** Get the current camera state (use for testing). */ public CameraState getState() { return this.cameraState; @@ -642,7 +556,8 @@ public void takePicture(@NonNull final Result result) { final File file = File.createTempFile("CAP", ".jpg", outputDir); // Start a new capture - pictureCaptureRequest = new PictureCaptureRequest(result, file, dartMessenger); + pictureCaptureRequest = PictureCaptureRequest.create(result, file, dartMessenger); + mCaptureCallback.setPictureCaptureRequest(pictureCaptureRequest); } catch (IOException | SecurityException e) { pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null); return; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java new file mode 100644 index 000000000000..67b872c01179 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java @@ -0,0 +1,120 @@ +package io.flutter.plugins.camera; + +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import androidx.annotation.NonNull; + +class CameraCaptureCallback extends CaptureCallback { + interface CameraCaptureStateListener { + void onConverged(); + void onPrecapture(); + void onPrecaptureTimeout(); + } + + private final CameraCaptureStateListener cameraStateListener; + + private CameraState cameraState; + private PictureCaptureRequest pictureCaptureRequest; + + public static CameraCaptureCallback create( + @NonNull CameraCaptureStateListener cameraStateListener) { + return new CameraCaptureCallback(cameraStateListener); + } + + private CameraCaptureCallback( + @NonNull CameraCaptureStateListener cameraStateListener) { + cameraState = CameraState.STATE_PREVIEW; + this.cameraStateListener = cameraStateListener; + this.pictureCaptureRequest = pictureCaptureRequest; + } + + public CameraState getCameraState() { + return cameraState; + } + + public void setCameraState(@NonNull CameraState state) { + cameraState = state; + + if (pictureCaptureRequest != null && state == CameraState.STATE_WAITING_PRECAPTURE_DONE) { + pictureCaptureRequest.setState( + PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE); + } + } + + public void setPictureCaptureRequest(@NonNull PictureCaptureRequest pictureCaptureRequest) { + this.pictureCaptureRequest = pictureCaptureRequest; + } + + private void process(CaptureResult result) { + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + + switch (cameraState) { + case STATE_PREVIEW: + { + // We have nothing to do when the camera preview is working normally. + break; + } + case STATE_WAITING_FOCUS: + { + if (afState == null) { + return; + } else if (afState == CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN + || afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED + || afState == CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + // CONTROL_AE_STATE can be null on some devices + + if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { + cameraStateListener.onConverged(); + } else { + cameraStateListener.onPrecapture(); + } + } + break; + } + + case STATE_WAITING_PRECAPTURE_START: + { + // CONTROL_AE_STATE can be null on some devices + if (aeState == null + || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED + || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE + || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) { + setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE); + } + break; + } + + case STATE_WAITING_PRECAPTURE_DONE: + { + // CONTROL_AE_STATE can be null on some devices + if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + cameraStateListener.onConverged(); + } else if (pictureCaptureRequest != null && pictureCaptureRequest.hitPreCaptureTimeout()) { + // Log.i(TAG, "===> Hit precapture timeout"); + cameraStateListener.onPrecaptureTimeout(); + } + break; + } + } + } + + @Override + public void onCaptureProgressed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureResult partialResult) { + process(partialResult); + } + + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + process(result); + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 3892452892d9..59dfec9c43db 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -41,7 +41,7 @@ enum CameraEventType { } } - DartMessenger(BinaryMessenger messenger, long cameraId) { + public DartMessenger(BinaryMessenger messenger, long cameraId) { cameraChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); deviceChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/device"); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java index b2a504b629d6..a1852ce545de 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java @@ -31,7 +31,19 @@ class DeviceOrientationManager { private OrientationEventListener orientationEventListener; private BroadcastReceiver broadcastReceiver; - public DeviceOrientationManager( + /** + * Factory method to create a device orientation manager. + */ + public static DeviceOrientationManager create( + Activity activity, + DartMessenger messenger, + boolean isFrontFacing, + int sensorOrientation + ) { + return new DeviceOrientationManager(activity, messenger, isFrontFacing, sensorOrientation); + } + + private DeviceOrientationManager( Activity activity, DartMessenger messenger, boolean isFrontFacing, int sensorOrientation) { this.activity = activity; this.messenger = messenger; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java index eb3cd6451316..7613083bfd72 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -51,33 +51,33 @@ class PictureCaptureRequest { }; /** - * Constructor to create a picture capture request. + * Factory method to create a picture capture request. * * @param result * @param file */ - public PictureCaptureRequest( + static PictureCaptureRequest create( MethodChannel.Result result, File file, DartMessenger dartMessenger) { - this( - result, - file, - dartMessenger, - new TimeoutHandler() - ); + return new PictureCaptureRequest(result, file, dartMessenger); } - /** Constructor for unit tests where we can mock the timeout handler */ - public PictureCaptureRequest( + /** + * Private constructor to create a picture capture request. + * + * @param result + * @param file + */ + private PictureCaptureRequest( MethodChannel.Result result, File file, - DartMessenger dartMessenger, - TimeoutHandler timeoutHandler) { + DartMessenger dartMessenger) { + this.result = result; - this.timeoutHandler = timeoutHandler; this.file = file; this.dartMessenger = dartMessenger; + this.timeoutHandler = TimeoutHandler.create(); } /** @@ -184,7 +184,11 @@ static class TimeoutHandler { private static final int REQUEST_TIMEOUT = 5000; private final Handler handler; - TimeoutHandler() { + public static TimeoutHandler create() { + return new TimeoutHandler(); + } + + private TimeoutHandler() { this.handler = new Handler(Looper.getMainLooper()); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index 4c3fb3add230..19acc6f29b0c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -11,30 +11,22 @@ public class MediaRecorderBuilder { static class MediaRecorderFactory { - MediaRecorder makeMediaRecorder() { + static MediaRecorder create() { return new MediaRecorder(); } } private final String outputFilePath; private final CamcorderProfile recordingProfile; - private final MediaRecorderFactory recorderFactory; private boolean enableAudio; private int mediaOrientation; public MediaRecorderBuilder( @NonNull CamcorderProfile recordingProfile, @NonNull String outputFilePath) { - this(recordingProfile, outputFilePath, new MediaRecorderFactory()); - } - MediaRecorderBuilder( - @NonNull CamcorderProfile recordingProfile, - @NonNull String outputFilePath, - MediaRecorderFactory helper) { - this.outputFilePath = outputFilePath; this.recordingProfile = recordingProfile; - this.recorderFactory = helper; + this.outputFilePath = outputFilePath; } public MediaRecorderBuilder setEnableAudio(boolean enableAudio) { @@ -48,7 +40,7 @@ public MediaRecorderBuilder setMediaOrientation(int orientation) { } public MediaRecorder build() throws IOException { - MediaRecorder mediaRecorder = recorderFactory.makeMediaRecorder(); + MediaRecorder mediaRecorder = MediaRecorderFactory.create(); // There's a fixed order that mediaRecorder expects. Only change these functions accordingly. // You can find the specifics here: https://developer.android.com/reference/android/media/MediaRecorder. 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 65c6c11e4dc4..54bb190bd43b 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 @@ -47,20 +47,29 @@ public void should_create_camera_plugin() throws CameraAccessException { mockCamcorderProfile.videoFrameHeight = 480; mockCamcorderProfile.videoFrameWidth = 640; + when(mockCameraProperties.getLensFacing()).thenReturn(0); + when(mockCameraProperties.getSensorOrientation()).thenReturn(0); when(mockCameraProperties.getCameraName()).thenReturn(cameraName); when(mockCameraProperties.getControlAutoFocusAvailableModes()) .thenReturn(new int[] {0, 1, 2}); when(mockCameraProperties.getControlAutoExposureAvailableTargetFpsRanges()).thenReturn(null); - final Camera camera = - new Camera( - mockActivity, - flutterTextureMock, - dartMessengerMock, - mockCameraProperties, - resolutionPreset, - enableAudio, - mockDeviceOrientationManager); + Camera camera = null; + try (MockedStatic mockOrientationManagerFactory = mockStatic(DeviceOrientationManager.class)) { + mockOrientationManagerFactory.when(() -> DeviceOrientationManager.create( + mockActivity, + dartMessengerMock, + true, + 0)).thenReturn(mockDeviceOrientationManager); + + camera = new Camera( + mockActivity, + flutterTextureMock, + dartMessengerMock, + mockCameraProperties, + resolutionPreset, + enableAudio); + } assertNotNull("should create a camera", camera); assertEquals( diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java index 54509857ba3a..5f3a18a30f6f 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -9,13 +9,16 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugins.camera.PictureCaptureRequest.TimeoutHandler; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockedStatic; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) @@ -23,13 +26,13 @@ public class PictureCaptureRequestTest { @Test public void state_is_idle_by_default() { - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, null); assertEquals("Default state is idle", req.getState(), PictureCaptureRequestState.STATE_IDLE); } @Test public void setState_sets_state() { - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, null); req.setState(PictureCaptureRequestState.STATE_WAITING_FOCUS); assertEquals( "State is focusing", req.getState(), PictureCaptureRequestState.STATE_WAITING_FOCUS); @@ -51,7 +54,7 @@ public void setState_sets_state() { @Test public void setState_sends_camera_error_event_When_already_finished() { DartMessenger mockMessenger = mock(DartMessenger.class); - PictureCaptureRequest req = new PictureCaptureRequest(null, null, mockMessenger); + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, mockMessenger); // Make sure state is set to finished req.setState(PictureCaptureRequestState.STATE_FINISHED); @@ -65,35 +68,46 @@ public void setState_sends_camera_error_event_When_already_finished() { @Test public void setState_resets_timeout() { - PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = - mock(PictureCaptureRequest.TimeoutHandler.class); - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null, mockTimeoutHandler); - req.setState(PictureCaptureRequestState.STATE_WAITING_FOCUS); - req.setState(PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_START); - req.setState(PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE); - req.setState(PictureCaptureRequestState.STATE_CAPTURING); - verify(mockTimeoutHandler, times(4)).resetTimeout(any()); - verify(mockTimeoutHandler, never()).clearTimeout(any()); + try(MockedStatic mockTimeoutFactory = mockStatic(TimeoutHandler.class)) { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + + mockTimeoutFactory.when(TimeoutHandler::create).thenReturn(mockTimeoutHandler); + + PictureCaptureRequest req = PictureCaptureRequest + .create(null, null, null); + req.setState(PictureCaptureRequestState.STATE_WAITING_FOCUS); + req.setState(PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_START); + req.setState(PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE); + req.setState(PictureCaptureRequestState.STATE_CAPTURING); + verify(mockTimeoutHandler, times(4)).resetTimeout(any()); + verify(mockTimeoutHandler, never()).clearTimeout(any()); + } } @Test public void setState_clears_timeout() { - PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = - mock(PictureCaptureRequest.TimeoutHandler.class); - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null, mockTimeoutHandler); - req.setState(PictureCaptureRequestState.STATE_IDLE); - req.setState(PictureCaptureRequestState.STATE_FINISHED); - req = new PictureCaptureRequest(null, null, null, mockTimeoutHandler); - req.setState(PictureCaptureRequestState.STATE_ERROR); - verify(mockTimeoutHandler, never()).resetTimeout(any()); - verify(mockTimeoutHandler, times(3)).clearTimeout(any()); + try(MockedStatic mockTimeoutFactory = mockStatic(TimeoutHandler.class)) { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + + mockTimeoutFactory.when(TimeoutHandler::create).thenReturn(mockTimeoutHandler); + + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, null); + req.setState(PictureCaptureRequestState.STATE_IDLE); + req.setState(PictureCaptureRequestState.STATE_FINISHED); + req = PictureCaptureRequest.create(null, null, null); + req.setState(PictureCaptureRequestState.STATE_ERROR); + verify(mockTimeoutHandler, never()).resetTimeout(any()); + verify(mockTimeoutHandler, times(3)).clearTimeout(any()); + } } @Test public void finish_sets_result_and_state() { // Setup MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - PictureCaptureRequest req = new PictureCaptureRequest(mockResult, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(mockResult, null, null); // Act req.finish("/test/path"); // Test @@ -103,20 +117,25 @@ public void finish_sets_result_and_state() { @Test public void finish_clears_timeout() { - PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = - mock(PictureCaptureRequest.TimeoutHandler.class); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - PictureCaptureRequest req = - new PictureCaptureRequest(mockResult, null, null, mockTimeoutHandler); - req.finish("/test/path"); - verify(mockTimeoutHandler, never()).resetTimeout(any()); - verify(mockTimeoutHandler).clearTimeout(any()); + try(MockedStatic mockTimeoutFactory = mockStatic(TimeoutHandler.class)) { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + + mockTimeoutFactory.when(TimeoutHandler::create).thenReturn(mockTimeoutHandler); + + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = + PictureCaptureRequest.create(mockResult, null, null); + req.finish("/test/path"); + verify(mockTimeoutHandler, never()).resetTimeout(any()); + verify(mockTimeoutHandler).clearTimeout(any()); + } } @Test public void isFinished_is_true_When_state_is_finished_or_error() { // Setup - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, null); // Test false states req.setState(PictureCaptureRequestState.STATE_IDLE); assertFalse(req.isFinished()); @@ -127,7 +146,7 @@ public void isFinished_is_true_When_state_is_finished_or_error() { // Test true states req.setState(PictureCaptureRequestState.STATE_FINISHED); assertTrue(req.isFinished()); - req = new PictureCaptureRequest(null, null, null); // Refresh + req = PictureCaptureRequest.create(null, null, null); // Refresh req.setState(PictureCaptureRequestState.STATE_ERROR); assertTrue(req.isFinished()); } @@ -135,7 +154,7 @@ public void isFinished_is_true_When_state_is_finished_or_error() { @Test public void finish_returns_When_in_error_state() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - PictureCaptureRequest req = new PictureCaptureRequest(mockResult, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(mockResult, null, null); // Make sure state is set to error req.setState(PictureCaptureRequestState.STATE_ERROR); @@ -149,7 +168,7 @@ public void finish_returns_When_in_error_state() { @Test(expected = IllegalStateException.class) public void finish_throws_When_already_finished() { // Setup - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, null); req.setState(PictureCaptureRequestState.STATE_FINISHED); // Act req.finish("/test/path"); @@ -159,7 +178,7 @@ public void finish_throws_When_already_finished() { public void error_sets_result_and_state() { // Setup MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - PictureCaptureRequest req = new PictureCaptureRequest(mockResult, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(mockResult, null, null); // Act req.error("ERROR_CODE", "Error Message", null); // Test @@ -169,20 +188,25 @@ public void error_sets_result_and_state() { @Test public void error_clears_timeout() { - PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = - mock(PictureCaptureRequest.TimeoutHandler.class); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - PictureCaptureRequest req = - new PictureCaptureRequest(mockResult, null, null, mockTimeoutHandler); - req.error("ERROR_CODE", "Error Message", null); - verify(mockTimeoutHandler, never()).resetTimeout(any()); - verify(mockTimeoutHandler).clearTimeout(any()); + try(MockedStatic mockTimeoutFactory = mockStatic(TimeoutHandler.class)) { + PictureCaptureRequest.TimeoutHandler mockTimeoutHandler = + mock(PictureCaptureRequest.TimeoutHandler.class); + + mockTimeoutFactory.when(TimeoutHandler::create).thenReturn(mockTimeoutHandler); + + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = PictureCaptureRequest + .create(mockResult, null, null); + req.error("ERROR_CODE", "Error Message", null); + verify(mockTimeoutHandler, never()).resetTimeout(any()); + verify(mockTimeoutHandler).clearTimeout(any()); + } } @Test public void error_returns_When_in_error_state() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - PictureCaptureRequest req = new PictureCaptureRequest(mockResult, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(mockResult, null, null); // Make sure state is set to error req.setState(PictureCaptureRequestState.STATE_ERROR); @@ -196,7 +220,7 @@ public void error_returns_When_in_error_state() { @Test(expected = IllegalStateException.class) public void error_throws_When_already_finished() { // Setup - PictureCaptureRequest req = new PictureCaptureRequest(null, null, null); + PictureCaptureRequest req = PictureCaptureRequest.create(null, null, null); req.setState(PictureCaptureRequestState.STATE_FINISHED); // Act req.error(null, null, null); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java index 823975803994..2f018cbd4215 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java @@ -9,10 +9,12 @@ import android.media.CamcorderProfile; import android.media.MediaRecorder; +import io.flutter.plugins.camera.media.MediaRecorderBuilder.MediaRecorderFactory; import java.io.IOException; import java.lang.reflect.Constructor; import org.junit.Test; import org.mockito.InOrder; +import org.mockito.MockedStatic; public class MediaRecorderBuilderTest { @Test @@ -26,50 +28,57 @@ public void ctor_test() { @Test public void build_Should_set_values_in_correct_order_When_audio_is_disabled() throws IOException { CamcorderProfile recorderProfile = getEmptyCamcorderProfile(); - MediaRecorderBuilder.MediaRecorderFactory mockFactory = - mock(MediaRecorderBuilder.MediaRecorderFactory.class); - MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; - MediaRecorderBuilder builder = - new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) - .setEnableAudio(false) - .setMediaOrientation(mediaOrientation); - - when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); - - MediaRecorder recorder = builder.build(); + MediaRecorder recorder = null; + + try (MockedStatic mockMediaRecorderFactory = mockStatic(MediaRecorderFactory.class)) { + MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); + mockMediaRecorderFactory.when(MediaRecorderFactory::create) + .thenReturn(mockMediaRecorder); + + MediaRecorderBuilder builder = + new MediaRecorderBuilder(recorderProfile, outputFilePath) + .setEnableAudio(false) + .setMediaOrientation(mediaOrientation); + recorder = builder.build(); + } - InOrder inOrder = inOrder(recorder); - inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE); - inOrder.verify(recorder).setOutputFormat(recorderProfile.fileFormat); - inOrder.verify(recorder).setVideoEncoder(recorderProfile.videoCodec); - inOrder.verify(recorder).setVideoEncodingBitRate(recorderProfile.videoBitRate); - inOrder.verify(recorder).setVideoFrameRate(recorderProfile.videoFrameRate); - inOrder - .verify(recorder) - .setVideoSize(recorderProfile.videoFrameWidth, recorderProfile.videoFrameHeight); - inOrder.verify(recorder).setOutputFile(outputFilePath); - inOrder.verify(recorder).setOrientationHint(mediaOrientation); - inOrder.verify(recorder).prepare(); + InOrder inOrder = inOrder(recorder); + inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE); + inOrder.verify(recorder).setOutputFormat(recorderProfile.fileFormat); + inOrder.verify(recorder).setVideoEncoder(recorderProfile.videoCodec); + inOrder.verify(recorder).setVideoEncodingBitRate(recorderProfile.videoBitRate); + inOrder.verify(recorder).setVideoFrameRate(recorderProfile.videoFrameRate); + inOrder + .verify(recorder) + .setVideoSize(recorderProfile.videoFrameWidth, recorderProfile.videoFrameHeight); + inOrder.verify(recorder).setOutputFile(outputFilePath); + inOrder.verify(recorder).setOrientationHint(mediaOrientation); + inOrder.verify(recorder).prepare(); } @Test public void build_Should_set_values_in_correct_order_When_audio_is_enabled() throws IOException { + MediaRecorder recorder = null; CamcorderProfile recorderProfile = getEmptyCamcorderProfile(); MediaRecorderBuilder.MediaRecorderFactory mockFactory = mock(MediaRecorderBuilder.MediaRecorderFactory.class); - MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; - MediaRecorderBuilder builder = - new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) - .setEnableAudio(true) - .setMediaOrientation(mediaOrientation); - when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); + try (MockedStatic mockMediaRecorderFactory = mockStatic(MediaRecorderFactory.class)) { + MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); + mockMediaRecorderFactory.when(MediaRecorderFactory::create) + .thenReturn(mockMediaRecorder); - MediaRecorder recorder = builder.build(); + MediaRecorderBuilder builder = + new MediaRecorderBuilder(recorderProfile, outputFilePath) + .setEnableAudio(true) + .setMediaOrientation(mediaOrientation); + + recorder = builder.build(); + } InOrder inOrder = inOrder(recorder); inOrder.verify(recorder).setAudioSource(MediaRecorder.AudioSource.MIC);