blob: 0dad0d55a58464c733f8ca8c6a21f0cd8519b79a [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ex.camera2.blocking;
import android.hardware.camera2.CameraCaptureSession;
import android.os.ConditionVariable;
import android.util.Log;
import android.view.Surface;
import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
import com.android.ex.camera2.utils.StateChangeListener;
import com.android.ex.camera2.utils.StateWaiter;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* A camera session listener that implements blocking operations on session state changes.
*
* <p>Provides a waiter that can be used to block until the next unobserved state of the
* requested type arrives.</p>
*
* <p>Pass-through all StateCallback changes to the proxy.</p>
*
* @see #getStateWaiter
*/
public class BlockingSessionCallback extends CameraCaptureSession.StateCallback {
/**
* Session is configured, ready for captures
*/
public static final int SESSION_CONFIGURED = 0;
/**
* Session has failed to configure, can't do any captures
*/
public static final int SESSION_CONFIGURE_FAILED = 1;
/**
* Session is ready
*/
public static final int SESSION_READY = 2;
/**
* Session is active (transitory)
*/
public static final int SESSION_ACTIVE = 3;
/**
* Session is closed
*/
public static final int SESSION_CLOSED = 4;
private static final int NUM_STATES = 5;
/*
* Private fields
*/
private static final String TAG = "BlockingSessionCallback";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private final CameraCaptureSession.StateCallback mProxy;
private final SessionFuture mSessionFuture = new SessionFuture();
private final StateWaiter mStateWaiter = new StateWaiter(sStateNames);
private final StateChangeListener mStateChangeListener = mStateWaiter.getListener();
private static final String[] sStateNames = {
"SESSION_CONFIGURED",
"SESSION_CONFIGURE_FAILED",
"SESSION_READY",
"SESSION_ACTIVE",
"SESSION_CLOSED"
};
/**
* Create a blocking session listener without forwarding the session listener invocations
* to another session listener.
*/
public BlockingSessionCallback() {
mProxy = null;
}
/**
* Create a blocking session listener; forward original listener invocations
* into {@code listener}.
*
* @param listener a non-{@code null} listener to forward invocations into
*
* @throws NullPointerException if {@code listener} was {@code null}
*/
public BlockingSessionCallback(CameraCaptureSession.StateCallback listener) {
if (listener == null) {
throw new NullPointerException("listener must not be null");
}
mProxy = listener;
}
/**
* Acquire the state waiter; can be used to block until a set of state transitions have
* been reached.
*
* <p>Only one thread should wait at a time.</p>
*/
public StateWaiter getStateWaiter() {
return mStateWaiter;
}
/**
* Return session if already have it; otherwise wait until any of the session listener
* invocations fire and the session is available.
*
* <p>Does not consume any of the states from the state waiter.</p>
*
* @param timeoutMs how many milliseconds to wait for
* @return a non-{@code null} {@link CameraCaptureSession} instance
*
* @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs}
*/
public CameraCaptureSession waitAndGetSession(long timeoutMs) {
try {
return mSessionFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new TimeoutRuntimeException(
String.format("Failed to get session after %s milliseconds", timeoutMs), e);
}
}
/*
* CameraCaptureSession.StateCallback implementation
*/
@Override
public void onActive(CameraCaptureSession session) {
mSessionFuture.setSession(session);
if (mProxy != null) mProxy.onActive(session);
mStateChangeListener.onStateChanged(SESSION_ACTIVE);
}
@Override
public void onClosed(CameraCaptureSession session) {
mSessionFuture.setSession(session);
if (mProxy != null) mProxy.onClosed(session);
mStateChangeListener.onStateChanged(SESSION_CLOSED);
}
@Override
public void onConfigured(CameraCaptureSession session) {
mSessionFuture.setSession(session);
if (mProxy != null) {
mProxy.onConfigured(session);
}
mStateChangeListener.onStateChanged(SESSION_CONFIGURED);
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
mSessionFuture.setSession(session);
if (mProxy != null) {
mProxy.onConfigureFailed(session);
}
mStateChangeListener.onStateChanged(SESSION_CONFIGURE_FAILED);
}
@Override
public void onReady(CameraCaptureSession session) {
mSessionFuture.setSession(session);
if (mProxy != null) {
mProxy.onReady(session);
}
mStateChangeListener.onStateChanged(SESSION_READY);
}
@Override
public void onSurfacePrepared(CameraCaptureSession session, Surface surface) {
mSessionFuture.setSession(session);
if (mProxy != null) {
mProxy.onSurfacePrepared(session, surface);
}
// Surface prepared doesn't cause a session state change, so don't trigger the
// state change listener
}
private static class SessionFuture implements Future<CameraCaptureSession> {
private volatile CameraCaptureSession mSession;
ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
public void setSession(CameraCaptureSession session) {
mSession = session;
mCondVar.open();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false; // don't allow canceling this task
}
@Override
public boolean isCancelled() {
return false; // can never cancel this task
}
@Override
public boolean isDone() {
return mSession != null;
}
@Override
public CameraCaptureSession get() {
mCondVar.block();
return mSession;
}
@Override
public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException {
long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
if (!mCondVar.block(timeoutMs)) {
throw new TimeoutException(
"Failed to receive session after " + timeout + " " + unit);
}
if (mSession == null) {
throw new AssertionError();
}
return mSession;
}
}
}