Home | History | Annotate | Download | only in blocking
      1 /*
      2  * Copyright 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ex.camera2.blocking;
     17 
     18 import android.hardware.camera2.CameraCaptureSession;
     19 import android.os.ConditionVariable;
     20 import android.os.SystemClock;
     21 import android.util.Log;
     22 import android.view.Surface;
     23 
     24 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
     25 import com.android.ex.camera2.utils.StateChangeListener;
     26 import com.android.ex.camera2.utils.StateWaiter;
     27 
     28 import java.util.List;
     29 import java.util.ArrayList;
     30 import java.util.HashMap;
     31 import java.util.concurrent.Future;
     32 import java.util.concurrent.TimeUnit;
     33 import java.util.concurrent.TimeoutException;
     34 
     35 
     36 /**
     37  * A camera session listener that implements blocking operations on session state changes.
     38  *
     39  * <p>Provides a waiter that can be used to block until the next unobserved state of the
     40  * requested type arrives.</p>
     41  *
     42  * <p>Pass-through all StateCallback changes to the proxy.</p>
     43  *
     44  * @see #getStateWaiter
     45  */
     46 public class BlockingSessionCallback extends CameraCaptureSession.StateCallback {
     47     /**
     48      * Session is configured, ready for captures
     49      */
     50     public static final int SESSION_CONFIGURED = 0;
     51 
     52     /**
     53      * Session has failed to configure, can't do any captures
     54      */
     55     public static final int SESSION_CONFIGURE_FAILED = 1;
     56 
     57     /**
     58      * Session is ready
     59      */
     60     public static final int SESSION_READY = 2;
     61 
     62     /**
     63      * Session is active (transitory)
     64      */
     65     public static final int SESSION_ACTIVE = 3;
     66 
     67     /**
     68      * Session is closed
     69      */
     70     public static final int SESSION_CLOSED = 4;
     71 
     72     private static final int NUM_STATES = 5;
     73 
     74     /*
     75      * Private fields
     76      */
     77     private static final String TAG = "BlockingSessionCallback";
     78     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     79 
     80     private final CameraCaptureSession.StateCallback mProxy;
     81     private final SessionFuture mSessionFuture = new SessionFuture();
     82 
     83     private final StateWaiter mStateWaiter = new StateWaiter(sStateNames);
     84     private final StateChangeListener mStateChangeListener = mStateWaiter.getListener();
     85     private final HashMap<CameraCaptureSession, List<Surface> > mPreparedSurfaces = new HashMap<>();
     86 
     87     private static final String[] sStateNames = {
     88         "SESSION_CONFIGURED",
     89         "SESSION_CONFIGURE_FAILED",
     90         "SESSION_READY",
     91         "SESSION_ACTIVE",
     92         "SESSION_CLOSED"
     93     };
     94 
     95     /**
     96      * Create a blocking session listener without forwarding the session listener invocations
     97      * to another session listener.
     98      */
     99     public BlockingSessionCallback() {
    100         mProxy = null;
    101     }
    102 
    103     /**
    104      * Create a blocking session listener; forward original listener invocations
    105      * into {@code listener}.
    106      *
    107      * @param listener a non-{@code null} listener to forward invocations into
    108      *
    109      * @throws NullPointerException if {@code listener} was {@code null}
    110      */
    111     public BlockingSessionCallback(CameraCaptureSession.StateCallback listener) {
    112         if (listener == null) {
    113             throw new NullPointerException("listener must not be null");
    114         }
    115         mProxy = listener;
    116     }
    117 
    118     /**
    119      * Acquire the state waiter; can be used to block until a set of state transitions have
    120      * been reached.
    121      *
    122      * <p>Only one thread should wait at a time.</p>
    123      */
    124     public StateWaiter getStateWaiter() {
    125         return mStateWaiter;
    126     }
    127 
    128     /**
    129      * Return session if already have it; otherwise wait until any of the session listener
    130      * invocations fire and the session is available.
    131      *
    132      * <p>Does not consume any of the states from the state waiter.</p>
    133      *
    134      * @param timeoutMs how many milliseconds to wait for
    135      * @return a non-{@code null} {@link CameraCaptureSession} instance
    136      *
    137      * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs}
    138      */
    139     public CameraCaptureSession waitAndGetSession(long timeoutMs) {
    140         try {
    141             return mSessionFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
    142         } catch (TimeoutException e) {
    143             throw new TimeoutRuntimeException(
    144                     String.format("Failed to get session after %s milliseconds", timeoutMs), e);
    145         }
    146     }
    147 
    148     /*
    149      * CameraCaptureSession.StateCallback implementation
    150      */
    151 
    152     @Override
    153     public void onActive(CameraCaptureSession session) {
    154         mSessionFuture.setSession(session);
    155         if (mProxy != null) mProxy.onActive(session);
    156         mStateChangeListener.onStateChanged(SESSION_ACTIVE);
    157     }
    158 
    159     @Override
    160     public void onClosed(CameraCaptureSession session) {
    161         mSessionFuture.setSession(session);
    162         if (mProxy != null) mProxy.onClosed(session);
    163         mStateChangeListener.onStateChanged(SESSION_CLOSED);
    164         synchronized (mPreparedSurfaces) {
    165             mPreparedSurfaces.remove(session);
    166         }
    167     }
    168 
    169     @Override
    170     public void onConfigured(CameraCaptureSession session) {
    171         mSessionFuture.setSession(session);
    172         if (mProxy != null) {
    173             mProxy.onConfigured(session);
    174         }
    175         mStateChangeListener.onStateChanged(SESSION_CONFIGURED);
    176     }
    177 
    178     @Override
    179     public void onConfigureFailed(CameraCaptureSession session) {
    180         mSessionFuture.setSession(session);
    181         if (mProxy != null) {
    182             mProxy.onConfigureFailed(session);
    183         }
    184         mStateChangeListener.onStateChanged(SESSION_CONFIGURE_FAILED);
    185     }
    186 
    187     @Override
    188     public void onReady(CameraCaptureSession session) {
    189         mSessionFuture.setSession(session);
    190         if (mProxy != null) {
    191             mProxy.onReady(session);
    192         }
    193         mStateChangeListener.onStateChanged(SESSION_READY);
    194     }
    195 
    196     @Override
    197     public void onSurfacePrepared(CameraCaptureSession session, Surface surface) {
    198         mSessionFuture.setSession(session);
    199         if (mProxy != null) {
    200             mProxy.onSurfacePrepared(session, surface);
    201         }
    202         // Surface prepared doesn't cause a session state change, so don't trigger the
    203         // state change listener
    204         synchronized (mPreparedSurfaces) {
    205             List<Surface> preparedSurfaces = mPreparedSurfaces.get(session);
    206             if (preparedSurfaces == null) {
    207                 preparedSurfaces = new ArrayList<Surface>();
    208             }
    209             preparedSurfaces.add(surface);
    210             mPreparedSurfaces.put(session, preparedSurfaces);
    211             mPreparedSurfaces.notifyAll();
    212         }
    213     }
    214 
    215     @Override
    216     public void onCaptureQueueEmpty(CameraCaptureSession session) {
    217         mSessionFuture.setSession(session);
    218         if (mProxy != null) {
    219             mProxy.onCaptureQueueEmpty(session);
    220         }
    221     }
    222 
    223     /**
    224      * Wait until the designated surface is prepared by the camera capture session.
    225      *
    226      * @param session the input {@link CameraCaptureSession} to wait for
    227      * @param surface the input {@link Surface} to wait for
    228      * @param timeoutMs how many milliseconds to wait for
    229      *
    230      * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs}
    231      */
    232     public void waitForSurfacePrepared(
    233             CameraCaptureSession session, Surface surface, long timeoutMs) {
    234         synchronized (mPreparedSurfaces) {
    235             List<Surface> preparedSurfaces = mPreparedSurfaces.get(session);
    236             if (preparedSurfaces != null && preparedSurfaces.contains(surface)) {
    237                 return;
    238             }
    239             try {
    240                 long waitTimeRemaining = timeoutMs;
    241                 while (waitTimeRemaining > 0) {
    242                     long waitStartTime = SystemClock.elapsedRealtime();
    243                     mPreparedSurfaces.wait(timeoutMs);
    244                     long waitTime = SystemClock.elapsedRealtime() - waitStartTime;
    245                     waitTimeRemaining -= waitTime;
    246                     preparedSurfaces = mPreparedSurfaces.get(session);
    247                     if (waitTimeRemaining >= 0 && preparedSurfaces != null &&
    248                             preparedSurfaces.contains(surface)) {
    249                         return;
    250                     }
    251                 }
    252                 throw new TimeoutRuntimeException(
    253                         "Unable to get Surface prepared in " + timeoutMs + "ms");
    254             } catch (InterruptedException ie) {
    255                 throw new AssertionError();
    256             }
    257         }
    258     }
    259 
    260     private static class SessionFuture implements Future<CameraCaptureSession> {
    261         private volatile CameraCaptureSession mSession;
    262         ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
    263 
    264         public void setSession(CameraCaptureSession session) {
    265             mSession = session;
    266             mCondVar.open();
    267         }
    268 
    269         @Override
    270         public boolean cancel(boolean mayInterruptIfRunning) {
    271             return false; // don't allow canceling this task
    272         }
    273 
    274         @Override
    275         public boolean isCancelled() {
    276             return false; // can never cancel this task
    277         }
    278 
    279         @Override
    280         public boolean isDone() {
    281             return mSession != null;
    282         }
    283 
    284         @Override
    285         public CameraCaptureSession get() {
    286             mCondVar.block();
    287             return mSession;
    288         }
    289 
    290         @Override
    291         public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException {
    292             long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
    293             if (!mCondVar.block(timeoutMs)) {
    294                 throw new TimeoutException(
    295                         "Failed to receive session after " + timeout + " " + unit);
    296             }
    297 
    298             if (mSession == null) {
    299                 throw new AssertionError();
    300             }
    301             return mSession;
    302         }
    303 
    304     }
    305 }
    306