Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 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 android.hardware.camera2.impl;
     17 
     18 import android.hardware.camera2.CameraAccessException;
     19 import android.hardware.camera2.CameraCaptureSession;
     20 import android.hardware.camera2.CameraDevice;
     21 import android.hardware.camera2.CaptureRequest;
     22 import android.hardware.camera2.dispatch.ArgumentReplacingDispatcher;
     23 import android.hardware.camera2.dispatch.BroadcastDispatcher;
     24 import android.hardware.camera2.dispatch.Dispatchable;
     25 import android.hardware.camera2.dispatch.DuckTypingDispatcher;
     26 import android.hardware.camera2.dispatch.HandlerDispatcher;
     27 import android.hardware.camera2.dispatch.InvokeDispatcher;
     28 import android.hardware.camera2.dispatch.NullDispatcher;
     29 import android.hardware.camera2.utils.TaskDrainer;
     30 import android.hardware.camera2.utils.TaskSingleDrainer;
     31 import android.os.Handler;
     32 import android.util.Log;
     33 import android.view.Surface;
     34 
     35 import java.util.Arrays;
     36 import java.util.List;
     37 
     38 import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler;
     39 import static com.android.internal.util.Preconditions.*;
     40 
     41 public class CameraCaptureSessionImpl extends CameraCaptureSession {
     42     private static final String TAG = "CameraCaptureSession";
     43     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     44 
     45     /** Simple integer ID for session for debugging */
     46     private final int mId;
     47     private final String mIdString;
     48 
     49     /** User-specified set of surfaces used as the configuration outputs */
     50     private final List<Surface> mOutputs;
     51     /**
     52      * User-specified state callback, used for outgoing events; calls to this object will be
     53      * automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}.
     54      */
     55     private final CameraCaptureSession.StateCallback mStateCallback;
     56     /** User-specified state handler used for outgoing state callback events */
     57     private final Handler mStateHandler;
     58 
     59     /** Internal camera device; used to translate calls into existing deprecated API */
     60     private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl;
     61     /** Internal handler; used for all incoming events to preserve total order */
     62     private final Handler mDeviceHandler;
     63 
     64     /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
     65     private final TaskDrainer<Integer> mSequenceDrainer;
     66     /** Drain state transitions from ACTIVE -> IDLE */
     67     private final TaskSingleDrainer mIdleDrainer;
     68     /** Drain state transitions from BUSY -> IDLE */
     69     private final TaskSingleDrainer mAbortDrainer;
     70     /** Drain the UNCONFIGURED state transition */
     71     private final TaskSingleDrainer mUnconfigureDrainer;
     72 
     73     /** This session is closed; all further calls will throw ISE */
     74     private boolean mClosed = false;
     75     /** This session failed to be configured successfully */
     76     private final boolean mConfigureSuccess;
     77     /** Do not unconfigure if this is set; another session will overwrite configuration */
     78     private boolean mSkipUnconfigure = false;
     79 
     80     /** Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions. */
     81     private volatile boolean mAborting;
     82 
     83     /**
     84      * Create a new CameraCaptureSession.
     85      *
     86      * <p>The camera device must already be in the {@code IDLE} state when this is invoked.
     87      * There must be no pending actions
     88      * (e.g. no pending captures, no repeating requests, no flush).</p>
     89      */
     90     CameraCaptureSessionImpl(int id, List<Surface> outputs,
     91             CameraCaptureSession.StateCallback callback, Handler stateHandler,
     92             android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
     93             Handler deviceStateHandler, boolean configureSuccess) {
     94         if (outputs == null || outputs.isEmpty()) {
     95             throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
     96         } else if (callback == null) {
     97             throw new IllegalArgumentException("callback must not be null");
     98         }
     99 
    100         mId = id;
    101         mIdString = String.format("Session %d: ", mId);
    102 
    103         // TODO: extra verification of outputs
    104         mOutputs = outputs;
    105         mStateHandler = checkHandler(stateHandler);
    106         mStateCallback = createUserStateCallbackProxy(mStateHandler, callback);
    107 
    108         mDeviceHandler = checkNotNull(deviceStateHandler, "deviceStateHandler must not be null");
    109         mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");
    110 
    111         /*
    112          * Use the same handler as the device's StateCallback for all the internal coming events
    113          *
    114          * This ensures total ordering between CameraDevice.StateCallback and
    115          * CameraDeviceImpl.CaptureCallback events.
    116          */
    117         mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
    118                 /*name*/"seq");
    119         mIdleDrainer = new TaskSingleDrainer(mDeviceHandler, new IdleDrainListener(),
    120                 /*name*/"idle");
    121         mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(),
    122                 /*name*/"abort");
    123         mUnconfigureDrainer = new TaskSingleDrainer(mDeviceHandler, new UnconfigureDrainListener(),
    124                 /*name*/"unconf");
    125 
    126         // CameraDevice should call configureOutputs and have it finish before constructing us
    127 
    128         if (configureSuccess) {
    129             mStateCallback.onConfigured(this);
    130             if (VERBOSE) Log.v(TAG, mIdString + "Created session successfully");
    131             mConfigureSuccess = true;
    132         } else {
    133             mStateCallback.onConfigureFailed(this);
    134             mClosed = true; // do not fire any other callbacks, do not allow any other work
    135             Log.e(TAG, mIdString + "Failed to create capture session; configuration failed");
    136             mConfigureSuccess = false;
    137         }
    138     }
    139 
    140     @Override
    141     public CameraDevice getDevice() {
    142         return mDeviceImpl;
    143     }
    144 
    145     @Override
    146     public synchronized int capture(CaptureRequest request, CaptureCallback callback,
    147             Handler handler) throws CameraAccessException {
    148         if (request == null) {
    149             throw new IllegalArgumentException("request must not be null");
    150         }
    151 
    152         checkNotClosed();
    153 
    154         handler = checkHandler(handler, callback);
    155 
    156         if (VERBOSE) {
    157             Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +
    158                     " handler " + handler);
    159         }
    160 
    161         return addPendingSequence(mDeviceImpl.capture(request,
    162                 createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    163     }
    164 
    165     @Override
    166     public synchronized int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
    167             Handler handler) throws CameraAccessException {
    168         if (requests == null) {
    169             throw new IllegalArgumentException("requests must not be null");
    170         } else if (requests.isEmpty()) {
    171             throw new IllegalArgumentException("requests must have at least one element");
    172         }
    173 
    174         checkNotClosed();
    175 
    176         handler = checkHandler(handler, callback);
    177 
    178         if (VERBOSE) {
    179             CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
    180             Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) +
    181                     ", callback " + callback + " handler " + handler);
    182         }
    183 
    184         return addPendingSequence(mDeviceImpl.captureBurst(requests,
    185                 createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    186     }
    187 
    188     @Override
    189     public synchronized int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
    190             Handler handler) throws CameraAccessException {
    191         if (request == null) {
    192             throw new IllegalArgumentException("request must not be null");
    193         }
    194 
    195         checkNotClosed();
    196 
    197         handler = checkHandler(handler, callback);
    198 
    199         if (VERBOSE) {
    200             Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " +
    201                     callback + " handler" + " " + handler);
    202         }
    203 
    204         return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
    205                 createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    206     }
    207 
    208     @Override
    209     public synchronized int setRepeatingBurst(List<CaptureRequest> requests,
    210             CaptureCallback callback, Handler handler) throws CameraAccessException {
    211         if (requests == null) {
    212             throw new IllegalArgumentException("requests must not be null");
    213         } else if (requests.isEmpty()) {
    214             throw new IllegalArgumentException("requests must have at least one element");
    215         }
    216 
    217         checkNotClosed();
    218 
    219         handler = checkHandler(handler, callback);
    220 
    221         if (VERBOSE) {
    222             CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
    223             Log.v(TAG, mIdString + "setRepeatingBurst - requests " + Arrays.toString(requestArray) +
    224                     ", callback " + callback + " handler" + "" + handler);
    225         }
    226 
    227         return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
    228                 createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    229     }
    230 
    231     @Override
    232     public synchronized void stopRepeating() throws CameraAccessException {
    233         checkNotClosed();
    234 
    235         if (VERBOSE) {
    236             Log.v(TAG, mIdString + "stopRepeating");
    237         }
    238 
    239         mDeviceImpl.stopRepeating();
    240     }
    241 
    242     @Override
    243     public synchronized void abortCaptures() throws CameraAccessException {
    244         checkNotClosed();
    245 
    246         if (VERBOSE) {
    247             Log.v(TAG, mIdString + "abortCaptures");
    248         }
    249 
    250         if (mAborting) {
    251             Log.w(TAG, mIdString + "abortCaptures - Session is already aborting; doing nothing");
    252             return;
    253         }
    254 
    255         mAborting = true;
    256         mAbortDrainer.taskStarted();
    257 
    258         mDeviceImpl.flush();
    259         // The next BUSY -> IDLE set of transitions will mark the end of the abort.
    260     }
    261 
    262     /**
    263      * Replace this session with another session.
    264      *
    265      * <p>This is an optimization to avoid unconfiguring and then immediately having to
    266      * reconfigure again.</p>
    267      *
    268      * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
    269      * <p>
    270      *
    271      * <p>After this call completes, the session will not call any further methods on the camera
    272      * device.</p>
    273      *
    274      * @see CameraCaptureSession#close
    275      */
    276     synchronized void replaceSessionClose() {
    277         /*
    278          * In order for creating new sessions to be fast, the new session should be created
    279          * before the old session is closed.
    280          *
    281          * Otherwise the old session will always unconfigure if there is no new session to
    282          * replace it.
    283          *
    284          * Unconfiguring could add hundreds of milliseconds of delay. We could race and attempt
    285          * to skip unconfigure if a new session is created before the captures are all drained,
    286          * but this would introduce nondeterministic behavior.
    287          */
    288 
    289         if (VERBOSE) Log.v(TAG, mIdString + "replaceSessionClose");
    290 
    291         // Set up fast shutdown. Possible alternative paths:
    292         // - This session is active, so close() below starts the shutdown drain
    293         // - This session is mid-shutdown drain, and hasn't yet reached the idle drain listener.
    294         // - This session is already closed and has executed the idle drain listener, and
    295         //   configureOutputsChecked(null) has already been called.
    296         //
    297         // Do not call configureOutputsChecked(null) going forward, since it would race with the
    298         // configuration for the new session. If it was already called, then we don't care, since it
    299         // won't get called again.
    300         mSkipUnconfigure = true;
    301 
    302         close();
    303     }
    304 
    305     @Override
    306     public synchronized void close() {
    307 
    308         if (mClosed) {
    309             if (VERBOSE) Log.v(TAG, mIdString + "close - reentering");
    310             return;
    311         }
    312 
    313         if (VERBOSE) Log.v(TAG, mIdString + "close - first time");
    314 
    315         mClosed = true;
    316 
    317         /*
    318          * Flush out any repeating request. Since camera is closed, no new requests
    319          * can be queued, and eventually the entire request queue will be drained.
    320          *
    321          * If the camera device was already closed, short circuit and do nothing; since
    322          * no more internal device callbacks will fire anyway.
    323          *
    324          * Otherwise, once stopRepeating is done, wait for camera to idle, then unconfigure the
    325          * camera. Once that's done, fire #onClosed.
    326          */
    327         try {
    328             mDeviceImpl.stopRepeating();
    329         } catch (IllegalStateException e) {
    330             // OK: Camera device may already be closed, nothing else to do
    331             Log.w(TAG, mIdString + "The camera device was already closed: ", e);
    332 
    333             // TODO: Fire onClosed anytime we get the device onClosed or the ISE?
    334             // or just suppress the ISE only and rely onClosed.
    335             // Also skip any of the draining work if this is already closed.
    336 
    337             // Short-circuit; queue callback immediately and return
    338             mStateCallback.onClosed(this);
    339             return;
    340         } catch (CameraAccessException e) {
    341             // OK: close does not throw checked exceptions.
    342             Log.e(TAG, mIdString + "Exception while stopping repeating: ", e);
    343 
    344             // TODO: call onError instead of onClosed if this happens
    345         }
    346 
    347         // If no sequences are pending, fire #onClosed immediately
    348         mSequenceDrainer.beginDrain();
    349     }
    350 
    351     /**
    352      * Whether currently in mid-abort.
    353      *
    354      * <p>This is used by the implementation to set the capture failure
    355      * reason, in lieu of more accurate error codes from the camera service.
    356      * Unsynchronized to avoid deadlocks between simultaneous session->device,
    357      * device->session calls.</p>
    358      *
    359      * <p>Package-private.</p>
    360      */
    361     boolean isAborting() {
    362         return mAborting;
    363     }
    364 
    365     /**
    366      * Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code handler}.
    367      */
    368     private StateCallback createUserStateCallbackProxy(Handler handler, StateCallback callback) {
    369         InvokeDispatcher<StateCallback> userCallbackSink = new InvokeDispatcher<>(callback);
    370         HandlerDispatcher<StateCallback> handlerPassthrough =
    371                 new HandlerDispatcher<>(userCallbackSink, handler);
    372 
    373         return new CallbackProxies.SessionStateCallbackProxy(handlerPassthrough);
    374     }
    375 
    376     /**
    377      * Forward callbacks from
    378      * CameraDeviceImpl.CaptureCallback to the CameraCaptureSession.CaptureCallback.
    379      *
    380      * <p>In particular, all calls are automatically split to go both to our own
    381      * internal callback, and to the user-specified callback (by transparently posting
    382      * to the user-specified handler).</p>
    383      *
    384      * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
    385      */
    386     @SuppressWarnings("deprecation")
    387     private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxy(
    388             Handler handler, CaptureCallback callback) {
    389         CameraDeviceImpl.CaptureCallback localCallback = new CameraDeviceImpl.CaptureCallback() {
    390             @Override
    391             public void onCaptureSequenceCompleted(CameraDevice camera,
    392                     int sequenceId, long frameNumber) {
    393                 finishPendingSequence(sequenceId);
    394             }
    395 
    396             @Override
    397             public void onCaptureSequenceAborted(CameraDevice camera,
    398                     int sequenceId) {
    399                 finishPendingSequence(sequenceId);
    400             }
    401         };
    402 
    403         /*
    404          * Split the calls from the device callback into local callback and the following chain:
    405          * - replace the first CameraDevice arg with a CameraCaptureSession
    406          * - duck type from device callback to session callback
    407          * - then forward the call to a handler
    408          * - then finally invoke the destination method on the session callback object
    409          */
    410         if (callback == null) {
    411             // OK: API allows the user to not specify a callback, and the handler may
    412             // also be null in that case. Collapse whole dispatch chain to only call the local
    413             // callback
    414             return localCallback;
    415         }
    416 
    417         InvokeDispatcher<CameraDeviceImpl.CaptureCallback> localSink =
    418                 new InvokeDispatcher<>(localCallback);
    419 
    420         InvokeDispatcher<CaptureCallback> userCallbackSink =
    421                 new InvokeDispatcher<>(callback);
    422         HandlerDispatcher<CaptureCallback> handlerPassthrough =
    423                 new HandlerDispatcher<>(userCallbackSink, handler);
    424         DuckTypingDispatcher<CameraDeviceImpl.CaptureCallback, CaptureCallback> duckToSession
    425                 = new DuckTypingDispatcher<>(handlerPassthrough, CaptureCallback.class);
    426         ArgumentReplacingDispatcher<CameraDeviceImpl.CaptureCallback, CameraCaptureSessionImpl>
    427                 replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
    428                         /*argumentIndex*/0, this);
    429 
    430         BroadcastDispatcher<CameraDeviceImpl.CaptureCallback> broadcaster =
    431                 new BroadcastDispatcher<CameraDeviceImpl.CaptureCallback>(
    432                     replaceDeviceWithSession,
    433                     localSink);
    434 
    435         return new CallbackProxies.DeviceCaptureCallbackProxy(broadcaster);
    436     }
    437 
    438     /**
    439      *
    440      * Create an internal state callback, to be invoked on the mDeviceHandler
    441      *
    442      * <p>It has a few behaviors:
    443      * <ul>
    444      * <li>Convert device state changes into session state changes.
    445      * <li>Keep track of async tasks that the session began (idle, abort).
    446      * </ul>
    447      * </p>
    448      * */
    449     CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() {
    450         final CameraCaptureSession session = this;
    451 
    452         return new CameraDeviceImpl.StateCallbackKK() {
    453             private boolean mBusy = false;
    454             private boolean mActive = false;
    455 
    456             @Override
    457             public void onOpened(CameraDevice camera) {
    458                 throw new AssertionError("Camera must already be open before creating a session");
    459             }
    460 
    461             @Override
    462             public void onDisconnected(CameraDevice camera) {
    463                 if (VERBOSE) Log.v(TAG, mIdString + "onDisconnected");
    464                 close();
    465             }
    466 
    467             @Override
    468             public void onError(CameraDevice camera, int error) {
    469                 // Should not be reached, handled by device code
    470                 Log.wtf(TAG, mIdString + "Got device error " + error);
    471             }
    472 
    473             @Override
    474             public void onActive(CameraDevice camera) {
    475                 mIdleDrainer.taskStarted();
    476                 mActive = true;
    477 
    478                 if (VERBOSE) Log.v(TAG, mIdString + "onActive");
    479                 mStateCallback.onActive(session);
    480             }
    481 
    482             @Override
    483             public void onIdle(CameraDevice camera) {
    484                 boolean isAborting;
    485                 if (VERBOSE) Log.v(TAG, mIdString + "onIdle");
    486 
    487                 synchronized (session) {
    488                     isAborting = mAborting;
    489                 }
    490 
    491                 /*
    492                  * Check which states we transitioned through:
    493                  *
    494                  * (ACTIVE -> IDLE)
    495                  * (BUSY -> IDLE)
    496                  *
    497                  * Note that this is also legal:
    498                  * (ACTIVE -> BUSY -> IDLE)
    499                  *
    500                  * and mark those tasks as finished
    501                  */
    502                 if (mBusy && isAborting) {
    503                     mAbortDrainer.taskFinished();
    504 
    505                     synchronized (session) {
    506                         mAborting = false;
    507                     }
    508                 }
    509 
    510                 if (mActive) {
    511                     mIdleDrainer.taskFinished();
    512                 }
    513 
    514                 mBusy = false;
    515                 mActive = false;
    516 
    517                 mStateCallback.onReady(session);
    518             }
    519 
    520             @Override
    521             public void onBusy(CameraDevice camera) {
    522                 mBusy = true;
    523 
    524                 // TODO: Queue captures during abort instead of failing them
    525                 // since the app won't be able to distinguish the two actives
    526                 // Don't signal the application since there's no clean mapping here
    527                 if (VERBOSE) Log.v(TAG, mIdString + "onBusy");
    528             }
    529 
    530             @Override
    531             public void onUnconfigured(CameraDevice camera) {
    532                 if (VERBOSE) Log.v(TAG, mIdString + "onUnconfigured");
    533                 synchronized (session) {
    534                     // Ignore #onUnconfigured before #close is called.
    535                     //
    536                     // Normally, this is reached when this session is closed and no immediate other
    537                     // activity happens for the camera, in which case the camera is configured to
    538                     // null streams by this session and the UnconfigureDrainer task is started.
    539                     // However, we can also end up here if
    540                     //
    541                     // 1) Session is closed
    542                     // 2) New session is created before this session finishes closing, setting
    543                     //    mSkipUnconfigure and therefore this session does not configure null or
    544                     //    start the UnconfigureDrainer task.
    545                     // 3) And then the new session fails to be created, so onUnconfigured fires
    546                     //    _anyway_.
    547                     // In this second case, need to not finish a task that was never started, so
    548                     // guard with mSkipUnconfigure
    549                     if (mClosed && mConfigureSuccess && !mSkipUnconfigure) {
    550                         mUnconfigureDrainer.taskFinished();
    551                     }
    552                 }
    553             }
    554         };
    555 
    556     }
    557 
    558     @Override
    559     protected void finalize() throws Throwable {
    560         try {
    561             close();
    562         } finally {
    563             super.finalize();
    564         }
    565     }
    566 
    567     private void checkNotClosed() {
    568         if (mClosed) {
    569             throw new IllegalStateException(
    570                     "Session has been closed; further changes are illegal.");
    571         }
    572     }
    573 
    574     /**
    575      * Notify the session that a pending capture sequence has just been queued.
    576      *
    577      * <p>During a shutdown/close, the session waits until all pending sessions are finished
    578      * before taking any further steps to shut down itself.</p>
    579      *
    580      * @see #finishPendingSequence
    581      */
    582     private int addPendingSequence(int sequenceId) {
    583         mSequenceDrainer.taskStarted(sequenceId);
    584         return sequenceId;
    585     }
    586 
    587     /**
    588      * Notify the session that a pending capture sequence is now finished.
    589      *
    590      * <p>During a shutdown/close, once all pending sequences finish, it is safe to
    591      * close the camera further by unconfiguring and then firing {@code onClosed}.</p>
    592      */
    593     private void finishPendingSequence(int sequenceId) {
    594         mSequenceDrainer.taskFinished(sequenceId);
    595     }
    596 
    597     private class SequenceDrainListener implements TaskDrainer.DrainListener {
    598         @Override
    599         public void onDrained() {
    600             /*
    601              * No repeating request is set; and the capture queue has fully drained.
    602              *
    603              * If no captures were queued to begin with, and an abort was queued,
    604              * it's still possible to get another BUSY before the last IDLE.
    605              *
    606              * If the camera is already "IDLE" and no aborts are pending,
    607              * then the drain immediately finishes.
    608              */
    609             if (VERBOSE) Log.v(TAG, mIdString + "onSequenceDrained");
    610             mAbortDrainer.beginDrain();
    611         }
    612     }
    613 
    614     private class AbortDrainListener implements TaskDrainer.DrainListener {
    615         @Override
    616         public void onDrained() {
    617             if (VERBOSE) Log.v(TAG, mIdString + "onAbortDrained");
    618             synchronized (CameraCaptureSessionImpl.this) {
    619                 /*
    620                  * Any queued aborts have now completed.
    621                  *
    622                  * It's now safe to wait to receive the final "IDLE" event, as the camera device
    623                  * will no longer again transition to "ACTIVE" by itself.
    624                  *
    625                  * If the camera is already "IDLE", then the drain immediately finishes.
    626                  */
    627                 mIdleDrainer.beginDrain();
    628             }
    629         }
    630     }
    631 
    632     private class IdleDrainListener implements TaskDrainer.DrainListener {
    633         @Override
    634         public void onDrained() {
    635             if (VERBOSE) Log.v(TAG, mIdString + "onIdleDrained");
    636 
    637             // Take device lock before session lock so that we can call back into device
    638             // without causing a deadlock
    639             synchronized (mDeviceImpl.mInterfaceLock) {
    640                 synchronized (CameraCaptureSessionImpl.this) {
    641                 /*
    642                  * The device is now IDLE, and has settled. It will not transition to
    643                  * ACTIVE or BUSY again by itself.
    644                  *
    645                  * It's now safe to unconfigure the outputs and after it's done invoke #onClosed.
    646                  *
    647                  * This operation is idempotent; a session will not be closed twice.
    648                  */
    649                     if (VERBOSE)
    650                         Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " +
    651                                 mSkipUnconfigure);
    652 
    653                     // Fast path: A new capture session has replaced this one; don't unconfigure.
    654                     if (mSkipUnconfigure) {
    655                         mStateCallback.onClosed(CameraCaptureSessionImpl.this);
    656                         return;
    657                     }
    658 
    659                     // Slow path: #close was called explicitly on this session; unconfigure first
    660                     mUnconfigureDrainer.taskStarted();
    661 
    662                     try {
    663                         mDeviceImpl
    664                                 .configureOutputsChecked(null); // begin transition to unconfigured
    665                     } catch (CameraAccessException e) {
    666                         // OK: do not throw checked exceptions.
    667                         Log.e(TAG, mIdString + "Exception while configuring outputs: ", e);
    668 
    669                         // TODO: call onError instead of onClosed if this happens
    670                     } catch (IllegalStateException e) {
    671                         // Camera is already closed, so go straight to the close callback
    672                         if (VERBOSE) Log.v(TAG, mIdString +
    673                                 "Camera was already closed or busy, skipping unconfigure");
    674                         mUnconfigureDrainer.taskFinished();
    675                     }
    676 
    677                     mUnconfigureDrainer.beginDrain();
    678                 }
    679             }
    680         }
    681     }
    682 
    683     private class UnconfigureDrainListener implements TaskDrainer.DrainListener {
    684         @Override
    685 
    686         public void onDrained() {
    687             if (VERBOSE) Log.v(TAG, mIdString + "onUnconfigureDrained");
    688             synchronized (CameraCaptureSessionImpl.this) {
    689                 // The device has finished unconfiguring. It's now fully closed.
    690                 mStateCallback.onClosed(CameraCaptureSessionImpl.this);
    691             }
    692         }
    693     }
    694 }
    695