| /* |
| * 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.os.SystemClock; |
| 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.List; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| 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 final HashMap<CameraCaptureSession, List<Surface> > mPreparedSurfaces = new HashMap<>(); |
| |
| 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); |
| synchronized (mPreparedSurfaces) { |
| mPreparedSurfaces.remove(session); |
| } |
| } |
| |
| @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 |
| synchronized (mPreparedSurfaces) { |
| List<Surface> preparedSurfaces = mPreparedSurfaces.get(session); |
| if (preparedSurfaces == null) { |
| preparedSurfaces = new ArrayList<Surface>(); |
| } |
| preparedSurfaces.add(surface); |
| mPreparedSurfaces.put(session, preparedSurfaces); |
| mPreparedSurfaces.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void onCaptureQueueEmpty(CameraCaptureSession session) { |
| mSessionFuture.setSession(session); |
| if (mProxy != null) { |
| mProxy.onCaptureQueueEmpty(session); |
| } |
| } |
| |
| /** |
| * Wait until the designated surface is prepared by the camera capture session. |
| * |
| * @param session the input {@link CameraCaptureSession} to wait for |
| * @param surface the input {@link Surface} to wait for |
| * @param timeoutMs how many milliseconds to wait for |
| * |
| * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs} |
| */ |
| public void waitForSurfacePrepared( |
| CameraCaptureSession session, Surface surface, long timeoutMs) { |
| synchronized (mPreparedSurfaces) { |
| List<Surface> preparedSurfaces = mPreparedSurfaces.get(session); |
| if (preparedSurfaces != null && preparedSurfaces.contains(surface)) { |
| return; |
| } |
| try { |
| long waitTimeRemaining = timeoutMs; |
| while (waitTimeRemaining > 0) { |
| long waitStartTime = SystemClock.elapsedRealtime(); |
| mPreparedSurfaces.wait(timeoutMs); |
| long waitTime = SystemClock.elapsedRealtime() - waitStartTime; |
| waitTimeRemaining -= waitTime; |
| preparedSurfaces = mPreparedSurfaces.get(session); |
| if (waitTimeRemaining >= 0 && preparedSurfaces != null && |
| preparedSurfaces.contains(surface)) { |
| return; |
| } |
| } |
| throw new TimeoutRuntimeException( |
| "Unable to get Surface prepared in " + timeoutMs + "ms"); |
| } catch (InterruptedException ie) { |
| throw new AssertionError(); |
| } |
| } |
| } |
| |
| 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; |
| } |
| |
| } |
| } |