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