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