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