CameraCaptureSessionImpl.java revision 51dcfd65a6742884e07182dd7d13b916fd4e0305
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 */
16package android.hardware.camera2.impl;
17
18import android.hardware.camera2.CameraAccessException;
19import android.hardware.camera2.CameraCaptureSession;
20import android.hardware.camera2.CameraDevice;
21import android.hardware.camera2.CaptureRequest;
22import android.hardware.camera2.dispatch.ArgumentReplacingDispatcher;
23import android.hardware.camera2.dispatch.BroadcastDispatcher;
24import android.hardware.camera2.dispatch.Dispatchable;
25import android.hardware.camera2.dispatch.DuckTypingDispatcher;
26import android.hardware.camera2.dispatch.HandlerDispatcher;
27import android.hardware.camera2.dispatch.InvokeDispatcher;
28import android.hardware.camera2.dispatch.NullDispatcher;
29import android.hardware.camera2.utils.TaskDrainer;
30import android.hardware.camera2.utils.TaskSingleDrainer;
31import android.os.Handler;
32import android.util.Log;
33import android.view.Surface;
34
35import java.util.Arrays;
36import java.util.List;
37
38import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler;
39import static com.android.internal.util.Preconditions.*;
40
41public 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
661                    try {
662                        mUnconfigureDrainer.taskStarted();
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                    }
671
672                    mUnconfigureDrainer.beginDrain();
673                }
674            }
675        }
676    }
677
678    private class UnconfigureDrainListener implements TaskDrainer.DrainListener {
679        @Override
680
681        public void onDrained() {
682            if (VERBOSE) Log.v(TAG, mIdString + "onUnconfigureDrained");
683            synchronized (CameraCaptureSessionImpl.this) {
684                // The device has finished unconfiguring. It's now fully closed.
685                mStateCallback.onClosed(CameraCaptureSessionImpl.this);
686            }
687        }
688    }
689}
690