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