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