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