pull/37/head
Mattia Iavarone 8 years ago
parent ef3672e8d6
commit 9065e195fd
  1. 25
      .travis.yml
  2. 48
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java
  3. 57
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  4. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java

@ -39,29 +39,34 @@ install:
- export EMULATOR="system-images;android-$EMULATOR_API;$EMULATOR_TAG;$EMULATOR_ABI"
- echo yes | sdkmanager "platforms;android-$EMULATOR_API" # Install sdk
- echo yes | sdkmanager "$EMULATOR" # Install system image
- sdkmanager --list || true # Check everything is updated
# Create adn start emulator
- echo no | avdmanager create avd -n test -k "$EMULATOR" -f # Create emulator
- emulator -avd test -no-window -camera-back emulated -camera-front emulated -memory 2048 & # Launch
- which emulator # ensure we are using the right emulator (home/emulator/)
- emulator -avd test -no-window -camera-back emulated -camera-front emulated -memory 2048 -writable-system & # Launch
- adb wait-for-device # Wait for adb process
- sdkmanager --list || true # Check everything is updated
- adb remount # Mount as writable
before_script:
# Wait for emulator
- android-wait-for-emulator # Wait for emulator ready to interact
- adb shell settings put global window_animation_scale 0 & # Disable animations
- adb shell settings put global transition_animation_scale 0 & # Disable animations
- adb shell settings put global animator_duration_scale 0 & # Disable animations
# Unlock and configure logs.
# Would be great to use -v color to adb logcat but looks not supported on travis.
- sleep 20 # Sleep 20 seconds just in case
- adb shell input keyevent 82 & # Dispatch event
- adb shell input keyevent 82 & # Dispatch unlock event
- adb logcat -c # Clear logcat until now
- adb logcat Test:V TestRunner:V CameraView:V CameraController:V Camera1:V WorkerHandler:V *:E &
# - export LOGCAT_PID=$! # Save PID of the logcat process. Should kill later with kill $LOGCAT_PID
script:
- ./gradlew clean testDebugUnitTest
- adb logcat -c
- adb logcat -v color Test:V TestRunner:V CameraView:V CameraController:V Camera1:V WorkerHandler:V *:S &
- export LOGCAT_PID=$!
- ./gradlew connectedCheck
- kill $LOGCAT_PID
- ./gradlew mergedCoverageReport
- ./gradlew clean testDebugUnitTest connectedCheck mergedCoverageReport
after_success:
- bash <(curl -s https://codecov.io/bash) -s "*/build/reports/mergedCoverageReport/"

@ -1,11 +1,13 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.ViewGroup;
import org.junit.After;
import org.junit.Before;
@ -103,7 +105,7 @@ public class IntegrationTest extends BaseTest {
final Task<byte[]> pic = new Task<>();
pic.listen();
doEndTask(pic, 0).when(listener).onPictureTaken(any(byte[].class));
byte[] result = pic.await(2000);
byte[] result = pic.await(5000);
if (expectSuccess) {
assertNotNull("Can take picture", result);
} else {
@ -143,7 +145,7 @@ public class IntegrationTest extends BaseTest {
}
@Test
// TODO: This works great on the device but crashes on the emulator.
// This works great on the device but crashes often on the emulator.
// There must be something wrong with the emulated camera...
// Like stopPreview() and release() are not really sync calls?
public void testConcurrentCalls() throws Exception {
@ -160,7 +162,7 @@ public class IntegrationTest extends BaseTest {
assertTrue("Handles concurrent calls to start & stop, " + latch.getCount(), did);
}
//-@Test
@Test
public void testStartInitializesOptions() {
assertNull(camera.getCameraOptions());
assertNull(camera.getExtraProperties());
@ -175,7 +177,7 @@ public class IntegrationTest extends BaseTest {
//region test Facing/SessionType
// Test things that should reset the camera.
//-@Test
@Test
public void testSetFacing() throws Exception {
camera.start();
CameraOptions o = waitForOpen(true);
@ -193,7 +195,7 @@ public class IntegrationTest extends BaseTest {
}
}
//-@Test
@Test
public void testSetSessionType() throws Exception {
camera.setSessionType(SessionType.PICTURE);
camera.start();
@ -216,7 +218,7 @@ public class IntegrationTest extends BaseTest {
//region test Set Parameters
// When camera is open, parameters will be set only if supported.
//-@Test
@Test
public void testSetZoom() {
camera.start();
CameraOptions options = waitForOpen(true);
@ -227,7 +229,7 @@ public class IntegrationTest extends BaseTest {
assertEquals(can ? newValue : oldValue, camera.getZoom(), 0f);
}
//-@Test
@Test
public void testSetExposureCorrection() {
camera.start();
CameraOptions options = waitForOpen(true);
@ -238,7 +240,7 @@ public class IntegrationTest extends BaseTest {
assertEquals(can ? newValue : oldValue, camera.getExposureCorrection(), 0f);
}
//-@Test
@Test
public void testSetFlash() {
camera.start();
CameraOptions options = waitForOpen(true);
@ -255,7 +257,7 @@ public class IntegrationTest extends BaseTest {
}
}
//-@Test
@Test
public void testSetWhiteBalance() {
camera.start();
CameraOptions options = waitForOpen(true);
@ -272,7 +274,7 @@ public class IntegrationTest extends BaseTest {
}
}
//-@Test
@Test
public void testSetHdr() {
camera.start();
CameraOptions options = waitForOpen(true);
@ -289,7 +291,7 @@ public class IntegrationTest extends BaseTest {
}
}
//-@Test
@Test
public void testSetAudio() {
// TODO: when permissions are managed, check that Audio.ON triggers the audio permission
camera.start();
@ -301,7 +303,7 @@ public class IntegrationTest extends BaseTest {
}
}
//-@Test
@Test
public void testSetLocation() {
camera.start();
waitForOpen(true);
@ -329,7 +331,7 @@ public class IntegrationTest extends BaseTest {
camera.setVideoQuality(VideoQuality.LOWEST);
}
//-@Test
@Test
public void testSetVideoQuality_whileInPictureSessionType() {
camera.setSessionType(SessionType.PICTURE);
camera.setVideoQuality(VideoQuality.HIGHEST);
@ -339,7 +341,7 @@ public class IntegrationTest extends BaseTest {
assertEquals(camera.getVideoQuality(), VideoQuality.LOWEST);
}
//-@Test
@Test
public void testSetVideoQuality_whileNotStarted() {
camera.setVideoQuality(VideoQuality.HIGHEST);
assertEquals(camera.getVideoQuality(), VideoQuality.HIGHEST);
@ -347,7 +349,7 @@ public class IntegrationTest extends BaseTest {
assertEquals(camera.getVideoQuality(), VideoQuality.LOWEST);
}
//-@Test
@Test
public void testSetVideoQuality_shouldRecompute() {
// If video quality changes bring to a new capture size,
// this might bring to a new aspect ratio,
@ -382,7 +384,7 @@ public class IntegrationTest extends BaseTest {
waitForVideo(true); // waits 2000
}
//-@Test
@Test
public void testEndVideo_withoutStarting() {
camera.setSessionType(SessionType.VIDEO);
camera.start();
@ -396,7 +398,7 @@ public class IntegrationTest extends BaseTest {
//region startAutoFocus
// TODO: won't test onStopAutoFocus because that is not guaranteed to be called
//-@Test
@Test
public void testStartAutoFocus() {
camera.start();
CameraOptions o = waitForOpen(true);
@ -412,13 +414,13 @@ public class IntegrationTest extends BaseTest {
//region capture
//-@Test
@Test
public void testCapturePicture_beforeStarted() {
camera.capturePicture();
waitForPicture(false);
}
//-@Test
@Test
public void testCapturePicture_concurrentCalls() throws Exception {
// Second take should fail.
camera.start();
@ -434,7 +436,7 @@ public class IntegrationTest extends BaseTest {
assertEquals(latch.getCount(), 1);
}
//-@Test
@Test
public void testCapturePicture_size() throws Exception {
camera.setCropOutput(false);
camera.start();
@ -450,13 +452,13 @@ public class IntegrationTest extends BaseTest {
assertTrue(b.getHeight() == size.getHeight() || b.getHeight() == size.getWidth());
}
//-@Test
@Test
public void testCaptureSnapshot_beforeStarted() {
camera.captureSnapshot();
waitForPicture(false);
}
//-@Test
@Test
public void testCaptureSnapshot_concurrentCalls() throws Exception {
// Second take should fail.
camera.start();
@ -472,7 +474,7 @@ public class IntegrationTest extends BaseTest {
assertEquals(latch.getCount(), 1);
}
//-@Test
@Test
public void testCaptureSnapshot_size() throws Exception {
camera.setCropOutput(false);
camera.start();

@ -66,15 +66,17 @@ class Camera1 extends CameraController {
*/
@Override
public void onSurfaceAvailable() {
LOG.i("onSurfaceAvailable, size is", mPreview.getSurfaceSize());
LOG.i("onSurfaceAvailable:", "Size is", mPreview.getSurfaceSize());
if (!shouldSetup()) return;
mHandler.post(new Runnable() {
@Override
public void run() {
if (!shouldSetup()) return;
LOG.i("onSurfaceAvailable:", "Inside handler. About to bind.");
try {
setup();
} catch (Exception e) {
LOG.w("onSurfaceAvailable:", "Exception while binding camera to preview.", e);
throw new RuntimeException(e);
}
}
@ -92,10 +94,13 @@ class Camera1 extends CameraController {
// Compute a new camera preview size.
Size newSize = computePreviewSize();
if (!newSize.equals(mPreviewSize)) {
LOG.i("onSurfaceChanged:", "Computed a new preview size. Dispatching.");
mPreviewSize = newSize;
mCameraCallbacks.onCameraPreviewSizeChanged();
synchronized (mLock) {
LOG.i("onSurfaceChanged:", "Stopping preview.");
mCamera.stopPreview();
LOG.i("onSurfaceChanged:", "Stopped preview.");
Camera.Parameters params = mCamera.getParameters();
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mCamera.setParameters(params);
@ -105,7 +110,9 @@ class Camera1 extends CameraController {
invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(),
invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight()
);
LOG.i("onSurfaceChanged:", "Restarting preview.");
mCamera.startPreview();
LOG.i("onSurfaceChanged:", "Restarted preview.");
}
}
}
@ -118,6 +125,7 @@ class Camera1 extends CameraController {
// These can happen at different times but we want to end up here.
@WorkerThread
private void setup() throws Exception {
LOG.i("setup:", "Started");
Object output = mPreview.getOutput();
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay((SurfaceHolder) output);
@ -128,6 +136,7 @@ class Camera1 extends CameraController {
boolean invertPreviewSizes = shouldFlipSizes();
mCaptureSize = computeCaptureSize();
mPreviewSize = computePreviewSize();
LOG.i("setup:", "Dispatching onCameraPreviewSizeChanged.");
mCameraCallbacks.onCameraPreviewSizeChanged();
mPreview.setDesiredSize(
invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(),
@ -139,8 +148,9 @@ class Camera1 extends CameraController {
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed
mCamera.setParameters(params);
}
LOG.i("setup:", "Starting preview with startPreview().");
mCamera.startPreview();
LOG.i("setup:", "Started preview with startPreview().");
mIsSetup = true;
}
@ -149,6 +159,7 @@ class Camera1 extends CameraController {
@Override
void onStart() throws Exception {
if (isCameraAvailable()) {
LOG.w("onStart:", "Camera not available. Should not happen.");
onStop(); // Should not happen.
}
if (collectCameraId()) {
@ -156,6 +167,7 @@ class Camera1 extends CameraController {
// Set parameters that might have been set before the camera was opened.
synchronized (mLock) {
LOG.i("onStart:", "Applying default parameters.");
Camera.Parameters params = mCamera.getParameters();
mExtraProperties = new ExtraProperties(params);
mOptions = new CameraOptions(params);
@ -170,6 +182,7 @@ class Camera1 extends CameraController {
// Try starting preview.
mCamera.setDisplayOrientation(computeSensorToDisplayOffset()); // <- not allowed during preview
if (shouldSetup()) setup();
LOG.i("onStart:", "Ended");
}
}
@ -177,19 +190,27 @@ class Camera1 extends CameraController {
@Override
void onStop() throws Exception {
Exception error = null;
LOG.i("onStop:", "About to clean up.");
mHandler.get().removeCallbacks(mPostFocusResetRunnable);
if (isCameraAvailable()) {
LOG.i("onStop:", "Clean up.", "Ending video?", mIsCapturingVideo);
if (mIsCapturingVideo) endVideo();
try {
LOG.i("onStop:", "Clean up.", "Stopping preview.");
mCamera.stopPreview();
LOG.i("onStop:", "Clean up.", "Stopped preview.");
} catch (Exception e) {
LOG.w("onStop:", "Clean up.", "Exception while stopping preview.");
error = e;
}
try {
LOG.i("onStop:", "Clean up.", "Releasing camera.");
mCamera.release();
LOG.i("onStop:", "Clean up.", "Released camera.");
} catch (Exception e) {
LOG.w("onStop:", "Clean up.", "Exception while releasing camera.");
error = e;
}
}
@ -392,8 +413,8 @@ class Camera1 extends CameraController {
}
onSurfaceChanged();
}
LOG.i("captureSize: "+mCaptureSize);
LOG.i("previewSize: "+mPreviewSize);
LOG.i("setVideoQuality:", "captureSize:", mCaptureSize);
LOG.i("setVideoQuality:", "previewSize:", mPreviewSize);
}
}
@ -484,8 +505,8 @@ class Camera1 extends CameraController {
@Override
boolean shouldFlipSizes() {
int offset = computeSensorToDisplayOffset();
LOG.i("shouldFlip:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset);
LOG.i("shouldFlip:", "sensorToDisplay=", offset);
LOG.i("shouldFlipSizes:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset);
LOG.i("shouldFlipSizes:", "sensorToDisplay=", offset);
return offset % 180 != 0;
}
@ -545,7 +566,7 @@ class Camera1 extends CameraController {
// Choose the max size.
List<Size> captureSizes = sizesFromList(params.getSupportedPictureSizes());
Size maxSize = Collections.max(captureSizes);
LOG.i("computeCaptureSize:", "computed", maxSize);
LOG.i("size:", "computeCaptureSize:", "computed", maxSize);
return Collections.max(captureSizes);
} else {
// Choose according to developer choice in setVideoQuality.
@ -554,7 +575,7 @@ class Camera1 extends CameraController {
List<Size> captureSizes = sizesFromList(params.getSupportedPictureSizes());
CamcorderProfile profile = getCamcorderProfile(mVideoQuality);
AspectRatio targetRatio = AspectRatio.of(profile.videoFrameWidth, profile.videoFrameHeight);
LOG.i("computeCaptureSize:", "videoQuality:", mVideoQuality, "targetRatio:", targetRatio);
LOG.i("size:", "computeCaptureSize:", "videoQuality:", mVideoQuality, "targetRatio:", targetRatio);
return matchSize(captureSizes, targetRatio, new Size(0, 0), true);
}
}
@ -564,7 +585,7 @@ class Camera1 extends CameraController {
List<Size> previewSizes = sizesFromList(params.getSupportedPreviewSizes());
AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight());
Size biggerThan = mPreview.getSurfaceSize();
LOG.i("computePreviewSize:", "targetRatio:", targetRatio, "surface size:", biggerThan);
LOG.i("size:", "computePreviewSize:", "targetRatio:", targetRatio, "surface size:", biggerThan);
return matchSize(previewSizes, targetRatio, biggerThan, false);
}
@ -586,7 +607,7 @@ class Camera1 extends CameraController {
mMediaRecorder.start();
return true;
} catch (Exception e) {
LOG.e("Error while starting MediaRecorder.", e);
LOG.e("Error while starting MediaRecorder. Swallowing.", e);
mVideoFile = null;
mCamera.lock();
endVideo();
@ -608,7 +629,7 @@ class Camera1 extends CameraController {
} catch (Exception e) {
// This can happen if endVideo() is called right after startVideo().
// We don't care.
LOG.w("Error while closing media recorder.", e);
LOG.w("Error while closing media recorder. Swallowing", e);
}
mMediaRecorder = null;
}
@ -781,8 +802,8 @@ class Camera1 extends CameraController {
double theta = ((double) displayToSensor) * Math.PI / 180;
double sensorClickX = viewClickX * Math.cos(theta) - viewClickY * Math.sin(theta);
double sensorClickY = viewClickX * Math.sin(theta) + viewClickY * Math.cos(theta);
LOG.i("viewClickX:", viewClickX, "viewClickY:", viewClickY);
LOG.i("sensorClickX:", sensorClickX, "sensorClickY:", sensorClickY);
LOG.i("focus:", "viewClickX:", viewClickX, "viewClickY:", viewClickY);
LOG.i("focus:", "sensorClickX:", sensorClickX, "sensorClickY:", sensorClickY);
// Compute the rect bounds.
Rect rect1 = computeMeteringArea(sensorClickX, sensorClickY, 150d);
@ -803,7 +824,7 @@ class Camera1 extends CameraController {
int bottom = (int) Math.min(centerY + delta, 1000);
int left = (int) Math.max(centerX - delta, -1000);
int right = (int) Math.min(centerX + delta, 1000);
LOG.i("metering area:", "top:", top, "left:", left, "bottom:", bottom, "right:", right);
LOG.i("focus:", "computeMeteringArea:", "top:", top, "left:", left, "bottom:", bottom, "right:", right);
return new Rect(left, top, right, bottom);
}
@ -822,7 +843,7 @@ class Camera1 extends CameraController {
for (Camera.Size size : sizes) {
result.add(new Size(size.width, size.height));
}
LOG.i("sizesFromList:", result);
LOG.i("size:", "sizesFromList:", result);
return result;
}
@ -852,8 +873,8 @@ class Camera1 extends CameraController {
}
}
LOG.i("matchSize:", "found consistent:", consistent.size());
LOG.i("matchSize:", "found big enough and consistent:", bigEnoughAndConsistent.size());
LOG.i("size:", "matchSize:", "found consistent:", consistent.size());
LOG.i("size:", "matchSize:", "found big enough and consistent:", bigEnoughAndConsistent.size());
Size result;
if (biggestPossible) {
if (bigEnoughAndConsistent.size() > 0) {
@ -872,7 +893,7 @@ class Camera1 extends CameraController {
result = Collections.max(sizes);
}
}
LOG.i("matchSize:", "returning result", result);
LOG.i("size", "matchSize:", "returning result", result);
return result;
}

@ -53,7 +53,9 @@ abstract class CameraController implements Preview.SurfaceCallback {
public void uncaughtException(Thread thread, Throwable throwable) {
// Something went wrong. Thread is terminated (about to?).
// Move to other thread and stop resources.
LOG.w("Interrupting thread, due to exception.", throwable);
thread.interrupt();
LOG.w("Interrupted thread. Posting a stopImmediately.", ss());
mHandler = WorkerHandler.get("CameraViewController");
mHandler.post(new Runnable() {
@Override
@ -67,16 +69,29 @@ abstract class CameraController implements Preview.SurfaceCallback {
//region Start&Stop
private String ss() {
switch (mState) {
case STATE_STOPPING: return "STATE_STOPPING";
case STATE_STOPPED: return "STATE_STOPPED";
case STATE_STARTING: return "STATE_STARTING";
case STATE_STARTED: return "STATE_STARTED";
}
return "null";
}
// Starts the preview asynchronously.
final void start() {
LOG.i("Start:", "posting runnable. State:", ss());
mHandler.post(new Runnable() {
@Override
public void run() {
try {
int a = mState;
LOG.i("Start:", "executing. State:", ss());
if (mState >= STATE_STARTING) return;
mState = STATE_STARTING;
LOG.i("Start:", "about to call onStart()", ss());
onStart();
LOG.i("Start:", "returned from onStart().", "Dispatching.", ss());
mState = STATE_STARTED;
mCameraCallbacks.dispatchOnCameraOpened(mOptions);
@ -90,14 +105,17 @@ abstract class CameraController implements Preview.SurfaceCallback {
// Stops the preview asynchronously.
final void stop() {
LOG.i("Stop:", "posting runnable. State:", ss());
mHandler.post(new Runnable() {
@Override
public void run() {
try {
int a = mState;
LOG.i("Stop:", "executing. State:", ss());
if (mState <= STATE_STOPPED) return;
mState = STATE_STOPPING;
LOG.i("Stop:", "about to call onStop()");
onStop();
LOG.i("Stop:", "returned from onStop().", "Dispatching.");
mState = STATE_STOPPED;
mCameraCallbacks.dispatchOnCameraClosed();
@ -113,31 +131,40 @@ abstract class CameraController implements Preview.SurfaceCallback {
void stopImmediately() {
try {
// Don't check, try stop again.
LOG.i("Stop immediately. State was:", ss());
mState = STATE_STOPPING;
onStop();
mState = STATE_STOPPED;
LOG.i("Stop immediately. Stopped. State is:", ss());
} catch (Exception e) {
// Do nothing.
LOG.i("Stop immediately. Exception while stopping.", e);
mState = STATE_STOPPED;
}
}
// Forces a restart.
protected final void restart() {
LOG.i("Restart:", "posting runnable");
mHandler.post(new Runnable() {
@Override
public void run() {
try {
LOG.i("Restart:", "executing. Needs stopping:", mState > STATE_STOPPED, ss());
// Don't stop if stopped.
if (mState > STATE_STOPPED) {
mState = STATE_STOPPING;
onStop();
mState = STATE_STOPPED;
LOG.i("Restart:", "stopped. Dispatching.", ss());
mCameraCallbacks.dispatchOnCameraClosed();
}
LOG.i("Restart: about to start. State:", ss());
mState = STATE_STARTING;
onStart();
mState = STATE_STARTED;
LOG.i("Restart: returned from start. Dispatching. State:", ss());
mCameraCallbacks.dispatchOnCameraOpened(mOptions);
} catch (Exception e) {

Loading…
Cancel
Save