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