CameraCaptureSessionImpl.java revision b0025e1f6848b02cb4bd25f07c6f9567116f6aaf
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.params.OutputConfiguration; 28import android.hardware.camera2.utils.TaskDrainer; 29import android.hardware.camera2.utils.TaskSingleDrainer; 30import android.os.Handler; 31import android.util.Log; 32import android.view.Surface; 33 34import java.util.Arrays; 35import java.util.List; 36 37import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler; 38import static com.android.internal.util.Preconditions.*; 39 40public 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 */ 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 140 public CameraDevice getDevice() { 141 return mDeviceImpl; 142 } 143 144 @Override 145 public void prepare(Surface surface) throws CameraAccessException { 146 mDeviceImpl.prepare(surface); 147 } 148 149 @Override 150 public void prepare(int maxCount, Surface surface) throws CameraAccessException { 151 mDeviceImpl.prepare(maxCount, surface); 152 } 153 154 @Override 155 public void tearDown(Surface surface) throws CameraAccessException { 156 mDeviceImpl.tearDown(surface); 157 } 158 159 @Override 160 public void finishDeferredConfiguration( 161 List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException { 162 mDeviceImpl.finishDeferredConfig(deferredOutputConfigs); 163 } 164 165 @Override 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 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 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 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 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 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 315 public boolean isReprocessable() { 316 return mInput != null; 317 } 318 319 @Override 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 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 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 431 public boolean isAborting() { 432 return mAborting; 433 } 434 435 /** 436 * Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code handler}. 437 */ 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") 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 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 617 protected void finalize() throws Throwable { 618 try { 619 close(); 620 } finally { 621 super.finalize(); 622 } 623 } 624 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 */ 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 */ 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 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 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 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