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