From 90f8325185d4d57c3751d51402eb027d5054e52b Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Mon, 13 Nov 2017 17:36:43 +0100 Subject: [PATCH] Schedule everything to the same handler --- .../cameraview/CameraCallbacksTest.java | 6 +- .../cameraview/CameraUtilsTest.java | 3 +- .../cameraview/IntegrationTest.java | 82 ++- .../otaliastudios/cameraview/PreviewTest.java | 3 +- .../cameraview/WorkerHandlerTest.java | 3 +- .../com/otaliastudios/cameraview/Camera1.java | 680 ++++++++++-------- .../cameraview/CameraController.java | 20 +- .../cameraview/CameraListener.java | 5 +- .../com/otaliastudios/cameraview/Task.java | 4 + 9 files changed, 448 insertions(+), 358 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java index eb1ff0ca..26add023 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java @@ -73,8 +73,7 @@ public class CameraCallbacksTest extends BaseTest { camera.instantiatePreview(); camera.addCameraListener(listener); camera.addFrameProcessor(processor); - task = new Task<>(); - task.listen(); + task = new Task<>(true); } }); } @@ -266,8 +265,7 @@ public class CameraCallbacksTest extends BaseTest { private int[] testProcessImage(boolean jpeg, boolean crop, int[] viewDim, int[] imageDim) { // End our task when onPictureTaken is called. Take note of the result. - final Task jpegTask = new Task<>(); - jpegTask.listen(); + final Task jpegTask = new Task<>(true); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java index b678e0ce..7961e376 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraUtilsTest.java @@ -48,8 +48,7 @@ public class CameraUtilsTest extends BaseTest { source.compress(Bitmap.CompressFormat.PNG, 100, os); final byte[] data = os.toByteArray(); - final Task decode = new Task<>(); - decode.listen(); + final Task decode = new Task<>(true); final CameraUtils.BitmapCallback callback = new CameraUtils.BitmapCallback() { @Override public void onBitmapReady(Bitmap bitmap) { diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java index 9d196b5a..45e21884 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java @@ -33,7 +33,7 @@ import static org.junit.Assert.*; */ @RunWith(AndroidJUnit4.class) @MediumTest -@Ignore +// @Ignore public class IntegrationTest extends BaseTest { @Rule @@ -77,8 +77,7 @@ public class IntegrationTest extends BaseTest { } private CameraOptions waitForOpen(boolean expectSuccess) { - final Task open = new Task<>(); - open.listen(); + final Task open = new Task<>(true); doEndTask(open, 0).when(listener).onCameraOpened(any(CameraOptions.class)); CameraOptions result = open.await(4000); if (expectSuccess) { @@ -89,9 +88,8 @@ public class IntegrationTest extends BaseTest { return result; } - private Boolean waitForClose(boolean expectSuccess) { - final Task close = new Task<>(); - close.listen(); + private void waitForClose(boolean expectSuccess) { + final Task close = new Task<>(true); doEndTask(close, true).when(listener).onCameraClosed(); Boolean result = close.await(4000); if (expectSuccess) { @@ -99,12 +97,10 @@ public class IntegrationTest extends BaseTest { } else { assertNull("Should not close", result); } - return result; } - private Boolean waitForVideo(boolean expectSuccess) { - final Task video = new Task<>(); - video.listen(); + private void waitForVideo(boolean expectSuccess) { + final Task video = new Task<>(true); doEndTask(video, true).when(listener).onVideoTaken(any(File.class)); Boolean result = video.await(2000); if (expectSuccess) { @@ -112,12 +108,10 @@ public class IntegrationTest extends BaseTest { } else { assertNull("Should not take video", result); } - return result; } private byte[] waitForPicture(boolean expectSuccess) { - final Task pic = new Task<>(); - pic.listen(); + final Task pic = new Task<>(true); doEndTask(pic, 0).when(listener).onPictureTaken(any(byte[].class)); byte[] result = pic.await(5000); if (expectSuccess) { @@ -130,7 +124,7 @@ public class IntegrationTest extends BaseTest { //region test open/close - //-@Test + @Test public void testOpenClose() throws Exception { // Starting and stopping are hard to get since they happen on another thread. assertEquals(controller.getState(), CameraController.STATE_STOPPED); @@ -144,7 +138,7 @@ public class IntegrationTest extends BaseTest { assertEquals(controller.getState(), CameraController.STATE_STOPPED); } - //-@Test + @Test public void testOpenTwice() { camera.start(); waitForOpen(true); @@ -152,7 +146,7 @@ public class IntegrationTest extends BaseTest { waitForOpen(false); } - //-@Test + @Test public void testCloseTwice() { camera.stop(); waitForClose(false); @@ -236,22 +230,36 @@ public class IntegrationTest extends BaseTest { public void testSetZoom() { camera.start(); CameraOptions options = waitForOpen(true); - boolean can = options.isZoomSupported(); + + controller.mZoomTask.listen(); float oldValue = camera.getZoom(); float newValue = 0.65f; camera.setZoom(newValue); - assertEquals(can ? newValue : oldValue, camera.getZoom(), 0f); + controller.mZoomTask.await(500); + + if (options.isZoomSupported()) { + assertEquals(newValue, camera.getZoom(), 0f); + } else { + assertEquals(oldValue, camera.getZoom(), 0f); + } } @Test public void testSetExposureCorrection() { camera.start(); CameraOptions options = waitForOpen(true); - boolean can = options.isExposureCorrectionSupported(); + + controller.mExposureCorrectionTask.listen(); float oldValue = camera.getExposureCorrection(); float newValue = options.getExposureCorrectionMaxValue(); camera.setExposureCorrection(newValue); - assertEquals(can ? newValue : oldValue, camera.getExposureCorrection(), 0f); + controller.mExposureCorrectionTask.await(300); + + if (options.isExposureCorrectionSupported()) { + assertEquals(newValue, camera.getExposureCorrection(), 0f); + } else { + assertEquals(oldValue, camera.getExposureCorrection(), 0f); + } } @Test @@ -261,7 +269,9 @@ public class IntegrationTest extends BaseTest { Flash[] values = Flash.values(); Flash oldValue = camera.getFlash(); for (Flash value : values) { + controller.mFlashTask.listen(); camera.setFlash(value); + controller.mFlashTask.await(300); if (options.supports(value)) { assertEquals(camera.getFlash(), value); oldValue = value; @@ -278,7 +288,9 @@ public class IntegrationTest extends BaseTest { WhiteBalance[] values = WhiteBalance.values(); WhiteBalance oldValue = camera.getWhiteBalance(); for (WhiteBalance value : values) { + controller.mWhiteBalanceTask.listen(); camera.setWhiteBalance(value); + controller.mWhiteBalanceTask.await(300); if (options.supports(value)) { assertEquals(camera.getWhiteBalance(), value); oldValue = value; @@ -295,7 +307,9 @@ public class IntegrationTest extends BaseTest { Hdr[] values = Hdr.values(); Hdr oldValue = camera.getHdr(); for (Hdr value : values) { + controller.mHdrTask.listen(); camera.setHdr(value); + controller.mHdrTask.await(300); if (options.supports(value)) { assertEquals(camera.getHdr(), value); oldValue = value; @@ -321,7 +335,9 @@ public class IntegrationTest extends BaseTest { public void testSetLocation() { camera.start(); waitForOpen(true); + controller.mLocationTask.listen(); camera.setLocation(10d, 2d); + controller.mLocationTask.await(300); assertNotNull(camera.getLocation()); assertEquals(camera.getLocation().getLatitude(), 10d, 0d); assertEquals(camera.getLocation().getLongitude(), 2d, 0d); @@ -333,16 +349,18 @@ public class IntegrationTest extends BaseTest { //region testSetVideoQuality // This can be tricky because can trigger layout changes. - // TODO: @Test(expected = IllegalStateException.class) - // Can't run on Travis, MediaRecorder not supported. - // Error while starting MediaRecorder. java.lang.RuntimeException: start failed. + // TODO: the exception is swallowed. @Test(expected = IllegalStateException.class) public void testSetVideoQuality_whileRecording() { + // Can't run on Travis, MediaRecorder not supported. + // Error while starting MediaRecorder. java.lang.RuntimeException: start failed. camera.setSessionType(SessionType.VIDEO); camera.setVideoQuality(VideoQuality.HIGHEST); camera.start(); waitForOpen(true); camera.startCapturingVideo(null); + controller.mVideoQualityTask.listen(); camera.setVideoQuality(VideoQuality.LOWEST); + controller.mVideoQualityTask.await(300); } @Test @@ -351,20 +369,28 @@ public class IntegrationTest extends BaseTest { camera.setVideoQuality(VideoQuality.HIGHEST); camera.start(); waitForOpen(true); + controller.mVideoQualityTask.listen(); camera.setVideoQuality(VideoQuality.LOWEST); + controller.mVideoQualityTask.await(300); assertEquals(camera.getVideoQuality(), VideoQuality.LOWEST); } @Test public void testSetVideoQuality_whileNotStarted() { + controller.mVideoQualityTask.listen(); camera.setVideoQuality(VideoQuality.HIGHEST); + controller.mVideoQualityTask.await(300); assertEquals(camera.getVideoQuality(), VideoQuality.HIGHEST); + + controller.mVideoQualityTask.listen(); camera.setVideoQuality(VideoQuality.LOWEST); + controller.mVideoQualityTask.await(300); assertEquals(camera.getVideoQuality(), VideoQuality.LOWEST); } @Test public void testSetVideoQuality_shouldRecompute() { + // TODO: // If video quality changes bring to a new capture size, // this might bring to a new aspect ratio, // which might bring to a new preview size. No idea how to test. @@ -416,11 +442,17 @@ public class IntegrationTest extends BaseTest { public void testStartAutoFocus() { camera.start(); CameraOptions o = waitForOpen(true); + + final Task focus = new Task<>(true); + doEndTask(focus, 0).when(listener).onFocusStart(any(PointF.class)); + camera.startAutoFocus(1, 1); + PointF point = focus.await(300); if (o.isAutoFocusSupported()) { - verify(listener, times(1)).onFocusStart(new PointF(1, 1)); + assertNotNull(point); + assertEquals(point, new PointF(1, 1)); } else { - verify(listener, never()).onFocusStart(any(PointF.class)); + assertNull(point); } } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java index e5fa7f9d..eb1fbf88 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java @@ -31,8 +31,7 @@ public abstract class PreviewTest extends BaseTest { @Before public void setUp() { - availability = new Task<>(); - availability.listen(); + availability = new Task<>(true); ui(new Runnable() { @Override diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java index 01383933..44e5e6b7 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/WorkerHandlerTest.java @@ -24,8 +24,7 @@ public class WorkerHandlerTest extends BaseTest { @Test public void testStaticRun() { - final Task task = new Task<>(); - task.listen(); + final Task task = new Task<>(true); Runnable action = new Runnable() { @Override public void run() { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 9f727797..1d50fb96 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -38,98 +38,103 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { private Runnable mPostFocusResetRunnable = new Runnable() { @Override public void run() { - synchronized (mLock) { - if (!isCameraAvailable()) return; - mCamera.cancelAutoFocus(); - Camera.Parameters params = mCamera.getParameters(); - params.setFocusAreas(null); - params.setMeteringAreas(null); - applyDefaultFocus(params); // Revert to internal focus. - mCamera.setParameters(params); - } + if (!isCameraAvailable()) return; + mCamera.cancelAutoFocus(); + Camera.Parameters params = mCamera.getParameters(); + params.setFocusAreas(null); + params.setMeteringAreas(null); + applyDefaultFocus(params); // Revert to internal focus. + mCamera.setParameters(params); } }; - private Mapper mMapper = new Mapper.Mapper1(); - private boolean mIsSetup = false; + private ScheduleRunnable mScheduleRunnable; + private Mapper mMapper; + private boolean mIsBound = false; private boolean mIsCapturingImage = false; private boolean mIsCapturingVideo = false; - Camera1(CameraView.CameraCallbacks callback) { super(callback); + mScheduleRunnable = new ScheduleRunnable(); + mMapper = new Mapper.Mapper1(); } - /** - * Preview surface is now available. If camera is open, set up. - */ + private class ScheduleRunnable implements Runnable { + private Task mTask; + private Runnable mAction; + private boolean mEnsureAvailable; + + @Override + public void run() { + if (mEnsureAvailable && !isCameraAvailable()) { + if (mTask != null) mTask.end(null); + } else { + mAction.run(); + if (mTask != null) mTask.end(null); + } + } + } + + private void schedule(@Nullable Task task, boolean ensureAvailable, Runnable action) { + mScheduleRunnable.mEnsureAvailable = ensureAvailable; + mScheduleRunnable.mAction = action; + mScheduleRunnable.mTask = task; + mHandler.post(mScheduleRunnable); + } + + // Preview surface is now available. If camera is open, set up. @Override public void onSurfaceAvailable() { LOG.i("onSurfaceAvailable:", "Size is", mPreview.getSurfaceSize()); - if (!shouldSetup()) return; - mHandler.post(new Runnable() { + schedule(null, false, 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); + if (shouldBindToSurface()) { + LOG.i("onSurfaceAvailable:", "Inside handler. About to bind."); + try { + bindToSurface(); + } catch (Exception e) { + LOG.e("onSurfaceAvailable:", "Exception while binding camera to preview.", e); + throw new RuntimeException(e); + } } } }); } - /** - * Preview surface did change its size. Compute a new preview size. - * This requires stopping and restarting the preview. - */ + // Preview surface did change its size. Compute a new preview size. + // This requires stopping and restarting the preview. @Override public void onSurfaceChanged() { LOG.i("onSurfaceChanged, size is", mPreview.getSurfaceSize()); - if (mIsSetup && isCameraAvailable()) { - // 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); - } - boolean invertPreviewSizes = shouldFlipSizes(); - mPreview.setDesiredSize( - invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(), - invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight() - ); + schedule(null, true, new Runnable() { + @Override + public void run() { + if (!mIsBound) return; - mCamera.setPreviewCallbackWithBuffer(null); // This clears the buffers - mCamera.setPreviewCallbackWithBuffer(this); // Reset - mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewSize); + // Compute a new camera preview size. + Size newSize = computePreviewSize(); + if (newSize.equals(mPreviewSize)) return; - LOG.i("onSurfaceChanged:", "Restarting preview."); - mCamera.startPreview(); - LOG.i("onSurfaceChanged:", "Restarted preview."); + // Apply. + LOG.i("onSurfaceChanged:", "Computed a new preview size. Going on."); + mPreviewSize = newSize; + mCamera.stopPreview(); + applySizesAndStartPreview("onSurfaceChanged:"); } - } + }); } - private boolean shouldSetup() { - return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsSetup; + private boolean shouldBindToSurface() { + return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsBound; } // The act of binding an "open" camera to a "ready" preview. // These can happen at different times but we want to end up here. @WorkerThread - private void setup() throws Exception { - LOG.i("setup:", "Started"); + private void bindToSurface() throws Exception { + LOG.i("bindToSurface:", "Started"); Object output = mPreview.getOutput(); if (mPreview.getOutputClass() == SurfaceHolder.class) { mCamera.setPreviewDisplay((SurfaceHolder) output); @@ -137,34 +142,38 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { mCamera.setPreviewTexture((SurfaceTexture) output); } - boolean invertPreviewSizes = shouldFlipSizes(); mCaptureSize = computeCaptureSize(); mPreviewSize = computePreviewSize(); - LOG.i("setup:", "Dispatching onCameraPreviewSizeChanged."); + applySizesAndStartPreview("bindToSurface:"); + mIsBound = true; + } + + // To be called when the preview size is setup or changed. + private void applySizesAndStartPreview(String log) { + LOG.i(log, "Dispatching onCameraPreviewSizeChanged."); mCameraCallbacks.onCameraPreviewSizeChanged(); + + boolean invertPreviewSizes = shouldFlipSizes(); mPreview.setDesiredSize( invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(), invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight() ); - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - mPreviewFormat = params.getPreviewFormat(); - params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview - params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed - mCamera.setParameters(params); - } + + Camera.Parameters params = mCamera.getParameters(); + mPreviewFormat = params.getPreviewFormat(); + params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview + params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed + mCamera.setParameters(params); mCamera.setPreviewCallbackWithBuffer(null); // Release anything left mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewSize); - LOG.i("setup:", "Starting preview with startPreview()."); + LOG.i(log, "Starting preview with startPreview()."); mCamera.startPreview(); - LOG.i("setup:", "Started preview with startPreview()."); - mIsSetup = true; + LOG.i(log, "Started preview."); } - @WorkerThread @Override void onStart() throws Exception { @@ -176,22 +185,21 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { mCamera = Camera.open(mCameraId); // 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); - applyDefaultFocus(params); - mergeFlash(params, Flash.DEFAULT); - mergeLocation(params, null); - mergeWhiteBalance(params, WhiteBalance.DEFAULT); - params.setRecordingHint(mSessionType == SessionType.VIDEO); - mCamera.setParameters(params); - } + LOG.i("onStart:", "Applying default parameters."); + Camera.Parameters params = mCamera.getParameters(); + mExtraProperties = new ExtraProperties(params); + mOptions = new CameraOptions(params); + applyDefaultFocus(params); + mergeFlash(params, Flash.DEFAULT); + mergeLocation(params, null); + mergeWhiteBalance(params, WhiteBalance.DEFAULT); + mergeHdr(params, Hdr.DEFAULT); + params.setRecordingHint(mSessionType == SessionType.VIDEO); + mCamera.setParameters(params); // Try starting preview. mCamera.setDisplayOrientation(computeSensorToDisplayOffset()); // <- not allowed during preview - if (shouldSetup()) setup(); + if (shouldBindToSurface()) bindToSurface(); LOG.i("onStart:", "Ended"); } } @@ -205,7 +213,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { mFrameManager.release(); if (mCamera != null) { - LOG.i("onStop:", "Clean up.", "Ending video?", mIsCapturingVideo); if (mIsCapturingVideo) endVideo(); @@ -233,7 +240,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { mCamera = null; mPreviewSize = null; mCaptureSize = null; - mIsSetup = false; + mIsBound = false; if (error != null) throw error; } @@ -254,6 +261,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override public void onBufferAvailable(byte[] buffer) { + // TODO: sync with handler? if (isCameraAvailable()) { mCamera.addCallbackBuffer(buffer); } @@ -263,22 +271,26 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { void setSessionType(SessionType sessionType) { if (sessionType != mSessionType) { mSessionType = sessionType; - if (isCameraAvailable()) { - restart(); - } + schedule(null, true, new Runnable() { + @Override + public void run() { + restart(); + } + }); } } @Override void setLocation(Location location) { - Location oldLocation = mLocation; + final Location oldLocation = mLocation; mLocation = location; - if (isCameraAvailable()) { - synchronized (mLock) { + schedule(mLocationTask, true, new Runnable() { + @Override + public void run() { Camera.Parameters params = mCamera.getParameters(); if (mergeLocation(params, oldLocation)) mCamera.setParameters(params); } - } + }); } private boolean mergeLocation(Camera.Parameters params, Location oldLocation) { @@ -301,22 +313,28 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { void setFacing(Facing facing) { if (facing != mFacing) { mFacing = facing; - if (collectCameraId() && isCameraAvailable()) { - restart(); - } + schedule(null, true, new Runnable() { + @Override + public void run() { + if (collectCameraId()) { + restart(); + } + } + }); } } @Override void setWhiteBalance(WhiteBalance whiteBalance) { - WhiteBalance old = mWhiteBalance; + final WhiteBalance old = mWhiteBalance; mWhiteBalance = whiteBalance; - if (isCameraAvailable()) { - synchronized (mLock) { + schedule(mWhiteBalanceTask, true, new Runnable() { + @Override + public void run() { Camera.Parameters params = mCamera.getParameters(); if (mergeWhiteBalance(params, old)) mCamera.setParameters(params); } - } + }); } private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) { @@ -330,14 +348,15 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override void setHdr(Hdr hdr) { - Hdr old = mHdr; + final Hdr old = mHdr; mHdr = hdr; - if (isCameraAvailable()) { - synchronized (mLock) { + schedule(mHdrTask, true, new Runnable() { + @Override + public void run() { Camera.Parameters params = mCamera.getParameters(); if (mergeHdr(params, old)) mCamera.setParameters(params); } - } + }); } private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) { @@ -354,7 +373,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { void setAudio(Audio audio) { if (mAudio != audio) { if (mIsCapturingVideo) { - LOG.w("Changing audio mode while recording. Changes will take place starting from next video"); + LOG.w("Audio setting was changed while recording. " + + "Changes will take place starting from next video"); } mAudio = audio; } @@ -362,14 +382,15 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override void setFlash(Flash flash) { - Flash old = mFlash; + final Flash old = mFlash; mFlash = flash; - if (isCameraAvailable()) { - synchronized (mLock) { + schedule(mFlashTask, true, new Runnable() { + @Override + public void run() { Camera.Parameters params = mCamera.getParameters(); if (mergeFlash(params, old)) mCamera.setParameters(params); } - } + }); } @@ -412,124 +433,131 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override void setVideoQuality(VideoQuality videoQuality) { - if (mIsCapturingVideo) { - // TODO: actually any call to getParameters() could fail while recording a video. - // See. https://stackoverflow.com/questions/14941625/correct-handling-of-exception-getparameters-failed-empty-parameters - throw new IllegalStateException("Can't change video quality while recording a video."); - } - + final VideoQuality old = mVideoQuality; mVideoQuality = videoQuality; - if (isCameraAvailable() && mSessionType == SessionType.VIDEO) { - // Change capture size to a size that fits the video aspect ratio. - Size oldSize = mCaptureSize; - mCaptureSize = computeCaptureSize(); - if (!mCaptureSize.equals(oldSize)) { - // New video quality triggers a new aspect ratio. - // Go on and see if preview size should change also. - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); - mCamera.setParameters(params); + schedule(mVideoQualityTask, true, new Runnable() { + @Override + public void run() { + if (mIsCapturingVideo) { + // TODO: actually any call to getParameters() could fail while recording a video. + // See. https://stackoverflow.com/questions/14941625/ + mVideoQuality = old; + throw new IllegalStateException("Can't change video quality while recording a video."); + } + + if (mSessionType == SessionType.VIDEO) { + // Change capture size to a size that fits the video aspect ratio. + Size oldSize = mCaptureSize; + mCaptureSize = computeCaptureSize(); + if (!mCaptureSize.equals(oldSize)) { + // New video quality triggers a new aspect ratio. + // Go on and see if preview size should change also. + Camera.Parameters params = mCamera.getParameters(); + params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); + mCamera.setParameters(params); + onSurfaceChanged(); + } + LOG.i("setVideoQuality:", "captureSize:", mCaptureSize); + LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); } - onSurfaceChanged(); } - LOG.i("setVideoQuality:", "captureSize:", mCaptureSize); - LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); - } + }); } @Override void capturePicture() { - if (mIsCapturingImage) return; - if (!isCameraAvailable()) return; - if (mSessionType == SessionType.VIDEO && mIsCapturingVideo) { - if (!mOptions.isVideoSnapshotSupported()) return; - } + schedule(null, true, new Runnable() { + @Override + public void run() { + if (mIsCapturingImage) return; + if (mSessionType == SessionType.VIDEO && mIsCapturingVideo) { + if (!mOptions.isVideoSnapshotSupported()) return; + } - // Set boolean to wait for image callback - mIsCapturingImage = true; - final int exifRotation = computeExifRotation(); - final boolean exifFlip = computeExifFlip(); - final int sensorToDisplay = computeSensorToDisplayOffset(); - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - params.setRotation(exifRotation); - mCamera.setParameters(params); - } - // Is the final picture (decoded respecting EXIF) consistent with CameraView orientation? - // We must consider exifOrientation to bring back the picture in the sensor world. - // Then use sensorToDisplay to move to the display world, where CameraView lives. - final boolean consistentWithView = (exifRotation + sensorToDisplay + 180) % 180 == 0; - mCamera.takePicture( - new Camera.ShutterCallback() { - @Override - public void onShutter() { - mCameraCallbacks.onShutter(false); - } - }, - null, - null, - new Camera.PictureCallback() { - @Override - public void onPictureTaken(byte[] data, final Camera camera) { - mIsCapturingImage = false; - mHandler.post(new Runnable() { + // Set boolean to wait for image callback + mIsCapturingImage = true; + final int exifRotation = computeExifRotation(); + final boolean exifFlip = computeExifFlip(); + final int sensorToDisplay = computeSensorToDisplayOffset(); + Camera.Parameters params = mCamera.getParameters(); + params.setRotation(exifRotation); + mCamera.setParameters(params); + + // Is the final picture (decoded respecting EXIF) consistent with CameraView orientation? + // We must consider exifOrientation to bring back the picture in the sensor world. + // Then use sensorToDisplay to move to the display world, where CameraView lives. + final boolean consistentWithView = (exifRotation + sensorToDisplay + 180) % 180 == 0; + mCamera.takePicture( + new Camera.ShutterCallback() { @Override - public void run() { - // This is needed, read somewhere in the docs. - camera.startPreview(); + public void onShutter() { + mCameraCallbacks.onShutter(false); } - }); - mCameraCallbacks.processImage(data, consistentWithView, exifFlip); - } - }); + }, + null, + null, + new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, final Camera camera) { + mIsCapturingImage = false; + mCameraCallbacks.processImage(data, consistentWithView, exifFlip); + camera.startPreview(); // This is needed, read somewhere in the docs. + } + } + ); + } + }); } @Override void captureSnapshot() { - if (!isCameraAvailable()) return; - if (mIsCapturingImage) return; - // This won't work while capturing a video. - // Switch to capturePicture. - if (mIsCapturingVideo) { - capturePicture(); - return; - } - mIsCapturingImage = true; - mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { + schedule(null, true, new Runnable() { @Override - public void onPreviewFrame(final byte[] data, Camera camera) { - mCameraCallbacks.onShutter(true); - - // Got to rotate the preview frame, since byte[] data here does not include - // EXIF tags automatically set by camera. So either we add EXIF, or we rotate. - // Adding EXIF to a byte array, unfortunately, is hard. - final int sensorToDevice = computeExifRotation(); - final int sensorToDisplay = computeSensorToDisplayOffset(); - final boolean exifFlip = computeExifFlip(); - final boolean flip = sensorToDevice % 180 != 0; - final int preWidth = mPreviewSize.getWidth(); - final int preHeight = mPreviewSize.getHeight(); - final int postWidth = flip ? preHeight : preWidth; - final int postHeight = flip ? preWidth : preHeight; - final int format = mPreviewFormat; - WorkerHandler.run(new Runnable() { + public void run() { + if (mIsCapturingImage) return; + // This won't work while capturing a video. + // Switch to capturePicture. + if (mIsCapturingVideo) { + capturePicture(); + return; + } + mIsCapturingImage = true; + mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { @Override - public void run() { + public void onPreviewFrame(final byte[] data, Camera camera) { + mCameraCallbacks.onShutter(true); + + // Got to rotate the preview frame, since byte[] data here does not include + // EXIF tags automatically set by camera. So either we add EXIF, or we rotate. + // Adding EXIF to a byte array, unfortunately, is hard. + final int sensorToDevice = computeExifRotation(); + final int sensorToDisplay = computeSensorToDisplayOffset(); + final boolean exifFlip = computeExifFlip(); + final boolean flip = sensorToDevice % 180 != 0; + final int preWidth = mPreviewSize.getWidth(); + final int preHeight = mPreviewSize.getHeight(); + final int postWidth = flip ? preHeight : preWidth; + final int postHeight = flip ? preWidth : preHeight; + final int format = mPreviewFormat; + WorkerHandler.run(new Runnable() { + @Override + public void run() { + + final boolean consistentWithView = (sensorToDevice + sensorToDisplay + 180) % 180 == 0; + byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToDevice); + YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); + mCameraCallbacks.processSnapshot(yuv, consistentWithView, exifFlip); + mIsCapturingImage = false; + } + }); - final boolean consistentWithView = (sensorToDevice + sensorToDisplay + 180) % 180 == 0; - byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToDevice); - YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); - mCameraCallbacks.processSnapshot(yuv, consistentWithView, exifFlip); - mIsCapturingImage = false; + // It seems that the buffers are already cleared here, so we need to allocate again. + mCamera.setPreviewCallbackWithBuffer(null); // Release anything left + mCamera.setPreviewCallbackWithBuffer(Camera1.this); // Add ourselves + mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewSize); } }); - - // It seems that the buffers are already cleared here, so we need to allocate again. - mCamera.setPreviewCallbackWithBuffer(null); // Release anything left - mCamera.setPreviewCallbackWithBuffer(Camera1.this); // Add ourselves - mFrameManager.allocate(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewSize); } }); } @@ -559,12 +587,11 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { // If we are going to be closed, don't act on camera. // Even if mCamera != null, it might have been released. case STATE_STOPPING: return false; - // If we are started, act as long as there is no stop/restart scheduled. - // At this point mCamera should never be null. - case STATE_STARTED: return !mScheduledForStop && !mScheduledForRestart; + // If we are started, mCamera should never be null. + case STATE_STARTED: return true; // If we are starting, theoretically we could act. // Just check that camera is available. - case STATE_STARTING: return mCamera != null && !mScheduledForStop && !mScheduledForRestart; + case STATE_STARTING: return mCamera != null; } return false; } @@ -628,7 +655,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { // The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc. // We want the picture size to be the max picture consistent with the video aspect ratio. List captureSizes = sizesFromList(params.getSupportedPictureSizes()); - CamcorderProfile profile = getCamcorderProfile(mVideoQuality); + CamcorderProfile profile = getCamcorderProfile(mCameraId, mVideoQuality); AspectRatio targetRatio = AspectRatio.of(profile.videoFrameWidth, profile.videoFrameHeight); LOG.i("size:", "computeCaptureSize:", "videoQuality:", mVideoQuality, "targetRatio:", targetRatio); return matchSize(captureSizes, targetRatio, new Size(0, 0), true); @@ -650,29 +677,43 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override - void startVideo(@NonNull File videoFile) { - if (mIsCapturingVideo) return; - if (!isCameraAvailable()) return; - if (mSessionType == SessionType.VIDEO) { - mVideoFile = videoFile; - mIsCapturingVideo = true; - initMediaRecorder(); - try { - mMediaRecorder.prepare(); - mMediaRecorder.start(); - } catch (Exception e) { - LOG.e("Error while starting MediaRecorder. Swallowing.", e); - mVideoFile = null; - mCamera.lock(); - endVideo(); + void startVideo(@NonNull final File videoFile) { + schedule(null, true, new Runnable() { + @Override + public void run() { + if (mIsCapturingVideo) return; + if (mSessionType == SessionType.VIDEO) { + mVideoFile = videoFile; + mIsCapturingVideo = true; + initMediaRecorder(); + try { + mMediaRecorder.prepare(); + mMediaRecorder.start(); + } catch (Exception e) { + LOG.e("Error while starting MediaRecorder. Swallowing.", e); + mVideoFile = null; + mCamera.lock(); + endVideoNow(); + } + } else { + throw new IllegalStateException("Can't record video while session type is picture"); + } } - } else { - throw new IllegalStateException("Can't record video while session type is picture"); - } + }); } @Override void endVideo() { + schedule(null, false, new Runnable() { + @Override + public void run() { + endVideoNow(); + } + }); + } + + @WorkerThread + private void endVideoNow() { if (mIsCapturingVideo) { mIsCapturingVideo = false; if (mMediaRecorder != null) { @@ -693,7 +734,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { } } - + @WorkerThread private void initMediaRecorder() { mMediaRecorder = new MediaRecorder(); mCamera.unlock(); @@ -701,7 +742,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - CamcorderProfile profile = getCamcorderProfile(mVideoQuality); + CamcorderProfile profile = getCamcorderProfile(mCameraId, mVideoQuality); if (mAudio == Audio.ON) { mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setProfile(profile); @@ -724,48 +765,47 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); } - @NonNull - private CamcorderProfile getCamcorderProfile(VideoQuality videoQuality) { + private static CamcorderProfile getCamcorderProfile(int cameraId, VideoQuality videoQuality) { switch (videoQuality) { case HIGHEST: - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); case MAX_2160P: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_2160P); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P); } // Don't break. case MAX_1080P: - if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P)) { - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_1080P); + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); } // Don't break. case MAX_720P: - if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P)) { - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_720P); + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); } // Don't break. case MAX_480P: - if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P)) { - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_480P); + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); } // Don't break. case MAX_QVGA: - if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA)) { - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_QVGA); + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA); } // Don't break. case LOWEST: default: // Fallback to lowest. - return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_LOW); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); } } @@ -774,42 +814,48 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override - void setZoom(float zoom, PointF[] points, boolean notify) { - if (!isCameraAvailable()) return; - if (!mOptions.isZoomSupported()) return; + void setZoom(final float zoom, final PointF[] points, final boolean notify) { + schedule(mZoomTask, true, new Runnable() { + @Override + public void run() { + if (!mOptions.isZoomSupported()) return; - mZoomValue = zoom; - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - float max = params.getMaxZoom(); - params.setZoom((int) (zoom * max)); - mCamera.setParameters(params); - } + mZoomValue = zoom; + Camera.Parameters params = mCamera.getParameters(); + float max = params.getMaxZoom(); + params.setZoom((int) (zoom * max)); + mCamera.setParameters(params); - if (notify) { - mCameraCallbacks.dispatchOnZoomChanged(zoom, points); - } + if (notify) { + mCameraCallbacks.dispatchOnZoomChanged(zoom, points); + } + } + }); } @Override - void setExposureCorrection(float EVvalue, float[] bounds, PointF[] points, boolean notify) { - if (!isCameraAvailable()) return; - if (!mOptions.isExposureCorrectionSupported()) return; - - float max = mOptions.getExposureCorrectionMaxValue(); - float min = mOptions.getExposureCorrectionMinValue(); - EVvalue = EVvalue < min ? min : EVvalue > max ? max : EVvalue; // cap - mExposureCorrectionValue = EVvalue; - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - int indexValue = (int) (EVvalue / params.getExposureCompensationStep()); - params.setExposureCompensation(indexValue); - mCamera.setParameters(params); - } + void setExposureCorrection(final float EVvalue, final float[] bounds, + final PointF[] points, final boolean notify) { + schedule(mExposureCorrectionTask, true, new Runnable() { + @Override + public void run() { + if (!mOptions.isExposureCorrectionSupported()) return; - if (notify) { - mCameraCallbacks.dispatchOnExposureCorrectionChanged(EVvalue, bounds, points); - } + float value = EVvalue; + float max = mOptions.getExposureCorrectionMaxValue(); + float min = mOptions.getExposureCorrectionMinValue(); + value = value < min ? min : value > max ? max : value; // cap + mExposureCorrectionValue = value; + Camera.Parameters params = mCamera.getParameters(); + int indexValue = (int) (value / params.getExposureCompensationStep()); + params.setExposureCompensation(indexValue); + mCamera.setParameters(params); + + if (notify) { + mCameraCallbacks.dispatchOnExposureCorrectionChanged(value, bounds, points); + } + } + }); } // ----------------- @@ -817,44 +863,58 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { @Override - void startAutoFocus(@Nullable final Gesture gesture, PointF point) { - if (!isCameraAvailable()) return; - if (!mOptions.isAutoFocusSupported()) return; - final PointF p = new PointF(point.x, point.y); // copy. - List meteringAreas2 = computeMeteringAreas(p.x, p.y); - List meteringAreas1 = meteringAreas2.subList(0, 1); - synchronized (mLock) { - // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent(). - Camera.Parameters params = mCamera.getParameters(); - int maxAF = params.getMaxNumFocusAreas(); - int maxAE = params.getMaxNumMeteringAreas(); - if (maxAF > 0) params.setFocusAreas(maxAF > 1 ? meteringAreas2 : meteringAreas1); - if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1); - params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); - mCamera.setParameters(params); - mCameraCallbacks.dispatchOnFocusStart(gesture, p); - // TODO this is not guaranteed to be called... Fix. - mCamera.autoFocus(new Camera.AutoFocusCallback() { - @Override - public void onAutoFocus(boolean success, Camera camera) { - // TODO lock auto exposure and white balance for a while - mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); - mHandler.get().removeCallbacks(mPostFocusResetRunnable); - mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay); - } - }); - } + void startAutoFocus(@Nullable final Gesture gesture, final PointF point) { + // Must get width and height from the UI thread. + int viewWidth = 0, viewHeight = 0; + if (mPreview != null && mPreview.isReady()) { + viewWidth = mPreview.getView().getWidth(); + viewHeight = mPreview.getView().getHeight(); + } + final int viewWidthF = viewWidth; + final int viewHeightF = viewHeight; + // Schedule. + schedule(null, true, new Runnable() { + @Override + public void run() { + if (!mOptions.isAutoFocusSupported()) return; + final PointF p = new PointF(point.x, point.y); // copy. + List meteringAreas2 = computeMeteringAreas(p.x, p.y, + viewWidthF, viewHeightF, computeSensorToDisplayOffset()); + List meteringAreas1 = meteringAreas2.subList(0, 1); + + // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent(). + Camera.Parameters params = mCamera.getParameters(); + int maxAF = params.getMaxNumFocusAreas(); + int maxAE = params.getMaxNumMeteringAreas(); + if (maxAF > 0) params.setFocusAreas(maxAF > 1 ? meteringAreas2 : meteringAreas1); + if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1); + params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + mCamera.setParameters(params); + mCameraCallbacks.dispatchOnFocusStart(gesture, p); + // TODO this is not guaranteed to be called... Fix. + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + // TODO lock auto exposure and white balance for a while + mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); + mHandler.get().removeCallbacks(mPostFocusResetRunnable); + mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay); + } + }); + } + }); } - private List computeMeteringAreas(double viewClickX, double viewClickY) { + @WorkerThread + private static List computeMeteringAreas(double viewClickX, double viewClickY, + int viewWidth, int viewHeight, + int sensorToDisplay) { // Event came in view coordinates. We must rotate to sensor coordinates. // First, rescale to the -1000 ... 1000 range. - int displayToSensor = -computeSensorToDisplayOffset(); - double viewWidth = mPreview.getView().getWidth(); - double viewHeight = mPreview.getView().getHeight(); - viewClickX = -1000d + (viewClickX / viewWidth) * 2000d; - viewClickY = -1000d + (viewClickY / viewHeight) * 2000d; + int displayToSensor = -sensorToDisplay; + viewClickX = -1000d + (viewClickX / (double) viewWidth) * 2000d; + viewClickY = -1000d + (viewClickY / (double) viewHeight) * 2000d; // Apply rotation to this point. // https://academo.org/demos/rotation-about-point/ @@ -877,7 +937,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback { } - private Rect computeMeteringArea(double centerX, double centerY, double size) { + private static Rect computeMeteringArea(double centerX, double centerY, double size) { double delta = size / 2d; int top = (int) Math.max(centerY - delta, -1000); int bottom = (int) Math.min(centerY + delta, 1000); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 979c8b58..404420c0 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -43,15 +43,19 @@ abstract class CameraController implements CameraPreview.SurfaceCallback, FrameM protected int mDisplayOffset; protected int mDeviceOrientation; - - protected boolean mScheduledForStart = false; - protected boolean mScheduledForStop = false; - protected boolean mScheduledForRestart = false; protected int mState = STATE_STOPPED; - protected final Object mLock = new Object(); protected WorkerHandler mHandler; + // Used for testing. + Task mZoomTask = new Task<>(); + Task mExposureCorrectionTask = new Task<>(); + Task mFlashTask = new Task<>(); + Task mWhiteBalanceTask = new Task<>(); + Task mHdrTask = new Task<>(); + Task mLocationTask = new Task<>(); + Task mVideoQualityTask = new Task<>(); + CameraController(CameraView.CameraCallbacks callback) { mCameraCallbacks = callback; mHandler = WorkerHandler.get("CameraViewController"); @@ -95,13 +99,11 @@ abstract class CameraController implements CameraPreview.SurfaceCallback, FrameM // Starts the preview asynchronously. final void start() { LOG.i("Start:", "posting runnable. State:", ss()); - mScheduledForStart = true; mHandler.post(new Runnable() { @Override public void run() { try { LOG.i("Start:", "executing. State:", ss()); - mScheduledForStart = false; if (mState >= STATE_STARTING) return; mState = STATE_STARTING; LOG.i("Start:", "about to call onStart()", ss()); @@ -121,13 +123,11 @@ abstract class CameraController implements CameraPreview.SurfaceCallback, FrameM // Stops the preview asynchronously. final void stop() { LOG.i("Stop:", "posting runnable. State:", ss()); - mScheduledForStop = true; mHandler.post(new Runnable() { @Override public void run() { try { LOG.i("Stop:", "executing. State:", ss()); - mScheduledForStop = false; if (mState <= STATE_STOPPED) return; mState = STATE_STOPPING; LOG.i("Stop:", "about to call onStop()"); @@ -165,13 +165,11 @@ abstract class CameraController implements CameraPreview.SurfaceCallback, FrameM // Forces a restart. protected final void restart() { LOG.i("Restart:", "posting runnable"); - mScheduledForRestart = true; mHandler.post(new Runnable() { @Override public void run() { try { LOG.i("Restart:", "executing. Needs stopping:", mState > STATE_STOPPED, ss()); - mScheduledForRestart = false; // Don't stop if stopped. if (mState > STATE_STOPPED) { mState = STATE_STOPPING; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java index 1da71188..7e5a6ddb 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java @@ -78,8 +78,8 @@ public abstract class CameraListener { /** * Notifies that user interacted with the screen and started focus with a gesture, - * and the autofocus is trying to focus around that area. - * This can be used to draw things on screen. + * and the autofocus is trying to focus around that area. This can be used to draw things on screen. + * Can also be triggered by {@link CameraView#startAutoFocus(float, float)}. * * @param point coordinates with respect to CameraView.getWidth() and CameraView.getHeight() */ @@ -93,6 +93,7 @@ public abstract class CameraListener { * Notifies that a gesture focus event just ended, and the camera converged * to a new focus (and possibly exposure and white balance). * This might succeed or not. + * Can also be triggered by {@link CameraView#startAutoFocus(float, float)}. * * @param successful whether camera succeeded * @param point coordinates with respect to CameraView.getWidth() and CameraView.getHeight() diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java index aa2a92e0..c7ea012f 100644 --- a/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java +++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/Task.java @@ -18,6 +18,10 @@ class Task { Task() { } + Task(boolean startListening) { + if (startListening) listen(); + } + private boolean listening() { return mLatch != null; }