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