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 */ 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 141 public CameraDevice getDevice() { 142 return mDeviceImpl; 143 } 144 145 @Override 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 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 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 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 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 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 */ 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 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 */ 361 boolean isAborting() { 362 return mAborting; 363 } 364 365 /** 366 * Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code handler}. 367 */ 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") 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 * */ 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 559 protected void finalize() throws Throwable { 560 try { 561 close(); 562 } finally { 563 super.finalize(); 564 } 565 } 566 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 */ 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 */ 593 private void finishPendingSequence(int sequenceId) { 594 mSequenceDrainer.taskFinished(sequenceId); 595 } 596 597 private class SequenceDrainListener implements TaskDrainer.DrainListener { 598 @Override 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 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 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 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