Rewrite tests, improve test tools

pull/697/head
Mattia Iavarone 6 years ago
parent 57a4cfba80
commit 7502939561
  1. 2
      cameraview/build.gradle
  2. 16
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java
  3. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraLoggerTest.java
  4. 18
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java
  5. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  6. 18
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  7. 76
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java
  8. 17
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java
  9. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/GestureFinderTest.java
  10. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/PinchGestureFinderTest.java
  11. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/ScrollGestureFinderTest.java
  12. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/TapGestureFinderTest.java
  13. 28
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/GridLinesLayoutTest.java
  14. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java
  15. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/ImageHelperTest.java
  16. 40
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java
  17. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/markers/MarkerLayoutTest.java
  18. 28
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/CameraPreviewTest.java
  19. 149
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/Op.java
  20. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExclude.java
  21. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/tools/SdkExcludeFilter.java
  22. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/GridLinesLayout.java
  23. 111
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Op.java
  24. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java
  25. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  26. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/TextureCameraPreview.java

@ -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 {

@ -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<Object>() {
@Override
public Object answer(InvocationOnMock invocation) {
@ -118,7 +118,7 @@ public class BaseTest {
return doAnswer(new Answer<Object>() {
@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 <T> Stubber doEndOp(final Op<T> op, final int withReturnArgument) {
return doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) {
//noinspection unchecked
T o = (T) invocation.getArguments()[withReturnArgument];
op.end(o);
return null;
}
});
return op.controller().from(withReturnArgument);
}
}

@ -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<Throwable> op = new Op<>();
final Op<Throwable> op = new Op<>(false);
doEndOp(op, 3)
.when(mock)
.log(anyInt(), anyString(), anyString(), any(Throwable.class));

@ -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<String> writeAndReadString(@NonNull String data) {
final File file = new File(getContext().getFilesDir(), "string.txt");
final byte[] bytes = data.getBytes(Charset.forName("UTF-8"));
final Op<String> result = new Op<>(true);
final Op<String> 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<Bitmap> decode = new Op<>(true);
final Op<Bitmap> 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;
}

@ -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<>();
}
});
}

@ -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<Boolean> op = new Op<>(true);
final Op<Boolean> op = new Op<>();
doEndOp(op, true).when(markerLayout).onEvent(MarkerLayout.TYPE_AUTOFOCUS, points);
cameraView.mCameraCallbacks.dispatchOnFocusStart(Gesture.TAP, point);
assertNotNull(op.await(100));

@ -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<CameraOptions> open = new Op<>(true);
final Op<CameraOptions> 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<Boolean> close = new Op<>(true);
final Op<Boolean> 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<VideoResult> video = new Op<>(true);
final Op<VideoResult> video = new Op<>();
doEndOp(video, 0).when(listener).onVideoTaken(any(VideoResult.class));
doEndOp(video, null).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
@Override
@ -218,7 +218,7 @@ public abstract class CameraIntegrationTest extends BaseTest {
@Nullable
private PictureResult waitForPictureResult(boolean expectSuccess) {
final Op<PictureResult> pic = new Op<>(true);
final Op<PictureResult> pic = new Op<>();
doEndOp(pic, 0).when(listener).onPictureTaken(any(PictureResult.class));
doEndOp(pic, null).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
@Override
@ -240,7 +240,7 @@ public abstract class CameraIntegrationTest extends BaseTest {
}
private void takeVideoSync(boolean expectSuccess, int duration) {
final Op<Boolean> op = new Op<>(true);
final Op<Boolean> op = new Op<>();
doEndOp(op, true).when(listener).onVideoRecordingStart();
doEndOp(op, false).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
@Override
@ -269,7 +269,7 @@ public abstract class CameraIntegrationTest extends BaseTest {
}
private void takeVideoSnapshotSync(boolean expectSuccess, int duration) {
final Op<Boolean> op = new Op<>(true);
final Op<Boolean> op = new Op<>();
doEndOp(op, true).when(listener).onVideoRecordingStart();
doEndOp(op, false).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
@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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<Void> 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<PointF> focus = new Op<>(true);
final Op<PointF> 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<PointF> focus = new Op<>(true);
final Op<PointF> 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)) {

@ -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<Void> change = mOrchestrator.scheduleStateChange(getState(),
state,
false,
new Callable<Task<Void>>() {
@Override
public Task<Void> call() {
return Tasks.forResult(null);
}
});
try {
Tasks.await(change);
} catch (Exception ignore) {}
}
@Override

@ -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<T extends GestureFinder> 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;
}
});

@ -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<PinchGestureFinder
private void testPinch(ViewAction action, boolean increasing) {
touchOp.listen();
touchOp.start();
touchOp.controller().start();
onLayout().perform(action);
Gesture found = touchOp.await(10000);
assertNotNull(found);

@ -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;
@ -49,7 +49,7 @@ public class ScrollGestureFinderTest extends GestureFinderTest<ScrollGestureFind
public void testScrollDisabled() {
finder.setActive(false);
touchOp.listen();
touchOp.start();
touchOp.controller().start();
onLayout().perform(swipeUp());
Gesture found = touchOp.await(WAIT);
assertNull(found);
@ -57,7 +57,7 @@ public class ScrollGestureFinderTest extends GestureFinderTest<ScrollGestureFind
private void testScroll(ViewAction scroll, Gesture expected, boolean increasing) {
touchOp.listen();
touchOp.start();
touchOp.controller().start();
onLayout().perform(scroll);
Gesture found = touchOp.await(WAIT);
assertEquals(found, expected);

@ -11,7 +11,7 @@ import androidx.test.filters.SmallTest;
import android.view.InputDevice;
import android.view.MotionEvent;
import com.otaliastudios.cameraview.runner.SdkExclude;
import com.otaliastudios.cameraview.tools.SdkExclude;
import com.otaliastudios.cameraview.size.Size;
import org.junit.Test;
@ -45,7 +45,7 @@ public class TapGestureFinderTest extends GestureFinderTest<TapGestureFinder> {
@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<TapGestureFinder> {
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<TapGestureFinder> {
@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);

@ -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<Integer> getDrawOp() {
final Op<Integer> 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<Integer> 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<Integer> 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);
}
}

@ -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;

@ -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<Image> imageOp = new Op<>(true);
final Op<Image> 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()));

@ -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<Boolean>() {
@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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op1 = new Op<>(true);
final Op<Boolean> op2 = new Op<>(true);
final Op<Boolean> op1 = new Op<>();
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op1 = new Op<>(true);
final Op<Boolean> op2 = new Op<>(true);
final Op<Boolean> op1 = new Op<>();
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> op = new Op<>();
handler.post(new Runnable() {
@Override
public void run() {
Task<Void> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> 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<Boolean> op = new Op<>(true);
final Op<Boolean> op = new Op<>();
executor.execute(getRunnableForOp(op));
waitOp(op);
}

@ -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;

@ -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<T extends CameraPreview> 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<T extends CameraPreview> 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<T extends CameraPreview> 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<T extends CameraPreview> 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<Void> 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<Void> op = new Op<>();
doEndOp(op, null).when(preview.mCropCallback).onCrop();
preview.setStreamSize((int) (10f * desiredAspectRatio), 10);
op.await(); // Wait...
assertEquals(desiredAspectRatio, getViewAspectRatioWithScale(), 0.01f);
}

@ -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<T> {
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<T> task) {
start();
task.addOnSuccessListener(new OnSuccessListener<T>() {
@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<T> 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;
}
}

@ -1,4 +1,4 @@
package com.otaliastudios.cameraview.runner;
package com.otaliastudios.cameraview.tools;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

@ -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<Integer> 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);
}
}
}

@ -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<T> {
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;
}
}

@ -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<T extends View, Output> {
void onSurfaceDestroyed();
}
@VisibleForTesting
Op<Void> 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<T extends View, Output> {
mInputStreamWidth = width;
mInputStreamHeight = height;
if (mInputStreamWidth > 0 && mInputStreamHeight > 0) {
crop(mCropOp);
crop(mCropCallback);
}
}
@ -194,7 +196,7 @@ public abstract class CameraPreview<T extends View, Output> {
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<T extends View, Output> {
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<T extends View, Output> {
* 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<Void> op) {
protected void crop(@Nullable CropCallback callback) {
// The base implementation does not support cropping.
op.start();
op.end(null);
if (callback != null) callback.onCrop();
}
/**

@ -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<GLSurfaceView, SurfaceT
* See {@link Renderer#onDrawFrame(GL10)}.
*/
@Override
protected void crop(@NonNull Op<Void> 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<GLSurfaceView, SurfaceT
mCropScaleY = 1F / scaleY;
getView().requestRender();
}
op.end(null);
if (callback != null) callback.onCrop();
}
/**

@ -17,7 +17,6 @@ import android.view.ViewGroup;
import com.google.android.gms.tasks.TaskCompletionSource;
import com.google.android.gms.tasks.Tasks;
import com.otaliastudios.cameraview.R;
import com.otaliastudios.cameraview.internal.utils.Op;
import com.otaliastudios.cameraview.size.AspectRatio;
import java.util.concurrent.ExecutionException;
@ -91,14 +90,13 @@ public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceText
}
@Override
protected void crop(final @NonNull Op<Void> 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<TextureView, SurfaceText
mCropping = scaleX > 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();
}
});
}

Loading…
Cancel
Save