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