From 75029395610e9543232eac36c477a633526d5dae Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sun, 8 Dec 2019 19:01:03 +0100 Subject: [PATCH] Rewrite tests, improve test tools --- cameraview/build.gradle | 2 +- .../otaliastudios/cameraview/BaseTest.java | 16 +- .../cameraview/CameraLoggerTest.java | 4 +- .../cameraview/CameraUtilsTest.java | 18 +-- .../cameraview/CameraViewCallbacksTest.java | 4 +- .../cameraview/CameraViewTest.java | 18 +-- .../engine/CameraIntegrationTest.java | 76 ++++----- .../cameraview/engine/MockCameraEngine.java | 17 +- .../cameraview/gesture/GestureFinderTest.java | 6 +- .../gesture/PinchGestureFinderTest.java | 4 +- .../gesture/ScrollGestureFinderTest.java | 6 +- .../gesture/TapGestureFinderTest.java | 8 +- .../internal/GridLinesLayoutTest.java | 28 ++-- .../internal/utils/CamcorderProfilesTest.java | 2 +- .../internal/utils/ImageHelperTest.java | 8 +- .../internal/utils/WorkerHandlerTest.java | 40 ++--- .../cameraview/markers/MarkerLayoutTest.java | 2 +- .../cameraview/preview/CameraPreviewTest.java | 28 ++-- .../otaliastudios/cameraview/tools/Op.java | 149 ++++++++++++++++++ .../{runner => tools}/SdkExclude.java | 2 +- .../{runner => tools}/SdkExcludeFilter.java | 2 +- .../cameraview/internal/GridLinesLayout.java | 13 +- .../cameraview/internal/utils/Op.java | 111 ------------- .../cameraview/preview/CameraPreview.java | 21 +-- .../cameraview/preview/GlCameraPreview.java | 7 +- .../preview/TextureCameraPreview.java | 8 +- 26 files changed, 326 insertions(+), 274 deletions(-) create mode 100644 cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/Op.java rename cameraview/src/androidTest/java/com/otaliastudios/cameraview/{runner => tools}/SdkExclude.java (92%) rename cameraview/src/androidTest/java/com/otaliastudios/cameraview/{runner => tools}/SdkExcludeFilter.java (96%) delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Op.java diff --git a/cameraview/build.gradle b/cameraview/build.gradle index 77d686b9..409e3ce7 100644 --- a/cameraview/build.gradle +++ b/cameraview/build.gradle @@ -17,7 +17,7 @@ android { versionCode 1 versionName project.version testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - testInstrumentationRunnerArgument "filter", "com.otaliastudios.cameraview.runner.SdkExcludeFilter" + testInstrumentationRunnerArgument "filter", "com.otaliastudios.cameraview.tools.SdkExcludeFilter" } buildTypes { diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java index 081a6fd5..494f03f8 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java @@ -11,7 +11,7 @@ import android.os.PowerManager; import androidx.annotation.NonNull; import androidx.test.platform.app.InstrumentationRegistry; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import org.junit.After; import org.junit.AfterClass; @@ -103,7 +103,7 @@ public class BaseTest { } @NonNull - protected static Stubber doCountDown(final CountDownLatch latch) { + protected static Stubber doCountDown(@NonNull final CountDownLatch latch) { return doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { @@ -118,7 +118,7 @@ public class BaseTest { return doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { - op.end(response); + op.controller().end(response); return null; } }); @@ -126,14 +126,6 @@ public class BaseTest { @NonNull protected static Stubber doEndOp(final Op op, final int withReturnArgument) { - return doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - //noinspection unchecked - T o = (T) invocation.getArguments()[withReturnArgument]; - op.end(o); - return null; - } - }); + return op.controller().from(withReturnArgument); } } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraLoggerTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraLoggerTest.java index e26c0bff..d3d985a4 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraLoggerTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraLoggerTest.java @@ -1,7 +1,7 @@ package com.otaliastudios.cameraview; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -109,7 +109,7 @@ public class CameraLoggerTest extends BaseTest { CameraLogger.Logger mock = mock(CameraLogger.Logger.class); CameraLogger.registerLogger(mock); - final Op op = new Op<>(); + final Op op = new Op<>(false); doEndOp(op, 3) .when(mock) .log(anyInt(), anyString(), anyString(), any(Throwable.class)); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java index dd22d5c5..8da82853 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java @@ -6,11 +6,10 @@ import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Color; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -20,7 +19,6 @@ import org.junit.runner.RunWith; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; @@ -51,21 +49,21 @@ public class CameraUtilsTest extends BaseTest { private Op writeAndReadString(@NonNull String data) { final File file = new File(getContext().getFilesDir(), "string.txt"); final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); - final Op result = new Op<>(true); + final Op result = new Op<>(); final FileCallback callback = new FileCallback() { @Override public void onFileReady(@Nullable File file) { if (file == null) { - result.end(null); + result.controller().end(null); } else { // Read back the file. try { FileInputStream stream = new FileInputStream(file); byte[] bytes = new byte[stream.available()]; stream.read(bytes); - result.end(new String(bytes, Charset.forName("UTF-8"))); + result.controller().end(new String(bytes, Charset.forName("UTF-8"))); } catch (IOException e) { - result.end(null); + result.controller().end(null); } } } @@ -94,12 +92,12 @@ public class CameraUtilsTest extends BaseTest { source.compress(Bitmap.CompressFormat.PNG, 100, os); final byte[] data = os.toByteArray(); - final Op decode = new Op<>(true); + final Op decode = new Op<>(); if (async) { final BitmapCallback callback = new BitmapCallback() { @Override public void onBitmapReady(Bitmap bitmap) { - decode.end(bitmap); + decode.controller().end(bitmap); } }; @@ -121,7 +119,7 @@ public class CameraUtilsTest extends BaseTest { } else { result = CameraUtils.decodeBitmap(data); } - decode.end(result); + decode.controller().end(result); } return decode; } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java index 935ec3d5..6439ee01 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java @@ -17,7 +17,7 @@ import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.gesture.GestureAction; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import com.otaliastudios.cameraview.engine.MockCameraEngine; import com.otaliastudios.cameraview.markers.AutoFocusMarker; import com.otaliastudios.cameraview.markers.AutoFocusTrigger; @@ -90,7 +90,7 @@ public class CameraViewCallbacksTest extends BaseTest { camera.doInstantiatePreview(); camera.addCameraListener(listener); camera.addFrameProcessor(processor); - op = new Op<>(true); + op = new Op<>(); } }); } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index 4a1bb5f1..7bdc790c 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -23,9 +23,9 @@ import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.engine.CameraEngine; +import com.otaliastudios.cameraview.engine.orchestrator.CameraState; import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.Filters; -import com.otaliastudios.cameraview.filter.NoFilter; import com.otaliastudios.cameraview.filters.DuotoneFilter; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; @@ -41,7 +41,7 @@ import com.otaliastudios.cameraview.gesture.PinchGestureFinder; import com.otaliastudios.cameraview.gesture.ScrollGestureFinder; import com.otaliastudios.cameraview.gesture.TapGestureFinder; import com.otaliastudios.cameraview.engine.MockCameraEngine; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import com.otaliastudios.cameraview.markers.AutoFocusMarker; import com.otaliastudios.cameraview.markers.DefaultAutoFocusMarker; import com.otaliastudios.cameraview.markers.MarkerLayout; @@ -239,7 +239,7 @@ public class CameraViewTest extends BaseTest { public void testGestureAction_capture() { CameraOptions o = mock(CameraOptions.class); mockController.setMockCameraOptions(o); - mockController.setMockEngineState(true); + mockController.setMockState(CameraState.PREVIEW); MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); uiSync(new Runnable() { @Override @@ -261,7 +261,7 @@ public class CameraViewTest extends BaseTest { public void testGestureAction_focus() { CameraOptions o = mock(CameraOptions.class); mockController.setMockCameraOptions(o); - mockController.setMockEngineState(true); + mockController.setMockState(CameraState.PREVIEW); MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); uiSync(new Runnable() { @Override @@ -286,7 +286,7 @@ public class CameraViewTest extends BaseTest { public void testGestureAction_zoom() { CameraOptions o = mock(CameraOptions.class); mockController.setMockCameraOptions(o); - mockController.setMockEngineState(true); + mockController.setMockState(CameraState.PREVIEW); mockController.mZoomChanged = false; MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); final FactorHolder factor = new FactorHolder(); @@ -327,7 +327,7 @@ public class CameraViewTest extends BaseTest { o.exposureCorrectionMaxValue = 10F; o.exposureCorrectionMinValue = -10F; mockController.setMockCameraOptions(o); - mockController.setMockEngineState(true); + mockController.setMockState(CameraState.PREVIEW); mockController.mExposureCorrectionChanged = false; MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); final FactorHolder factor = new FactorHolder(); @@ -363,7 +363,7 @@ public class CameraViewTest extends BaseTest { @Test public void testGestureAction_filterControl1() { - mockController.setMockEngineState(true); + mockController.setMockState(CameraState.PREVIEW); mockController.setMockCameraOptions(mock(CameraOptions.class)); DuotoneFilter filter = new DuotoneFilter(); // supports two parameters filter.setParameter1(0F); @@ -405,7 +405,7 @@ public class CameraViewTest extends BaseTest { @Test public void testGestureAction_filterControl2() { - mockController.setMockEngineState(true); + mockController.setMockState(CameraState.PREVIEW); mockController.setMockCameraOptions(mock(CameraOptions.class)); DuotoneFilter filter = new DuotoneFilter(); // supports two parameters filter.setParameter2(0F); @@ -891,7 +891,7 @@ public class CameraViewTest extends BaseTest { cameraView.mMarkerLayout = markerLayout; final PointF point = new PointF(0, 0); final PointF[] points = new PointF[]{ point }; - final Op op = new Op<>(true); + final Op op = new Op<>(); doEndOp(op, true).when(markerLayout).onEvent(MarkerLayout.TYPE_AUTOFOCUS, points); cameraView.mCameraCallbacks.dispatchOnFocusStart(Gesture.TAP, point); assertNotNull(op.await(100)); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java index 5563aba2..2c85f09f 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java @@ -25,9 +25,10 @@ import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.WhiteBalance; +import com.otaliastudios.cameraview.engine.orchestrator.CameraState; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.size.Size; @@ -110,12 +111,12 @@ public abstract class CameraIntegrationTest extends BaseTest { // Ensure that controller exceptions are thrown on this thread (not on the UI thread). // TODO this makes debugging for wrong tests very hard, as we don't get the exception // unless waitForUiException() is called. - uiExceptionOp = new Op<>(true); + uiExceptionOp = new Op<>(); WorkerHandler crashThread = WorkerHandler.get("CrashThread"); crashThread.getThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { - uiExceptionOp.end(e); + uiExceptionOp.controller().end(e); } }); controller.mCrashHandler = crashThread.getHandler(); @@ -139,7 +140,7 @@ public abstract class CameraIntegrationTest extends BaseTest { private CameraOptions openSync(boolean expectSuccess) { camera.open(); - final Op open = new Op<>(true); + final Op open = new Op<>(); doEndOp(open, 0).when(listener).onCameraOpened(any(CameraOptions.class)); CameraOptions result = open.await(DELAY); if (expectSuccess) { @@ -156,13 +157,12 @@ public abstract class CameraIntegrationTest extends BaseTest { // Extra wait for the bind and preview state, so we run tests in a fully operational // state. If we didn't do so, we could have null values, for example, in getPictureSize // or in getSnapshotSize. - while (controller.getBindState() != CameraEngine.STATE_STARTED) {} - while (controller.getPreviewState() != CameraEngine.STATE_STARTED) {} + while (controller.getState() != CameraState.PREVIEW) {} } private void closeSync(boolean expectSuccess) { camera.close(); - final Op close = new Op<>(true); + final Op close = new Op<>(); doEndOp(close, true).when(listener).onCameraClosed(); Boolean result = close.await(DELAY); if (expectSuccess) { @@ -180,7 +180,7 @@ public abstract class CameraIntegrationTest extends BaseTest { doCountDown(onVideoRecordingEnd).when(listener).onVideoRecordingEnd(); // Op for onVideoTaken. - final Op video = new Op<>(true); + final Op video = new Op<>(); doEndOp(video, 0).when(listener).onVideoTaken(any(VideoResult.class)); doEndOp(video, null).when(listener).onCameraError(argThat(new ArgumentMatcher() { @Override @@ -218,7 +218,7 @@ public abstract class CameraIntegrationTest extends BaseTest { @Nullable private PictureResult waitForPictureResult(boolean expectSuccess) { - final Op pic = new Op<>(true); + final Op pic = new Op<>(); doEndOp(pic, 0).when(listener).onPictureTaken(any(PictureResult.class)); doEndOp(pic, null).when(listener).onCameraError(argThat(new ArgumentMatcher() { @Override @@ -240,7 +240,7 @@ public abstract class CameraIntegrationTest extends BaseTest { } private void takeVideoSync(boolean expectSuccess, int duration) { - final Op op = new Op<>(true); + final Op op = new Op<>(); doEndOp(op, true).when(listener).onVideoRecordingStart(); doEndOp(op, false).when(listener).onCameraError(argThat(new ArgumentMatcher() { @Override @@ -269,7 +269,7 @@ public abstract class CameraIntegrationTest extends BaseTest { } private void takeVideoSnapshotSync(boolean expectSuccess, int duration) { - final Op op = new Op<>(true); + final Op op = new Op<>(); doEndOp(op, true).when(listener).onVideoRecordingStart(); doEndOp(op, false).when(listener).onCameraError(argThat(new ArgumentMatcher() { @Override @@ -296,14 +296,11 @@ public abstract class CameraIntegrationTest extends BaseTest { @Test public void testOpenClose() { - // Starting and stopping are hard to get since they happen on another thread. - assertEquals(controller.getEngineState(), CameraEngine.STATE_STOPPED); - + assertEquals(controller.getState(), CameraState.OFF); openSync(true); - assertEquals(controller.getEngineState(), CameraEngine.STATE_STARTED); - + assertTrue(controller.getState().isAtLeast(CameraState.ENGINE)); closeSync(true); - assertEquals(controller.getEngineState(), CameraEngine.STATE_STOPPED); + assertEquals(controller.getState(), CameraState.OFF); } @Test @@ -389,12 +386,11 @@ public abstract class CameraIntegrationTest extends BaseTest { @Test public void testSetZoom() { CameraOptions options = openSync(true); - - controller.mZoomOp.listen(); float oldValue = camera.getZoom(); float newValue = 0.65f; camera.setZoom(newValue); - controller.mZoomOp.await(500); + Op op = new Op<>(controller.mZoomTask); + op.await(500); if (options.isZoomSupported()) { assertEquals(newValue, camera.getZoom(), 0f); @@ -406,12 +402,11 @@ public abstract class CameraIntegrationTest extends BaseTest { @Test public void testSetExposureCorrection() { CameraOptions options = openSync(true); - - controller.mExposureCorrectionOp.listen(); float oldValue = camera.getExposureCorrection(); float newValue = options.getExposureCorrectionMaxValue(); camera.setExposureCorrection(newValue); - controller.mExposureCorrectionOp.await(300); + Op op = new Op<>(controller.mExposureCorrectionTask); + op.await(300); if (options.isExposureCorrectionSupported()) { assertEquals(newValue, camera.getExposureCorrection(), 0f); @@ -426,9 +421,10 @@ public abstract class CameraIntegrationTest extends BaseTest { Flash[] values = Flash.values(); Flash oldValue = camera.getFlash(); for (Flash value : values) { - controller.mFlashOp.listen(); + camera.setFlash(value); - controller.mFlashOp.await(300); + Op op = new Op<>(controller.mFlashTask); + op.await(300); if (options.supports(value)) { assertEquals(camera.getFlash(), value); oldValue = value; @@ -444,9 +440,9 @@ public abstract class CameraIntegrationTest extends BaseTest { WhiteBalance[] values = WhiteBalance.values(); WhiteBalance oldValue = camera.getWhiteBalance(); for (WhiteBalance value : values) { - controller.mWhiteBalanceOp.listen(); camera.setWhiteBalance(value); - controller.mWhiteBalanceOp.await(300); + Op op = new Op<>(controller.mWhiteBalanceTask); + op.await(300); if (options.supports(value)) { assertEquals(camera.getWhiteBalance(), value); oldValue = value; @@ -462,9 +458,9 @@ public abstract class CameraIntegrationTest extends BaseTest { Hdr[] values = Hdr.values(); Hdr oldValue = camera.getHdr(); for (Hdr value : values) { - controller.mHdrOp.listen(); camera.setHdr(value); - controller.mHdrOp.await(300); + Op op = new Op<>(controller.mHdrTask); + op.await(300); if (options.supports(value)) { assertEquals(camera.getHdr(), value); oldValue = value; @@ -487,9 +483,9 @@ public abstract class CameraIntegrationTest extends BaseTest { @Test public void testSetLocation() { openSync(true); - controller.mLocationOp.listen(); camera.setLocation(10d, 2d); - controller.mLocationOp.await(300); + Op op = new Op<>(controller.mLocationTask); + op.await(300); assertNotNull(camera.getLocation()); assertEquals(camera.getLocation().getLatitude(), 10d, 0d); assertEquals(camera.getLocation().getLongitude(), 2d, 0d); @@ -499,19 +495,19 @@ public abstract class CameraIntegrationTest extends BaseTest { @Test public void testSetPreviewFrameRate() { openSync(true); - controller.mPreviewFrameRateOp.listen(); camera.setPreviewFrameRate(30); - controller.mPreviewFrameRateOp.await(300); + Op op = new Op<>(controller.mPreviewFrameRateTask); + op.await(300); assertEquals(camera.getPreviewFrameRate(), 30, 0); } @Test public void testSetPlaySounds() { - controller.mPlaySoundsOp.listen(); boolean oldValue = camera.getPlaySounds(); boolean newValue = !oldValue; camera.setPlaySounds(newValue); - controller.mPlaySoundsOp.await(300); + Op op = new Op<>(controller.mPlaySoundsTask); + op.await(300); if (controller instanceof Camera1Engine) { Camera1Engine camera1Engine = (Camera1Engine) controller; @@ -647,7 +643,7 @@ public abstract class CameraIntegrationTest extends BaseTest { public void testStartAutoFocus() { CameraOptions o = openSync(true); - final Op focus = new Op<>(true); + final Op focus = new Op<>(); doEndOp(focus, 0).when(listener).onAutoFocusStart(any(PointF.class)); camera.startAutoFocus(1, 1); @@ -664,7 +660,7 @@ public abstract class CameraIntegrationTest extends BaseTest { public void testStopAutoFocus() { CameraOptions o = openSync(true); - final Op focus = new Op<>(true); + final Op focus = new Op<>(); doEndOp(focus, 1).when(listener).onAutoFocusEnd(anyBoolean(), any(PointF.class)); camera.startAutoFocus(1, 1); @@ -809,10 +805,8 @@ public abstract class CameraIntegrationTest extends BaseTest { //region Picture Formats - // TODO this fails because setPictureFormat triggers a restart() and takePicture can be called - // in the middle of the restart, failing because the engine is not properly set up. To fix this - // we would have to change the whole CameraEngine threading scheme. - // @Test + @SuppressWarnings("ConstantConditions") + @Test public void testPictureFormat_DNG() { openSync(true); if (camera.getCameraOptions().supports(PictureFormat.DNG)) { diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java index 6bbd97d1..0c0b50d6 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java @@ -12,6 +12,7 @@ import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.PictureFormat; +import com.otaliastudios.cameraview.engine.orchestrator.CameraState; import com.otaliastudios.cameraview.frame.FrameManager; import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.controls.Hdr; @@ -24,6 +25,7 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; public class MockCameraEngine extends CameraEngine { @@ -80,8 +82,19 @@ public class MockCameraEngine extends CameraEngine { mPreviewStreamSize = size; } - public void setMockEngineState(boolean started) { - mEngineStep.setState(started ? STATE_STARTED : STATE_STOPPED); + public void setMockState(@NonNull CameraState state) { + Task change = mOrchestrator.scheduleStateChange(getState(), + state, + false, + new Callable>() { + @Override + public Task call() { + return Tasks.forResult(null); + } + }); + try { + Tasks.await(change); + } catch (Exception ignore) {} } @Override diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/GestureFinderTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/GestureFinderTest.java index f7c04d43..cdcc7e8f 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/GestureFinderTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/GestureFinderTest.java @@ -15,7 +15,7 @@ import android.widget.FrameLayout; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.TestActivity; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import org.hamcrest.Matchers; import org.junit.Before; @@ -49,12 +49,12 @@ public abstract class GestureFinderTest extends BaseTes finder.setActive(true); a.inflate(layout); - touchOp = new Op<>(); + touchOp = new Op<>(false); layout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { boolean found = finder.onTouchEvent(motionEvent); - if (found) touchOp.end(finder.getGesture()); + if (found) touchOp.controller().end(finder.getGesture()); return true; } }); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/PinchGestureFinderTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/PinchGestureFinderTest.java index 74cc7252..58bc5865 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/PinchGestureFinderTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/PinchGestureFinderTest.java @@ -6,7 +6,7 @@ import androidx.test.espresso.ViewAction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.otaliastudios.cameraview.runner.SdkExclude; +import com.otaliastudios.cameraview.tools.SdkExclude; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,7 +52,7 @@ public class PinchGestureFinderTest extends GestureFinderTest { @Test public void testTap() { touchOp.listen(); - touchOp.start(); + touchOp.controller().start(); GeneralClickAction a = new GeneralClickAction( Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER, InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); @@ -62,7 +62,7 @@ public class TapGestureFinderTest extends GestureFinderTest { public void testTapWhileDisabled() { finder.setActive(false); touchOp.listen(); - touchOp.start(); + touchOp.controller().start(); onLayout().perform(click()); Gesture found = touchOp.await(500); assertNull(found); @@ -71,7 +71,7 @@ public class TapGestureFinderTest extends GestureFinderTest { @Test public void testLongTap() { touchOp.listen(); - touchOp.start(); + touchOp.controller().start(); GeneralClickAction a = new GeneralClickAction( Tap.LONG, GeneralLocation.CENTER, Press.FINGER, InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/GridLinesLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/GridLinesLayoutTest.java index 5323d66f..389b8a88 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/GridLinesLayoutTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/GridLinesLayoutTest.java @@ -4,7 +4,9 @@ package com.otaliastudios.cameraview.internal; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.TestActivity; import com.otaliastudios.cameraview.controls.Grid; +import com.otaliastudios.cameraview.tools.Op; +import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; import androidx.test.rule.ActivityTestRule; @@ -15,6 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; @RunWith(AndroidJUnit4.class) @MediumTest @@ -25,6 +28,14 @@ public class GridLinesLayoutTest extends BaseTest { private GridLinesLayout layout; + @NonNull + private Op getDrawOp() { + final Op op = new Op<>(); + layout.callback = mock(GridLinesLayout.DrawCallback.class); + doEndOp(op, 0).when(layout.callback).onDraw(anyInt()); + return op; + } + @Before public void setUp() { uiSync(new Runnable() { @@ -33,18 +44,17 @@ public class GridLinesLayoutTest extends BaseTest { TestActivity a = rule.getActivity(); layout = new GridLinesLayout(a); layout.setGridMode(Grid.OFF); - layout.drawOp.listen(); + Op op = getDrawOp(); a.getContentView().addView(layout); + op.await(1000); } }); - // Wait for first draw. - layout.drawOp.await(1000); } private int setGridAndWait(Grid value) { - layout.drawOp.listen(); layout.setGridMode(value); - Integer result = layout.drawOp.await(1000); + Op op = getDrawOp(); + Integer result = op.await(1000); assertNotNull(result); return result; } @@ -52,25 +62,25 @@ public class GridLinesLayoutTest extends BaseTest { @Test public void testOff() { int linesDrawn = setGridAndWait(Grid.OFF); - assertEquals(linesDrawn, 0); + assertEquals(0, linesDrawn); } @Test public void test3x3() { int linesDrawn = setGridAndWait(Grid.DRAW_3X3); - assertEquals(linesDrawn, 2); + assertEquals(2, linesDrawn); } @Test public void testPhi() { int linesDrawn = setGridAndWait(Grid.DRAW_PHI); - assertEquals(linesDrawn, 2); + assertEquals(2, linesDrawn); } @Test public void test4x4() { int linesDrawn = setGridAndWait(Grid.DRAW_4X4); - assertEquals(linesDrawn, 3); + assertEquals(3, linesDrawn); } } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java index e8960e5b..169e1c48 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java @@ -8,7 +8,7 @@ import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.CameraUtils; -import com.otaliastudios.cameraview.runner.SdkExclude; +import com.otaliastudios.cameraview.tools.SdkExclude; import com.otaliastudios.cameraview.size.Size; import org.junit.Test; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/ImageHelperTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/ImageHelperTest.java index f884a467..a75f8438 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/ImageHelperTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/ImageHelperTest.java @@ -18,11 +18,11 @@ import android.view.Surface; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseTest; -import com.otaliastudios.cameraview.runner.SdkExclude; +import com.otaliastudios.cameraview.tools.Op; +import com.otaliastudios.cameraview.tools.SdkExclude; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,12 +45,12 @@ public class ImageHelperTest extends BaseTest { private Image getImage() { ImageReader reader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1); Surface readerSurface = reader.getSurface(); - final Op imageOp = new Op<>(true); + final Op imageOp = new Op<>(); reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage(); - if (image != null) imageOp.end(image); + if (image != null) imageOp.controller().end(image); } }, new Handler(Looper.getMainLooper())); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java index fd5349be..5a733217 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java @@ -4,6 +4,7 @@ package com.otaliastudios.cameraview.internal.utils; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Tasks; import com.otaliastudios.cameraview.BaseTest; +import com.otaliastudios.cameraview.tools.Op; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -13,7 +14,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -43,7 +43,7 @@ public class WorkerHandlerTest extends BaseTest { return new Runnable() { @Override public void run() { - op.end(true); + op.controller().end(true); } }; } @@ -53,7 +53,7 @@ public class WorkerHandlerTest extends BaseTest { return new Callable() { @Override public Boolean call() { - op.end(true); + op.controller().end(true); return true; } }; @@ -77,7 +77,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testFallbackExecute() { - final Op op = new Op<>(true); + final Op op = new Op<>(); WorkerHandler.execute(getRunnableForOp(op)); waitOp(op); } @@ -85,7 +85,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testPostRunnable() { WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.post(getRunnableForOp(op)); waitOp(op); } @@ -93,7 +93,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testPostCallable() { WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.post(getCallableForOp(op)); waitOp(op); } @@ -110,7 +110,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testRunRunnable_background() { WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.run(getRunnableForOp(op)); waitOp(op); } @@ -118,14 +118,14 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testRunRunnable_sameThread() { final WorkerHandler handler = WorkerHandler.get("handler"); - final Op op1 = new Op<>(true); - final Op op2 = new Op<>(true); + final Op op1 = new Op<>(); + final Op op2 = new Op<>(); handler.post(new Runnable() { @Override public void run() { handler.run(getRunnableForOp(op2)); assertTrue(op2.await(0)); // Do not wait. - op1.end(true); + op1.controller().end(true); } }); waitOp(op1); @@ -134,7 +134,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testRunCallable_background() { WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.run(getCallableForOp(op)); waitOp(op); } @@ -142,14 +142,14 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testRunCallable_sameThread() { final WorkerHandler handler = WorkerHandler.get("handler"); - final Op op1 = new Op<>(true); - final Op op2 = new Op<>(true); + final Op op1 = new Op<>(); + final Op op2 = new Op<>(); handler.post(new Runnable() { @Override public void run() { handler.run(getCallableForOp(op2)); assertTrue(op2.await(0)); // Do not wait. - op1.end(true); + op1.controller().end(true); } }); waitOp(op1); @@ -158,14 +158,14 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testRunCallable_sameThread_throws() { final WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.post(new Runnable() { @Override public void run() { Task task = handler.run(getThrowCallable()); assertTrue(task.isComplete()); // Already complete assertFalse(task.isSuccessful()); - op.end(true); + op.controller().end(true); } }); waitOp(op); @@ -174,7 +174,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testPostDelayed_tooEarly() { final WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.post(1000, getRunnableForOp(op)); assertNull(op.await(500)); } @@ -182,7 +182,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testPostDelayed() { final WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); handler.post(1000, getRunnableForOp(op)); assertNotNull(op.await(2000)); } @@ -190,7 +190,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testRemove() { final WorkerHandler handler = WorkerHandler.get("handler"); - final Op op = new Op<>(true); + final Op op = new Op<>(); Runnable runnable = getRunnableForOp(op); handler.post(1000, runnable); handler.remove(runnable); @@ -210,7 +210,7 @@ public class WorkerHandlerTest extends BaseTest { public void testExecutor() { final WorkerHandler handler = WorkerHandler.get("handler"); Executor executor = handler.getExecutor(); - final Op op = new Op<>(true); + final Op op = new Op<>(); executor.execute(getRunnableForOp(op)); waitOp(op); } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/markers/MarkerLayoutTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/markers/MarkerLayoutTest.java index c203932f..9220a938 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/markers/MarkerLayoutTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/markers/MarkerLayoutTest.java @@ -9,7 +9,7 @@ import android.view.ViewGroup; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.TestActivity; -import com.otaliastudios.cameraview.runner.SdkExclude; +import com.otaliastudios.cameraview.tools.SdkExclude; import org.junit.Assert; import org.junit.Before; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/CameraPreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/CameraPreviewTest.java index 2142eae9..b2d3d064 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/CameraPreviewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/CameraPreviewTest.java @@ -7,7 +7,7 @@ import android.view.ViewGroup; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.TestActivity; -import com.otaliastudios.cameraview.internal.utils.Op; +import com.otaliastudios.cameraview.tools.Op; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; @@ -42,8 +42,8 @@ public abstract class CameraPreviewTest extends BaseTes @Before public void setUp() { - available = new Op<>(true); - destroyed = new Op<>(true); + available = new Op<>(); + destroyed = new Op<>(); uiSync(new Runnable() { @Override @@ -55,7 +55,7 @@ public abstract class CameraPreviewTest extends BaseTes doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { - if (available != null) available.end(true); + if (available != null) available.controller().end(true); return null; } }).when(callback).onSurfaceAvailable(); @@ -63,7 +63,7 @@ public abstract class CameraPreviewTest extends BaseTes doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) { - if (destroyed != null) destroyed.end(true); + if (destroyed != null) destroyed.controller().end(true); return null; } }).when(callback).onSurfaceDestroyed(); @@ -153,17 +153,23 @@ public abstract class CameraPreviewTest extends BaseTes // Since desired is 'desired', let's fake a new view size that is consistent with it. // Ensure crop is not happening anymore. - preview.mCropOp.listen(); - preview.dispatchOnSurfaceSizeChanged((int) (50f * desired), 50); // Wait... - preview.mCropOp.await(); + preview.mCropCallback = mock(CameraPreview.CropCallback.class); + Op op = new Op<>(); + doEndOp(op, null).when(preview.mCropCallback).onCrop(); + preview.dispatchOnSurfaceSizeChanged((int) (50f * desired), 50); + + op.await(); // Wait... assertEquals(desired, getViewAspectRatioWithScale(), 0.01f); assertFalse(preview.isCropping()); } private void setDesiredAspectRatio(float desiredAspectRatio) { - preview.mCropOp.listen(); - preview.setStreamSize((int) (10f * desiredAspectRatio), 10); // Wait... - preview.mCropOp.await(); + preview.mCropCallback = mock(CameraPreview.CropCallback.class); + Op op = new Op<>(); + doEndOp(op, null).when(preview.mCropCallback).onCrop(); + preview.setStreamSize((int) (10f * desiredAspectRatio), 10); + + op.await(); // Wait... assertEquals(desiredAspectRatio, getViewAspectRatioWithScale(), 0.01f); } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/Op.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/Op.java new file mode 100644 index 00000000..2d81c55f --- /dev/null +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/Op.java @@ -0,0 +1,149 @@ +package com.otaliastudios.cameraview.tools; + +import androidx.annotation.NonNull; + +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.otaliastudios.cameraview.controls.Control; + +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.stubbing.Stubber; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * A naive implementation of {@link java.util.concurrent.CountDownLatch} + * to help in testing. + */ +public class Op { + + public class Controller { + private int mToBeIgnored; + + private Controller() { } + + /** Op owner method: notifies the action started. */ + public void start() { + if (!isListening()) mToBeIgnored++; + } + + /** Op owner method: notifies the action ended. */ + public void end(T result) { + if (mToBeIgnored > 0) { + mToBeIgnored--; + return; + } + + if (isListening()) { // Should be always true. + mResult = result; + mLatch.countDown(); + } + } + + public void from(@NonNull Task task) { + start(); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(T result) { + end(result); + } + }); + } + + @NonNull + public Stubber from(final int invocationArgument) { + return Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + //noinspection unchecked + T o = (T) invocation.getArguments()[invocationArgument]; + start(); + end(o); + return null; + } + }); + } + } + + private CountDownLatch mLatch; + private Controller mController = new Controller(); + private T mResult; + + /** + * Listeners should: + * - call {@link #listen()} to notify they are interested in the next action + * - call {@link #await()} to know when the action is performed. + * + * Op owners should: + * - call {@link Controller#start()} when task started + * - call {@link Controller#end(Object)} when task ends + */ + public Op() { + this(true); + } + + public Op(boolean startListening) { + if (startListening) listen(); + } + + public Op(@NonNull Task task) { + listen(); + controller().from(task); + } + + private boolean isListening() { + return mLatch != null; + } + + /** + * Listener method: notifies we are interested in the next action. + */ + public void listen() { + if (isListening()) throw new RuntimeException("Should not happen."); + mResult = null; + mLatch = new CountDownLatch(1); + } + + /** + * Listener method: waits for next task action to end. + * @param millis milliseconds + * @return the action result + */ + public T await(long millis) { + return await(millis, TimeUnit.MILLISECONDS); + } + + /** + * Listener method: waits 1 minute for next task action to end. + * @return the action result + */ + public T await() { + return await(1, TimeUnit.MINUTES); + } + + /** + * Listener method: waits for next task action to end. + * @param time time + * @param unit the time unit + * @return the action result + */ + private T await(long time, @NonNull TimeUnit unit) { + try { + mLatch.await(time, unit); + } catch (Exception e) { + e.printStackTrace(); + } + T result = mResult; + mResult = null; + mLatch = null; + return result; + } + + @NonNull + public Controller controller() { + return mController; + } +} diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/runner/SdkExclude.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExclude.java similarity index 92% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/runner/SdkExclude.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExclude.java index 7a119a9b..23a2d86e 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/runner/SdkExclude.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExclude.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.runner; +package com.otaliastudios.cameraview.tools; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/runner/SdkExcludeFilter.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExcludeFilter.java similarity index 96% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/runner/SdkExcludeFilter.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExcludeFilter.java index 8dc014ad..4978e74e 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/runner/SdkExcludeFilter.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExcludeFilter.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.runner; +package com.otaliastudios.cameraview.tools; import android.os.Build; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java index 8743858f..3c070464 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java @@ -15,7 +15,6 @@ import android.util.TypedValue; import android.view.View; import com.otaliastudios.cameraview.controls.Grid; -import com.otaliastudios.cameraview.internal.utils.Op; /** * A layout overlay that draws grid lines based on the {@link Grid} parameter. @@ -32,8 +31,11 @@ public class GridLinesLayout extends View { private ColorDrawable vert; private final float width; - @VisibleForTesting - Op drawOp = new Op<>(); + interface DrawCallback { + void onDraw(int lines); + } + + @VisibleForTesting DrawCallback callback; public GridLinesLayout(@NonNull Context context) { this(context, null); @@ -117,7 +119,6 @@ public class GridLinesLayout extends View { @Override protected void onDraw(@NonNull Canvas canvas) { super.onDraw(canvas); - drawOp.start(); int count = getLineCount(); for (int n = 0; n < count; n++) { float pos = getLinePosition(n); @@ -132,6 +133,8 @@ public class GridLinesLayout extends View { vert.draw(canvas); canvas.translate(- pos * getWidth(), 0); } - drawOp.end(count); + if (callback != null) { + callback.onDraw(count); + } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Op.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Op.java deleted file mode 100644 index 0b2bd1d4..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Op.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.otaliastudios.cameraview.internal.utils; - -import androidx.annotation.NonNull; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * A naive implementation of {@link java.util.concurrent.CountDownLatch} - * to help in testing. - */ -public class Op { - - private CountDownLatch mLatch; - private T mResult; - private int mCount; - - /** - * Creates an empty task. - * - * Listeners should: - * - call {@link #listen()} to notify they are interested in the next action - * - call {@link #await()} to know when the action is performed. - * - * Op owners should: - * - call {@link #start()} when task started - * - call {@link #end(Object)} when task ends - */ - public Op() { } - - /** - * Creates an empty task and starts listening. - * @param startListening whether to call listen - */ - public Op(boolean startListening) { - if (startListening) listen(); - } - - private boolean isListening() { - return mLatch != null; - } - - /** - * Op owner method: notifies the action started. - */ - public void start() { - if (!isListening()) mCount++; - } - - /** - * Op owner method: notifies the action ended. - * @param result the action result - */ - public void end(T result) { - if (mCount > 0) { - mCount--; - return; - } - - if (isListening()) { // Should be always true. - mResult = result; - mLatch.countDown(); - } - } - - /** - * Listener method: notifies we are interested in the next action. - */ - public void listen() { - if (isListening()) throw new RuntimeException("Should not happen."); - mResult = null; - mLatch = new CountDownLatch(1); - } - - /** - * Listener method: waits for next task action to end. - * @param millis milliseconds - * @return the action result - */ - public T await(long millis) { - return await(millis, TimeUnit.MILLISECONDS); - } - - /** - * Listener method: waits 1 minute for next task action to end. - * @return the action result - */ - public T await() { - return await(1, TimeUnit.MINUTES); - } - - /** - * Listener method: waits for next task action to end. - * @param time time - * @param unit the time unit - * @return the action result - */ - private T await(long time, @NonNull TimeUnit unit) { - try { - mLatch.await(time, unit); - } catch (Exception e) { - e.printStackTrace(); - } - T result = mResult; - mResult = null; - mLatch = null; - return result; - } - - -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java index 29df062d..deaa9002 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java @@ -18,7 +18,6 @@ import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.engine.CameraEngine; -import com.otaliastudios.cameraview.internal.utils.Op; import com.otaliastudios.cameraview.size.Size; /** @@ -56,8 +55,11 @@ public abstract class CameraPreview { void onSurfaceDestroyed(); } - @VisibleForTesting - Op mCropOp = new Op<>(); + protected interface CropCallback { + void onCrop(); + } + + @VisibleForTesting CropCallback mCropCallback; private SurfaceCallback mSurfaceCallback; private T mView; boolean mCropping; @@ -152,7 +154,7 @@ public abstract class CameraPreview { mInputStreamWidth = width; mInputStreamHeight = height; if (mInputStreamWidth > 0 && mInputStreamHeight > 0) { - crop(mCropOp); + crop(mCropCallback); } } @@ -194,7 +196,7 @@ public abstract class CameraPreview { mOutputSurfaceWidth = width; mOutputSurfaceHeight = height; if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) { - crop(mCropOp); + crop(mCropCallback); } if (mSurfaceCallback != null) { mSurfaceCallback.onSurfaceAvailable(); @@ -213,7 +215,7 @@ public abstract class CameraPreview { mOutputSurfaceWidth = width; mOutputSurfaceHeight = height; if (width > 0 && height > 0) { - crop(mCropOp); + crop(mCropCallback); } if (mSurfaceCallback != null) { mSurfaceCallback.onSurfaceChanged(); @@ -291,12 +293,11 @@ public abstract class CameraPreview { * There might still be some absolute difference (e.g. same ratio but bigger / smaller). * However that should be already managed by the framework. * - * @param op the op + * @param callback the callback */ - protected void crop(@NonNull Op op) { + protected void crop(@Nullable CropCallback callback) { // The base implementation does not support cropping. - op.start(); - op.end(null); + if (callback != null) callback.onCrop(); } /** diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java index ffabc8d4..361113f1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java @@ -5,6 +5,7 @@ import android.graphics.SurfaceTexture; import android.opengl.GLSurfaceView; import android.opengl.Matrix; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import android.view.LayoutInflater; @@ -14,7 +15,6 @@ import android.view.ViewGroup; import com.otaliastudios.cameraview.R; import com.otaliastudios.cameraview.internal.egl.EglViewport; -import com.otaliastudios.cameraview.internal.utils.Op; import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.NoFilter; import com.otaliastudios.cameraview.size.AspectRatio; @@ -270,8 +270,7 @@ public class GlCameraPreview extends FilterCameraPreview op) { - op.start(); + protected void crop(@Nullable final CropCallback callback) { if (mInputStreamWidth > 0 && mInputStreamHeight > 0 && mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) { float scaleX = 1f, scaleY = 1f; @@ -289,7 +288,7 @@ public class GlCameraPreview extends FilterCameraPreview op) { - op.start(); + protected void crop(@Nullable final CropCallback callback) { getView().post(new Runnable() { @Override public void run() { if (mInputStreamHeight == 0 || mInputStreamWidth == 0 || mOutputSurfaceHeight == 0 || mOutputSurfaceWidth == 0) { - op.end(null); + if (callback != null) callback.onCrop(); return; } float scaleX = 1f, scaleY = 1f; @@ -118,7 +116,7 @@ public class TextureCameraPreview extends CameraPreview 1.02f || scaleY > 1.02f; LOG.i("crop:", "applied scaleX=", scaleX); LOG.i("crop:", "applied scaleY=", scaleY); - op.end(null); + if (callback != null) callback.onCrop(); } }); }