diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java index e3896661..4a1aec1c 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java @@ -314,11 +314,11 @@ public abstract class CameraIntegrationTest extends Base @Test public void testOpenClose() { - assertEquals(controller.getState(), CameraState.OFF); + assertEquals(CameraState.OFF, controller.getState()); openSync(true); assertTrue(controller.getState().isAtLeast(CameraState.ENGINE)); closeSync(true); - assertEquals(controller.getState(), CameraState.OFF); + assertEquals(CameraState.OFF, controller.getState()); } @Test diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 77447c91..7d17f7e8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -408,23 +408,24 @@ public abstract class CameraEngine implements private Task startEngine() { return mOrchestrator.scheduleStateChange(CameraState.OFF, CameraState.ENGINE, true, - new Callable>() { + new Callable>() { @Override - public Task call() { + public Task call() { if (!collectCameraInfo(mFacing)) { LOG.e("onStartEngine:", "No camera available for facing", mFacing); throw new CameraException(CameraException.REASON_NO_CAMERA); } - return onStartEngine().onSuccessTask( - new SuccessContinuation() { - @NonNull - @Override - public Task then(@Nullable CameraOptions cameraOptions) { - if (cameraOptions == null) throw new RuntimeException("Null options!"); - mCallback.dispatchOnCameraOpened(cameraOptions); - return Tasks.forResult(null); - } - }); + return onStartEngine(); + } + }).onSuccessTask(new SuccessContinuation() { + @NonNull + @Override + public Task then(@Nullable CameraOptions cameraOptions) { + // Put this on the outer task so we're sure it's called after getState() is changed. + // This was breaking some tests on rare occasions. + if (cameraOptions == null) throw new RuntimeException("Null options!"); + mCallback.dispatchOnCameraOpened(cameraOptions); + return Tasks.forResult(null); } }); } @@ -437,12 +438,14 @@ public abstract class CameraEngine implements new Callable>() { @Override public Task call() { - return onStopEngine().addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(Void aVoid) { - mCallback.dispatchOnCameraClosed(); - } - }); + return onStopEngine(); + } + }).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + // Put this on the outer task so we're sure it's called after getState() is OFF. + // This was breaking some tests on rare occasions. + mCallback.dispatchOnCameraClosed(); } }); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java index 8388b8df..7ac3c2d8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java @@ -40,9 +40,9 @@ public class CameraOrchestrator { protected static class Token { public final String name; - public final Task task; + public final Task task; - private Token(@NonNull String name, @NonNull Task task) { + private Token(@NonNull String name, @NonNull Task task) { this.name = name; this.task = task; } @@ -76,28 +76,29 @@ public class CameraOrchestrator { }); } + @SuppressWarnings("unchecked") @NonNull - public Task schedule(@NonNull final String name, - final boolean dispatchExceptions, - @NonNull final Callable> job) { + public Task schedule(@NonNull final String name, + final boolean dispatchExceptions, + @NonNull final Callable> job) { LOG.i(name.toUpperCase(), "- Scheduling."); - final TaskCompletionSource source = new TaskCompletionSource<>(); + final TaskCompletionSource source = new TaskCompletionSource<>(); final WorkerHandler handler = mCallback.getJobWorker(name); synchronized (mLock) { applyCompletionListener(mJobs.getLast().task, handler, - new OnCompleteListener() { + new OnCompleteListener() { @Override - public void onComplete(@NonNull Task task) { + public void onComplete(@NonNull Task task) { synchronized (mLock) { mJobs.removeFirst(); ensureToken(); } try { LOG.i(name.toUpperCase(), "- Executing."); - Task inner = job.call(); - applyCompletionListener(inner, handler, new OnCompleteListener() { + Task inner = job.call(); + applyCompletionListener(inner, handler, new OnCompleteListener() { @Override - public void onComplete(@NonNull Task task) { + public void onComplete(@NonNull Task task) { Exception e = task.getException(); if (e != null) { LOG.w(name.toUpperCase(), "- Finished with ERROR.", e); @@ -110,7 +111,7 @@ public class CameraOrchestrator { source.trySetException(new CancellationException()); } else { LOG.i(name.toUpperCase(), "- Finished."); - source.trySetResult(null); + source.trySetResult(task.getResult()); } } }); @@ -153,7 +154,7 @@ public class CameraOrchestrator { mCallback.getJobWorker(name).remove(mDelayedJobs.get(name)); mDelayedJobs.remove(name); } - Token token = new Token(name, Tasks.forResult(null)); + Token token = new Token(name, Tasks.forResult(null)); //noinspection StatementWithEmptyBody while (mJobs.remove(token)) { /* do nothing */ } ensureToken(); @@ -163,14 +164,14 @@ public class CameraOrchestrator { private void ensureToken() { synchronized (mLock) { if (mJobs.isEmpty()) { - mJobs.add(new Token("BASE", Tasks.forResult(null))); + mJobs.add(new Token("BASE", Tasks.forResult(null))); } } } - private static void applyCompletionListener(@NonNull final Task task, - @NonNull WorkerHandler handler, - @NonNull final OnCompleteListener listener) { + private static void applyCompletionListener(@NonNull final Task task, + @NonNull WorkerHandler handler, + @NonNull final OnCompleteListener listener) { if (task.isComplete()) { handler.run(new Runnable() { @Override diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraStateOrchestrator.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraStateOrchestrator.java index 2bc8c033..0c3d5aff 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraStateOrchestrator.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraStateOrchestrator.java @@ -47,19 +47,19 @@ public class CameraStateOrchestrator extends CameraOrchestrator { } @NonNull - public Task scheduleStateChange(@NonNull final CameraState fromState, - @NonNull final CameraState toState, - boolean dispatchExceptions, - @NonNull final Callable> stateChange) { + public Task scheduleStateChange(@NonNull final CameraState fromState, + @NonNull final CameraState toState, + boolean dispatchExceptions, + @NonNull final Callable> stateChange) { final int changeCount = ++mStateChangeCount; mTargetState = toState; final boolean isTearDown = !toState.isAtLeast(fromState); final String name = isTearDown ? fromState.name() + " << " + toState.name() : fromState.name() + " >> " + toState.name(); - return schedule(name, dispatchExceptions, new Callable>() { + return schedule(name, dispatchExceptions, new Callable>() { @Override - public Task call() throws Exception { + public Task call() throws Exception { if (getCurrentState() != fromState) { LOG.w(name.toUpperCase(), "- State mismatch, aborting. current:", getCurrentState(), "from:", fromState, "to:", toState); @@ -67,9 +67,9 @@ public class CameraStateOrchestrator extends CameraOrchestrator { } else { Executor executor = mCallback.getJobWorker(name).getExecutor(); return stateChange.call().continueWithTask(executor, - new Continuation>() { + new Continuation>() { @Override - public Task then(@NonNull Task task) { + public Task then(@NonNull Task task) { if (task.isSuccessful() || isTearDown) { mCurrentState = toState; } @@ -78,9 +78,9 @@ public class CameraStateOrchestrator extends CameraOrchestrator { }); } } - }).addOnCompleteListener(new OnCompleteListener() { + }).addOnCompleteListener(new OnCompleteListener() { @Override - public void onComplete(@NonNull Task task) { + public void onComplete(@NonNull Task task) { if (changeCount == mStateChangeCount) { mTargetState = mCurrentState; }