1/* 2 * Copyright (C) 2013 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 */ 16 17package android.hardware.camera2.impl; 18 19import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; 20 21import android.graphics.ImageFormat; 22import android.hardware.camera2.CameraAccessException; 23import android.hardware.camera2.CameraCaptureSession; 24import android.hardware.camera2.CameraCharacteristics; 25import android.hardware.camera2.CameraDevice; 26import android.hardware.camera2.CaptureRequest; 27import android.hardware.camera2.CaptureResult; 28import android.hardware.camera2.CaptureFailure; 29import android.hardware.camera2.ICameraDeviceCallbacks; 30import android.hardware.camera2.ICameraDeviceUser; 31import android.hardware.camera2.TotalCaptureResult; 32import android.hardware.camera2.params.InputConfiguration; 33import android.hardware.camera2.params.OutputConfiguration; 34import android.hardware.camera2.params.ReprocessFormatsMap; 35import android.hardware.camera2.params.StreamConfigurationMap; 36import android.hardware.camera2.utils.SubmitInfo; 37import android.hardware.camera2.utils.SurfaceUtils; 38import android.hardware.ICameraService; 39import android.os.Handler; 40import android.os.IBinder; 41import android.os.Looper; 42import android.os.RemoteException; 43import android.os.ServiceSpecificException; 44import android.util.Log; 45import android.util.Range; 46import android.util.Size; 47import android.util.SparseArray; 48import android.view.Surface; 49 50import java.util.AbstractMap.SimpleEntry; 51import java.util.ArrayList; 52import java.util.Arrays; 53import java.util.Collection; 54import java.util.Collections; 55import java.util.concurrent.atomic.AtomicBoolean; 56import java.util.HashMap; 57import java.util.HashSet; 58import java.util.Iterator; 59import java.util.List; 60import java.util.LinkedList; 61import java.util.TreeMap; 62 63/** 64 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate 65 */ 66public class CameraDeviceImpl extends CameraDevice 67 implements IBinder.DeathRecipient { 68 private final String TAG; 69 private final boolean DEBUG = false; 70 71 private static final int REQUEST_ID_NONE = -1; 72 73 // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) 74 private ICameraDeviceUserWrapper mRemoteDevice; 75 76 // Lock to synchronize cross-thread access to device public interface 77 final Object mInterfaceLock = new Object(); // access from this class and Session only! 78 private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); 79 80 private final StateCallback mDeviceCallback; 81 private volatile StateCallbackKK mSessionStateCallback; 82 private final Handler mDeviceHandler; 83 84 private final AtomicBoolean mClosing = new AtomicBoolean(); 85 private boolean mInError = false; 86 private boolean mIdle = true; 87 88 /** map request IDs to callback/request data */ 89 private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap = 90 new SparseArray<CaptureCallbackHolder>(); 91 92 private int mRepeatingRequestId = REQUEST_ID_NONE; 93 // Map stream IDs to input/output configurations 94 private SimpleEntry<Integer, InputConfiguration> mConfiguredInput = 95 new SimpleEntry<>(REQUEST_ID_NONE, null); 96 private final SparseArray<OutputConfiguration> mConfiguredOutputs = 97 new SparseArray<>(); 98 99 private final String mCameraId; 100 private final CameraCharacteristics mCharacteristics; 101 private final int mTotalPartialCount; 102 103 /** 104 * A list tracking request and its expected last regular frame number and last reprocess frame 105 * number. Updated when calling ICameraDeviceUser methods. 106 */ 107 private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList = 108 new ArrayList<>(); 109 110 /** 111 * An object tracking received frame numbers. 112 * Updated when receiving callbacks from ICameraDeviceCallbacks. 113 */ 114 private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker(); 115 116 private CameraCaptureSessionCore mCurrentSession; 117 private int mNextSessionId = 0; 118 119 // Runnables for all state transitions, except error, which needs the 120 // error code argument 121 122 private final Runnable mCallOnOpened = new Runnable() { 123 @Override 124 public void run() { 125 StateCallbackKK sessionCallback = null; 126 synchronized(mInterfaceLock) { 127 if (mRemoteDevice == null) return; // Camera already closed 128 129 sessionCallback = mSessionStateCallback; 130 } 131 if (sessionCallback != null) { 132 sessionCallback.onOpened(CameraDeviceImpl.this); 133 } 134 mDeviceCallback.onOpened(CameraDeviceImpl.this); 135 } 136 }; 137 138 private final Runnable mCallOnUnconfigured = new Runnable() { 139 @Override 140 public void run() { 141 StateCallbackKK sessionCallback = null; 142 synchronized(mInterfaceLock) { 143 if (mRemoteDevice == null) return; // Camera already closed 144 145 sessionCallback = mSessionStateCallback; 146 } 147 if (sessionCallback != null) { 148 sessionCallback.onUnconfigured(CameraDeviceImpl.this); 149 } 150 } 151 }; 152 153 private final Runnable mCallOnActive = new Runnable() { 154 @Override 155 public void run() { 156 StateCallbackKK sessionCallback = null; 157 synchronized(mInterfaceLock) { 158 if (mRemoteDevice == null) return; // Camera already closed 159 160 sessionCallback = mSessionStateCallback; 161 } 162 if (sessionCallback != null) { 163 sessionCallback.onActive(CameraDeviceImpl.this); 164 } 165 } 166 }; 167 168 private final Runnable mCallOnBusy = new Runnable() { 169 @Override 170 public void run() { 171 StateCallbackKK sessionCallback = null; 172 synchronized(mInterfaceLock) { 173 if (mRemoteDevice == null) return; // Camera already closed 174 175 sessionCallback = mSessionStateCallback; 176 } 177 if (sessionCallback != null) { 178 sessionCallback.onBusy(CameraDeviceImpl.this); 179 } 180 } 181 }; 182 183 private final Runnable mCallOnClosed = new Runnable() { 184 private boolean mClosedOnce = false; 185 186 @Override 187 public void run() { 188 if (mClosedOnce) { 189 throw new AssertionError("Don't post #onClosed more than once"); 190 } 191 StateCallbackKK sessionCallback = null; 192 synchronized(mInterfaceLock) { 193 sessionCallback = mSessionStateCallback; 194 } 195 if (sessionCallback != null) { 196 sessionCallback.onClosed(CameraDeviceImpl.this); 197 } 198 mDeviceCallback.onClosed(CameraDeviceImpl.this); 199 mClosedOnce = true; 200 } 201 }; 202 203 private final Runnable mCallOnIdle = new Runnable() { 204 @Override 205 public void run() { 206 StateCallbackKK sessionCallback = null; 207 synchronized(mInterfaceLock) { 208 if (mRemoteDevice == null) return; // Camera already closed 209 210 sessionCallback = mSessionStateCallback; 211 } 212 if (sessionCallback != null) { 213 sessionCallback.onIdle(CameraDeviceImpl.this); 214 } 215 } 216 }; 217 218 private final Runnable mCallOnDisconnected = new Runnable() { 219 @Override 220 public void run() { 221 StateCallbackKK sessionCallback = null; 222 synchronized(mInterfaceLock) { 223 if (mRemoteDevice == null) return; // Camera already closed 224 225 sessionCallback = mSessionStateCallback; 226 } 227 if (sessionCallback != null) { 228 sessionCallback.onDisconnected(CameraDeviceImpl.this); 229 } 230 mDeviceCallback.onDisconnected(CameraDeviceImpl.this); 231 } 232 }; 233 234 public CameraDeviceImpl(String cameraId, StateCallback callback, Handler handler, 235 CameraCharacteristics characteristics) { 236 if (cameraId == null || callback == null || handler == null || characteristics == null) { 237 throw new IllegalArgumentException("Null argument given"); 238 } 239 mCameraId = cameraId; 240 mDeviceCallback = callback; 241 mDeviceHandler = handler; 242 mCharacteristics = characteristics; 243 244 final int MAX_TAG_LEN = 23; 245 String tag = String.format("CameraDevice-JV-%s", mCameraId); 246 if (tag.length() > MAX_TAG_LEN) { 247 tag = tag.substring(0, MAX_TAG_LEN); 248 } 249 TAG = tag; 250 251 Integer partialCount = 252 mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT); 253 if (partialCount == null) { 254 // 1 means partial result is not supported. 255 mTotalPartialCount = 1; 256 } else { 257 mTotalPartialCount = partialCount; 258 } 259 } 260 261 public CameraDeviceCallbacks getCallbacks() { 262 return mCallbacks; 263 } 264 265 /** 266 * Set remote device, which triggers initial onOpened/onUnconfigured callbacks 267 * 268 * <p>This function may post onDisconnected and throw CAMERA_DISCONNECTED if remoteDevice dies 269 * during setup.</p> 270 * 271 */ 272 public void setRemoteDevice(ICameraDeviceUser remoteDevice) throws CameraAccessException { 273 synchronized(mInterfaceLock) { 274 // TODO: Move from decorator to direct binder-mediated exceptions 275 // If setRemoteFailure already called, do nothing 276 if (mInError) return; 277 278 mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice); 279 280 IBinder remoteDeviceBinder = remoteDevice.asBinder(); 281 // For legacy camera device, remoteDevice is in the same process, and 282 // asBinder returns NULL. 283 if (remoteDeviceBinder != null) { 284 try { 285 remoteDeviceBinder.linkToDeath(this, /*flag*/ 0); 286 } catch (RemoteException e) { 287 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); 288 289 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, 290 "The camera device has encountered a serious error"); 291 } 292 } 293 294 mDeviceHandler.post(mCallOnOpened); 295 mDeviceHandler.post(mCallOnUnconfigured); 296 } 297 } 298 299 /** 300 * Call to indicate failed connection to a remote camera device. 301 * 302 * <p>This places the camera device in the error state and informs the callback. 303 * Use in place of setRemoteDevice() when startup fails.</p> 304 */ 305 public void setRemoteFailure(final ServiceSpecificException failure) { 306 int failureCode = StateCallback.ERROR_CAMERA_DEVICE; 307 boolean failureIsError = true; 308 309 switch (failure.errorCode) { 310 case ICameraService.ERROR_CAMERA_IN_USE: 311 failureCode = StateCallback.ERROR_CAMERA_IN_USE; 312 break; 313 case ICameraService.ERROR_MAX_CAMERAS_IN_USE: 314 failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE; 315 break; 316 case ICameraService.ERROR_DISABLED: 317 failureCode = StateCallback.ERROR_CAMERA_DISABLED; 318 break; 319 case ICameraService.ERROR_DISCONNECTED: 320 failureIsError = false; 321 break; 322 case ICameraService.ERROR_INVALID_OPERATION: 323 failureCode = StateCallback.ERROR_CAMERA_DEVICE; 324 break; 325 default: 326 Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode + 327 failure.getMessage()); 328 break; 329 } 330 final int code = failureCode; 331 final boolean isError = failureIsError; 332 synchronized(mInterfaceLock) { 333 mInError = true; 334 mDeviceHandler.post(new Runnable() { 335 @Override 336 public void run() { 337 if (isError) { 338 mDeviceCallback.onError(CameraDeviceImpl.this, code); 339 } else { 340 mDeviceCallback.onDisconnected(CameraDeviceImpl.this); 341 } 342 } 343 }); 344 } 345 } 346 347 @Override 348 public String getId() { 349 return mCameraId; 350 } 351 352 public void configureOutputs(List<Surface> outputs) throws CameraAccessException { 353 // Leave this here for backwards compatibility with older code using this directly 354 ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size()); 355 for (Surface s : outputs) { 356 outputConfigs.add(new OutputConfiguration(s)); 357 } 358 configureStreamsChecked(/*inputConfig*/null, outputConfigs, 359 /*isConstrainedHighSpeed*/false); 360 361 } 362 363 /** 364 * Attempt to configure the input and outputs; the device goes to idle and then configures the 365 * new input and outputs if possible. 366 * 367 * <p>The configuration may gracefully fail, if input configuration is not supported, 368 * if there are too many outputs, if the formats are not supported, or if the sizes for that 369 * format is not supported. In this case this function will return {@code false} and the 370 * unconfigured callback will be fired.</p> 371 * 372 * <p>If the configuration succeeds (with 1 or more outputs with or without an input), 373 * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p> 374 * 375 * @param inputConfig input configuration or {@code null} for no input 376 * @param outputs a list of one or more surfaces, or {@code null} to unconfigure 377 * @param isConstrainedHighSpeed If the streams configuration is for constrained high speed output. 378 * @return whether or not the configuration was successful 379 * 380 * @throws CameraAccessException if there were any unexpected problems during configuration 381 */ 382 public boolean configureStreamsChecked(InputConfiguration inputConfig, 383 List<OutputConfiguration> outputs, boolean isConstrainedHighSpeed) 384 throws CameraAccessException { 385 // Treat a null input the same an empty list 386 if (outputs == null) { 387 outputs = new ArrayList<OutputConfiguration>(); 388 } 389 if (outputs.size() == 0 && inputConfig != null) { 390 throw new IllegalArgumentException("cannot configure an input stream without " + 391 "any output streams"); 392 } 393 394 checkInputConfiguration(inputConfig); 395 396 boolean success = false; 397 398 synchronized(mInterfaceLock) { 399 checkIfCameraClosedOrInError(); 400 // Streams to create 401 HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs); 402 // Streams to delete 403 List<Integer> deleteList = new ArrayList<Integer>(); 404 405 // Determine which streams need to be created, which to be deleted 406 for (int i = 0; i < mConfiguredOutputs.size(); ++i) { 407 int streamId = mConfiguredOutputs.keyAt(i); 408 OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i); 409 410 if (!outputs.contains(outConfig)) { 411 deleteList.add(streamId); 412 } else { 413 addSet.remove(outConfig); // Don't create a stream previously created 414 } 415 } 416 417 mDeviceHandler.post(mCallOnBusy); 418 stopRepeating(); 419 420 try { 421 waitUntilIdle(); 422 423 mRemoteDevice.beginConfigure(); 424 425 // reconfigure the input stream if the input configuration is different. 426 InputConfiguration currentInputConfig = mConfiguredInput.getValue(); 427 if (inputConfig != currentInputConfig && 428 (inputConfig == null || !inputConfig.equals(currentInputConfig))) { 429 if (currentInputConfig != null) { 430 mRemoteDevice.deleteStream(mConfiguredInput.getKey()); 431 mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( 432 REQUEST_ID_NONE, null); 433 } 434 if (inputConfig != null) { 435 int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), 436 inputConfig.getHeight(), inputConfig.getFormat()); 437 mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( 438 streamId, inputConfig); 439 } 440 } 441 442 // Delete all streams first (to free up HW resources) 443 for (Integer streamId : deleteList) { 444 mRemoteDevice.deleteStream(streamId); 445 mConfiguredOutputs.delete(streamId); 446 } 447 448 // Add all new streams 449 for (OutputConfiguration outConfig : outputs) { 450 if (addSet.contains(outConfig)) { 451 int streamId = mRemoteDevice.createStream(outConfig); 452 mConfiguredOutputs.put(streamId, outConfig); 453 } 454 } 455 456 mRemoteDevice.endConfigure(isConstrainedHighSpeed); 457 458 success = true; 459 } catch (IllegalArgumentException e) { 460 // OK. camera service can reject stream config if it's not supported by HAL 461 // This is only the result of a programmer misusing the camera2 api. 462 Log.w(TAG, "Stream configuration failed due to: " + e.getMessage()); 463 return false; 464 } catch (CameraAccessException e) { 465 if (e.getReason() == CameraAccessException.CAMERA_IN_USE) { 466 throw new IllegalStateException("The camera is currently busy." + 467 " You must wait until the previous operation completes.", e); 468 } 469 throw e; 470 } finally { 471 if (success && outputs.size() > 0) { 472 mDeviceHandler.post(mCallOnIdle); 473 } else { 474 // Always return to the 'unconfigured' state if we didn't hit a fatal error 475 mDeviceHandler.post(mCallOnUnconfigured); 476 } 477 } 478 } 479 480 return success; 481 } 482 483 @Override 484 public void createCaptureSession(List<Surface> outputs, 485 CameraCaptureSession.StateCallback callback, Handler handler) 486 throws CameraAccessException { 487 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); 488 for (Surface surface : outputs) { 489 outConfigurations.add(new OutputConfiguration(surface)); 490 } 491 createCaptureSessionInternal(null, outConfigurations, callback, handler, 492 /*isConstrainedHighSpeed*/false); 493 } 494 495 @Override 496 public void createCaptureSessionByOutputConfigurations( 497 List<OutputConfiguration> outputConfigurations, 498 CameraCaptureSession.StateCallback callback, Handler handler) 499 throws CameraAccessException { 500 if (DEBUG) { 501 Log.d(TAG, "createCaptureSessionByOutputConfiguration"); 502 } 503 504 // OutputConfiguration objects are immutable, but need to have our own array 505 List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations); 506 507 createCaptureSessionInternal(null, currentOutputs, callback, handler, 508 /*isConstrainedHighSpeed*/false); 509 } 510 511 @Override 512 public void createReprocessableCaptureSession(InputConfiguration inputConfig, 513 List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) 514 throws CameraAccessException { 515 if (DEBUG) { 516 Log.d(TAG, "createReprocessableCaptureSession"); 517 } 518 519 if (inputConfig == null) { 520 throw new IllegalArgumentException("inputConfig cannot be null when creating a " + 521 "reprocessable capture session"); 522 } 523 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); 524 for (Surface surface : outputs) { 525 outConfigurations.add(new OutputConfiguration(surface)); 526 } 527 createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler, 528 /*isConstrainedHighSpeed*/false); 529 } 530 531 @Override 532 public void createReprocessableCaptureSessionByConfigurations(InputConfiguration inputConfig, 533 List<OutputConfiguration> outputs, 534 android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) 535 throws CameraAccessException { 536 if (DEBUG) { 537 Log.d(TAG, "createReprocessableCaptureSessionWithConfigurations"); 538 } 539 540 if (inputConfig == null) { 541 throw new IllegalArgumentException("inputConfig cannot be null when creating a " + 542 "reprocessable capture session"); 543 } 544 545 if (outputs == null) { 546 throw new IllegalArgumentException("Output configurations cannot be null when " + 547 "creating a reprocessable capture session"); 548 } 549 550 // OutputConfiguration objects aren't immutable, make a copy before using. 551 List<OutputConfiguration> currentOutputs = new ArrayList<OutputConfiguration>(); 552 for (OutputConfiguration output : outputs) { 553 currentOutputs.add(new OutputConfiguration(output)); 554 } 555 createCaptureSessionInternal(inputConfig, currentOutputs, 556 callback, handler, /*isConstrainedHighSpeed*/false); 557 } 558 559 @Override 560 public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs, 561 android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) 562 throws CameraAccessException { 563 if (outputs == null || outputs.size() == 0 || outputs.size() > 2) { 564 throw new IllegalArgumentException( 565 "Output surface list must not be null and the size must be no more than 2"); 566 } 567 StreamConfigurationMap config = 568 getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 569 SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config); 570 571 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); 572 for (Surface surface : outputs) { 573 outConfigurations.add(new OutputConfiguration(surface)); 574 } 575 createCaptureSessionInternal(null, outConfigurations, callback, handler, 576 /*isConstrainedHighSpeed*/true); 577 } 578 579 private void createCaptureSessionInternal(InputConfiguration inputConfig, 580 List<OutputConfiguration> outputConfigurations, 581 CameraCaptureSession.StateCallback callback, Handler handler, 582 boolean isConstrainedHighSpeed) throws CameraAccessException { 583 synchronized(mInterfaceLock) { 584 if (DEBUG) { 585 Log.d(TAG, "createCaptureSessionInternal"); 586 } 587 588 checkIfCameraClosedOrInError(); 589 590 if (isConstrainedHighSpeed && inputConfig != null) { 591 throw new IllegalArgumentException("Constrained high speed session doesn't support" 592 + " input configuration yet."); 593 } 594 595 // Notify current session that it's going away, before starting camera operations 596 // After this call completes, the session is not allowed to call into CameraDeviceImpl 597 if (mCurrentSession != null) { 598 mCurrentSession.replaceSessionClose(); 599 } 600 601 // TODO: dont block for this 602 boolean configureSuccess = true; 603 CameraAccessException pendingException = null; 604 Surface input = null; 605 try { 606 // configure streams and then block until IDLE 607 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations, 608 isConstrainedHighSpeed); 609 if (configureSuccess == true && inputConfig != null) { 610 input = mRemoteDevice.getInputSurface(); 611 } 612 } catch (CameraAccessException e) { 613 configureSuccess = false; 614 pendingException = e; 615 input = null; 616 if (DEBUG) { 617 Log.v(TAG, "createCaptureSession - failed with exception ", e); 618 } 619 } 620 621 List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); 622 for (OutputConfiguration config : outputConfigurations) { 623 outSurfaces.add(config.getSurface()); 624 } 625 // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. 626 CameraCaptureSessionCore newSession = null; 627 if (isConstrainedHighSpeed) { 628 newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++, 629 outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess, 630 mCharacteristics); 631 } else { 632 newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, 633 outSurfaces, callback, handler, this, mDeviceHandler, 634 configureSuccess); 635 } 636 637 // TODO: wait until current session closes, then create the new session 638 mCurrentSession = newSession; 639 640 if (pendingException != null) { 641 throw pendingException; 642 } 643 644 mSessionStateCallback = mCurrentSession.getDeviceStateCallback(); 645 } 646 } 647 648 /** 649 * For use by backwards-compatibility code only. 650 */ 651 public void setSessionListener(StateCallbackKK sessionCallback) { 652 synchronized(mInterfaceLock) { 653 mSessionStateCallback = sessionCallback; 654 } 655 } 656 657 @Override 658 public CaptureRequest.Builder createCaptureRequest(int templateType) 659 throws CameraAccessException { 660 synchronized(mInterfaceLock) { 661 checkIfCameraClosedOrInError(); 662 663 CameraMetadataNative templatedRequest = null; 664 665 templatedRequest = mRemoteDevice.createDefaultRequest(templateType); 666 667 CaptureRequest.Builder builder = new CaptureRequest.Builder( 668 templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); 669 670 return builder; 671 } 672 } 673 674 @Override 675 public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult) 676 throws CameraAccessException { 677 synchronized(mInterfaceLock) { 678 checkIfCameraClosedOrInError(); 679 680 CameraMetadataNative resultMetadata = new 681 CameraMetadataNative(inputResult.getNativeCopy()); 682 683 return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, 684 inputResult.getSessionId()); 685 } 686 } 687 688 public void prepare(Surface surface) throws CameraAccessException { 689 if (surface == null) throw new IllegalArgumentException("Surface is null"); 690 691 synchronized(mInterfaceLock) { 692 int streamId = -1; 693 for (int i = 0; i < mConfiguredOutputs.size(); i++) { 694 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { 695 streamId = mConfiguredOutputs.keyAt(i); 696 break; 697 } 698 } 699 if (streamId == -1) { 700 throw new IllegalArgumentException("Surface is not part of this session"); 701 } 702 703 mRemoteDevice.prepare(streamId); 704 } 705 } 706 707 public void prepare(int maxCount, Surface surface) throws CameraAccessException { 708 if (surface == null) throw new IllegalArgumentException("Surface is null"); 709 if (maxCount <= 0) throw new IllegalArgumentException("Invalid maxCount given: " + 710 maxCount); 711 712 synchronized(mInterfaceLock) { 713 int streamId = -1; 714 for (int i = 0; i < mConfiguredOutputs.size(); i++) { 715 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { 716 streamId = mConfiguredOutputs.keyAt(i); 717 break; 718 } 719 } 720 if (streamId == -1) { 721 throw new IllegalArgumentException("Surface is not part of this session"); 722 } 723 724 mRemoteDevice.prepare2(maxCount, streamId); 725 } 726 } 727 728 public void tearDown(Surface surface) throws CameraAccessException { 729 if (surface == null) throw new IllegalArgumentException("Surface is null"); 730 731 synchronized(mInterfaceLock) { 732 int streamId = -1; 733 for (int i = 0; i < mConfiguredOutputs.size(); i++) { 734 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { 735 streamId = mConfiguredOutputs.keyAt(i); 736 break; 737 } 738 } 739 if (streamId == -1) { 740 throw new IllegalArgumentException("Surface is not part of this session"); 741 } 742 743 mRemoteDevice.tearDown(streamId); 744 } 745 } 746 747 public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) 748 throws CameraAccessException { 749 if (DEBUG) { 750 Log.d(TAG, "calling capture"); 751 } 752 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); 753 requestList.add(request); 754 return submitCaptureRequest(requestList, callback, handler, /*streaming*/false); 755 } 756 757 public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, 758 Handler handler) throws CameraAccessException { 759 if (requests == null || requests.isEmpty()) { 760 throw new IllegalArgumentException("At least one request must be given"); 761 } 762 return submitCaptureRequest(requests, callback, handler, /*streaming*/false); 763 } 764 765 /** 766 * This method checks lastFrameNumber returned from ICameraDeviceUser methods for 767 * starting and stopping repeating request and flushing. 768 * 769 * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never 770 * sent to HAL. Then onCaptureSequenceAborted is immediately triggered. 771 * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last 772 * regular frame number will be added to the list mRequestLastFrameNumbersList.</p> 773 * 774 * @param requestId the request ID of the current repeating request. 775 * 776 * @param lastFrameNumber last frame number returned from binder. 777 */ 778 private void checkEarlyTriggerSequenceComplete( 779 final int requestId, final long lastFrameNumber) { 780 // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request 781 // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately. 782 if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { 783 final CaptureCallbackHolder holder; 784 int index = mCaptureCallbackMap.indexOfKey(requestId); 785 holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; 786 if (holder != null) { 787 mCaptureCallbackMap.removeAt(index); 788 if (DEBUG) { 789 Log.v(TAG, String.format( 790 "remove holder for requestId %d, " 791 + "because lastFrame is %d.", 792 requestId, lastFrameNumber)); 793 } 794 } 795 796 if (holder != null) { 797 if (DEBUG) { 798 Log.v(TAG, "immediately trigger onCaptureSequenceAborted because" 799 + " request did not reach HAL"); 800 } 801 802 Runnable resultDispatch = new Runnable() { 803 @Override 804 public void run() { 805 if (!CameraDeviceImpl.this.isClosed()) { 806 if (DEBUG) { 807 Log.d(TAG, String.format( 808 "early trigger sequence complete for request %d", 809 requestId)); 810 } 811 holder.getCallback().onCaptureSequenceAborted( 812 CameraDeviceImpl.this, 813 requestId); 814 } 815 } 816 }; 817 holder.getHandler().post(resultDispatch); 818 } else { 819 Log.w(TAG, String.format( 820 "did not register callback to request %d", 821 requestId)); 822 } 823 } else { 824 // This function is only called for regular request so lastFrameNumber is the last 825 // regular frame number. 826 mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId, 827 lastFrameNumber)); 828 829 // It is possible that the last frame has already arrived, so we need to check 830 // for sequence completion right away 831 checkAndFireSequenceComplete(); 832 } 833 } 834 835 private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback, 836 Handler handler, boolean repeating) throws CameraAccessException { 837 838 // Need a valid handler, or current thread needs to have a looper, if 839 // callback is valid 840 handler = checkHandler(handler, callback); 841 842 // Make sure that there all requests have at least 1 surface; all surfaces are non-null 843 for (CaptureRequest request : requestList) { 844 if (request.getTargets().isEmpty()) { 845 throw new IllegalArgumentException( 846 "Each request must have at least one Surface target"); 847 } 848 849 for (Surface surface : request.getTargets()) { 850 if (surface == null) { 851 throw new IllegalArgumentException("Null Surface targets are not allowed"); 852 } 853 } 854 } 855 856 synchronized(mInterfaceLock) { 857 checkIfCameraClosedOrInError(); 858 if (repeating) { 859 stopRepeating(); 860 } 861 862 SubmitInfo requestInfo; 863 864 CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]); 865 requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating); 866 if (DEBUG) { 867 Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber()); 868 } 869 870 if (callback != null) { 871 mCaptureCallbackMap.put(requestInfo.getRequestId(), 872 new CaptureCallbackHolder( 873 callback, requestList, handler, repeating, mNextSessionId - 1)); 874 } else { 875 if (DEBUG) { 876 Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null"); 877 } 878 } 879 880 if (repeating) { 881 if (mRepeatingRequestId != REQUEST_ID_NONE) { 882 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, 883 requestInfo.getLastFrameNumber()); 884 } 885 mRepeatingRequestId = requestInfo.getRequestId(); 886 } else { 887 mRequestLastFrameNumbersList.add( 888 new RequestLastFrameNumbersHolder(requestList, requestInfo)); 889 } 890 891 if (mIdle) { 892 mDeviceHandler.post(mCallOnActive); 893 } 894 mIdle = false; 895 896 return requestInfo.getRequestId(); 897 } 898 } 899 900 public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, 901 Handler handler) throws CameraAccessException { 902 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); 903 requestList.add(request); 904 return submitCaptureRequest(requestList, callback, handler, /*streaming*/true); 905 } 906 907 public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback, 908 Handler handler) throws CameraAccessException { 909 if (requests == null || requests.isEmpty()) { 910 throw new IllegalArgumentException("At least one request must be given"); 911 } 912 return submitCaptureRequest(requests, callback, handler, /*streaming*/true); 913 } 914 915 public void stopRepeating() throws CameraAccessException { 916 917 synchronized(mInterfaceLock) { 918 checkIfCameraClosedOrInError(); 919 if (mRepeatingRequestId != REQUEST_ID_NONE) { 920 921 int requestId = mRepeatingRequestId; 922 mRepeatingRequestId = REQUEST_ID_NONE; 923 924 long lastFrameNumber; 925 try { 926 lastFrameNumber = mRemoteDevice.cancelRequest(requestId); 927 } catch (IllegalArgumentException e) { 928 if (DEBUG) { 929 Log.v(TAG, "Repeating request was already stopped for request " + requestId); 930 } 931 // Repeating request was already stopped. Nothing more to do. 932 return; 933 } 934 935 checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber); 936 } 937 } 938 } 939 940 private void waitUntilIdle() throws CameraAccessException { 941 942 synchronized(mInterfaceLock) { 943 checkIfCameraClosedOrInError(); 944 945 if (mRepeatingRequestId != REQUEST_ID_NONE) { 946 throw new IllegalStateException("Active repeating request ongoing"); 947 } 948 949 mRemoteDevice.waitUntilIdle(); 950 } 951 } 952 953 public void flush() throws CameraAccessException { 954 synchronized(mInterfaceLock) { 955 checkIfCameraClosedOrInError(); 956 957 mDeviceHandler.post(mCallOnBusy); 958 959 // If already idle, just do a busy->idle transition immediately, don't actually 960 // flush. 961 if (mIdle) { 962 mDeviceHandler.post(mCallOnIdle); 963 return; 964 } 965 966 long lastFrameNumber = mRemoteDevice.flush(); 967 if (mRepeatingRequestId != REQUEST_ID_NONE) { 968 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber); 969 mRepeatingRequestId = REQUEST_ID_NONE; 970 } 971 } 972 } 973 974 @Override 975 public void close() { 976 synchronized (mInterfaceLock) { 977 if (mClosing.getAndSet(true)) { 978 return; 979 } 980 981 if (mRemoteDevice != null) { 982 mRemoteDevice.disconnect(); 983 mRemoteDevice.unlinkToDeath(this, /*flags*/0); 984 } 985 986 // Only want to fire the onClosed callback once; 987 // either a normal close where the remote device is valid 988 // or a close after a startup error (no remote device but in error state) 989 if (mRemoteDevice != null || mInError) { 990 mDeviceHandler.post(mCallOnClosed); 991 } 992 993 mRemoteDevice = null; 994 } 995 } 996 997 @Override 998 protected void finalize() throws Throwable { 999 try { 1000 close(); 1001 } 1002 finally { 1003 super.finalize(); 1004 } 1005 } 1006 1007 private void checkInputConfiguration(InputConfiguration inputConfig) { 1008 if (inputConfig != null) { 1009 StreamConfigurationMap configMap = mCharacteristics.get( 1010 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1011 1012 int[] inputFormats = configMap.getInputFormats(); 1013 boolean validFormat = false; 1014 for (int format : inputFormats) { 1015 if (format == inputConfig.getFormat()) { 1016 validFormat = true; 1017 } 1018 } 1019 1020 if (validFormat == false) { 1021 throw new IllegalArgumentException("input format " + inputConfig.getFormat() + 1022 " is not valid"); 1023 } 1024 1025 boolean validSize = false; 1026 Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); 1027 for (Size s : inputSizes) { 1028 if (inputConfig.getWidth() == s.getWidth() && 1029 inputConfig.getHeight() == s.getHeight()) { 1030 validSize = true; 1031 } 1032 } 1033 1034 if (validSize == false) { 1035 throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + 1036 inputConfig.getHeight() + " is not valid"); 1037 } 1038 } 1039 } 1040 1041 /** 1042 * <p>A callback for tracking the progress of a {@link CaptureRequest} 1043 * submitted to the camera device.</p> 1044 * 1045 */ 1046 public static abstract class CaptureCallback { 1047 1048 /** 1049 * This constant is used to indicate that no images were captured for 1050 * the request. 1051 * 1052 * @hide 1053 */ 1054 public static final int NO_FRAMES_CAPTURED = -1; 1055 1056 /** 1057 * This method is called when the camera device has started capturing 1058 * the output image for the request, at the beginning of image exposure. 1059 * 1060 * @see android.media.MediaActionSound 1061 */ 1062 public void onCaptureStarted(CameraDevice camera, 1063 CaptureRequest request, long timestamp, long frameNumber) { 1064 // default empty implementation 1065 } 1066 1067 /** 1068 * This method is called when some results from an image capture are 1069 * available. 1070 * 1071 * @hide 1072 */ 1073 public void onCapturePartial(CameraDevice camera, 1074 CaptureRequest request, CaptureResult result) { 1075 // default empty implementation 1076 } 1077 1078 /** 1079 * This method is called when an image capture makes partial forward progress; some 1080 * (but not all) results from an image capture are available. 1081 * 1082 */ 1083 public void onCaptureProgressed(CameraDevice camera, 1084 CaptureRequest request, CaptureResult partialResult) { 1085 // default empty implementation 1086 } 1087 1088 /** 1089 * This method is called when an image capture has fully completed and all the 1090 * result metadata is available. 1091 */ 1092 public void onCaptureCompleted(CameraDevice camera, 1093 CaptureRequest request, TotalCaptureResult result) { 1094 // default empty implementation 1095 } 1096 1097 /** 1098 * This method is called instead of {@link #onCaptureCompleted} when the 1099 * camera device failed to produce a {@link CaptureResult} for the 1100 * request. 1101 */ 1102 public void onCaptureFailed(CameraDevice camera, 1103 CaptureRequest request, CaptureFailure failure) { 1104 // default empty implementation 1105 } 1106 1107 /** 1108 * This method is called independently of the others in CaptureCallback, 1109 * when a capture sequence finishes and all {@link CaptureResult} 1110 * or {@link CaptureFailure} for it have been returned via this callback. 1111 */ 1112 public void onCaptureSequenceCompleted(CameraDevice camera, 1113 int sequenceId, long frameNumber) { 1114 // default empty implementation 1115 } 1116 1117 /** 1118 * This method is called independently of the others in CaptureCallback, 1119 * when a capture sequence aborts before any {@link CaptureResult} 1120 * or {@link CaptureFailure} for it have been returned via this callback. 1121 */ 1122 public void onCaptureSequenceAborted(CameraDevice camera, 1123 int sequenceId) { 1124 // default empty implementation 1125 } 1126 1127 public void onCaptureBufferLost(CameraDevice camera, 1128 CaptureRequest request, Surface target, long frameNumber) { 1129 // default empty implementation 1130 } 1131 } 1132 1133 /** 1134 * A callback for notifications about the state of a camera device, adding in the callbacks that 1135 * were part of the earlier KK API design, but now only used internally. 1136 */ 1137 public static abstract class StateCallbackKK extends StateCallback { 1138 /** 1139 * The method called when a camera device has no outputs configured. 1140 * 1141 */ 1142 public void onUnconfigured(CameraDevice camera) { 1143 // Default empty implementation 1144 } 1145 1146 /** 1147 * The method called when a camera device begins processing 1148 * {@link CaptureRequest capture requests}. 1149 * 1150 */ 1151 public void onActive(CameraDevice camera) { 1152 // Default empty implementation 1153 } 1154 1155 /** 1156 * The method called when a camera device is busy. 1157 * 1158 */ 1159 public void onBusy(CameraDevice camera) { 1160 // Default empty implementation 1161 } 1162 1163 /** 1164 * The method called when a camera device has finished processing all 1165 * submitted capture requests and has reached an idle state. 1166 * 1167 */ 1168 public void onIdle(CameraDevice camera) { 1169 // Default empty implementation 1170 } 1171 1172 /** 1173 * The method called when the camera device has finished preparing 1174 * an output Surface 1175 */ 1176 public void onSurfacePrepared(Surface surface) { 1177 // Default empty implementation 1178 } 1179 } 1180 1181 static class CaptureCallbackHolder { 1182 1183 private final boolean mRepeating; 1184 private final CaptureCallback mCallback; 1185 private final List<CaptureRequest> mRequestList; 1186 private final Handler mHandler; 1187 private final int mSessionId; 1188 1189 CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList, 1190 Handler handler, boolean repeating, int sessionId) { 1191 if (callback == null || handler == null) { 1192 throw new UnsupportedOperationException( 1193 "Must have a valid handler and a valid callback"); 1194 } 1195 mRepeating = repeating; 1196 mHandler = handler; 1197 mRequestList = new ArrayList<CaptureRequest>(requestList); 1198 mCallback = callback; 1199 mSessionId = sessionId; 1200 } 1201 1202 public boolean isRepeating() { 1203 return mRepeating; 1204 } 1205 1206 public CaptureCallback getCallback() { 1207 return mCallback; 1208 } 1209 1210 public CaptureRequest getRequest(int subsequenceId) { 1211 if (subsequenceId >= mRequestList.size()) { 1212 throw new IllegalArgumentException( 1213 String.format( 1214 "Requested subsequenceId %d is larger than request list size %d.", 1215 subsequenceId, mRequestList.size())); 1216 } else { 1217 if (subsequenceId < 0) { 1218 throw new IllegalArgumentException(String.format( 1219 "Requested subsequenceId %d is negative", subsequenceId)); 1220 } else { 1221 return mRequestList.get(subsequenceId); 1222 } 1223 } 1224 } 1225 1226 public CaptureRequest getRequest() { 1227 return getRequest(0); 1228 } 1229 1230 public Handler getHandler() { 1231 return mHandler; 1232 } 1233 1234 public int getSessionId() { 1235 return mSessionId; 1236 } 1237 } 1238 1239 /** 1240 * This class holds a capture ID and its expected last regular frame number and last reprocess 1241 * frame number. 1242 */ 1243 static class RequestLastFrameNumbersHolder { 1244 // request ID 1245 private final int mRequestId; 1246 // The last regular frame number for this request ID. It's 1247 // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request. 1248 private final long mLastRegularFrameNumber; 1249 // The last reprocess frame number for this request ID. It's 1250 // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request. 1251 private final long mLastReprocessFrameNumber; 1252 1253 /** 1254 * Create a request-last-frame-numbers holder with a list of requests, request ID, and 1255 * the last frame number returned by camera service. 1256 */ 1257 public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, SubmitInfo requestInfo) { 1258 long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1259 long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1260 long frameNumber = requestInfo.getLastFrameNumber(); 1261 1262 if (requestInfo.getLastFrameNumber() < requestList.size() - 1) { 1263 throw new IllegalArgumentException( 1264 "lastFrameNumber: " + requestInfo.getLastFrameNumber() + 1265 " should be at least " + (requestList.size() - 1) + " for the number of " + 1266 " requests in the list: " + requestList.size()); 1267 } 1268 1269 // find the last regular frame number and the last reprocess frame number 1270 for (int i = requestList.size() - 1; i >= 0; i--) { 1271 CaptureRequest request = requestList.get(i); 1272 if (request.isReprocess() && lastReprocessFrameNumber == 1273 CaptureCallback.NO_FRAMES_CAPTURED) { 1274 lastReprocessFrameNumber = frameNumber; 1275 } else if (!request.isReprocess() && lastRegularFrameNumber == 1276 CaptureCallback.NO_FRAMES_CAPTURED) { 1277 lastRegularFrameNumber = frameNumber; 1278 } 1279 1280 if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && 1281 lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) { 1282 break; 1283 } 1284 1285 frameNumber--; 1286 } 1287 1288 mLastRegularFrameNumber = lastRegularFrameNumber; 1289 mLastReprocessFrameNumber = lastReprocessFrameNumber; 1290 mRequestId = requestInfo.getRequestId(); 1291 } 1292 1293 /** 1294 * Create a request-last-frame-numbers holder with a request ID and last regular frame 1295 * number. 1296 */ 1297 public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) { 1298 mLastRegularFrameNumber = lastRegularFrameNumber; 1299 mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1300 mRequestId = requestId; 1301 } 1302 1303 /** 1304 * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if 1305 * it contains no regular request. 1306 */ 1307 public long getLastRegularFrameNumber() { 1308 return mLastRegularFrameNumber; 1309 } 1310 1311 /** 1312 * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if 1313 * it contains no reprocess request. 1314 */ 1315 public long getLastReprocessFrameNumber() { 1316 return mLastReprocessFrameNumber; 1317 } 1318 1319 /** 1320 * Return the last frame number overall. 1321 */ 1322 public long getLastFrameNumber() { 1323 return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber); 1324 } 1325 1326 /** 1327 * Return the request ID. 1328 */ 1329 public int getRequestId() { 1330 return mRequestId; 1331 } 1332 } 1333 1334 /** 1335 * This class tracks the last frame number for submitted requests. 1336 */ 1337 public class FrameNumberTracker { 1338 1339 private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1340 private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1341 /** the skipped frame numbers that belong to regular results */ 1342 private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); 1343 /** the skipped frame numbers that belong to reprocess results */ 1344 private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>(); 1345 /** frame number -> is reprocess */ 1346 private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>(); 1347 /** Map frame numbers to list of partial results */ 1348 private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>(); 1349 1350 private void update() { 1351 Iterator iter = mFutureErrorMap.entrySet().iterator(); 1352 while (iter.hasNext()) { 1353 TreeMap.Entry pair = (TreeMap.Entry)iter.next(); 1354 Long errorFrameNumber = (Long)pair.getKey(); 1355 Boolean reprocess = (Boolean)pair.getValue(); 1356 Boolean removeError = true; 1357 if (reprocess) { 1358 if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) { 1359 mCompletedReprocessFrameNumber = errorFrameNumber; 1360 } else if (mSkippedReprocessFrameNumbers.isEmpty() != true && 1361 errorFrameNumber == mSkippedReprocessFrameNumbers.element()) { 1362 mCompletedReprocessFrameNumber = errorFrameNumber; 1363 mSkippedReprocessFrameNumbers.remove(); 1364 } else { 1365 removeError = false; 1366 } 1367 } else { 1368 if (errorFrameNumber == mCompletedFrameNumber + 1) { 1369 mCompletedFrameNumber = errorFrameNumber; 1370 } else if (mSkippedRegularFrameNumbers.isEmpty() != true && 1371 errorFrameNumber == mSkippedRegularFrameNumbers.element()) { 1372 mCompletedFrameNumber = errorFrameNumber; 1373 mSkippedRegularFrameNumbers.remove(); 1374 } else { 1375 removeError = false; 1376 } 1377 } 1378 if (removeError) { 1379 iter.remove(); 1380 } 1381 } 1382 } 1383 1384 /** 1385 * This function is called every time when a result or an error is received. 1386 * @param frameNumber the frame number corresponding to the result or error 1387 * @param isError true if it is an error, false if it is not an error 1388 * @param isReprocess true if it is a reprocess result, false if it is a regular result. 1389 */ 1390 public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) { 1391 if (isError) { 1392 mFutureErrorMap.put(frameNumber, isReprocess); 1393 } else { 1394 try { 1395 if (isReprocess) { 1396 updateCompletedReprocessFrameNumber(frameNumber); 1397 } else { 1398 updateCompletedFrameNumber(frameNumber); 1399 } 1400 } catch (IllegalArgumentException e) { 1401 Log.e(TAG, e.getMessage()); 1402 } 1403 } 1404 update(); 1405 } 1406 1407 /** 1408 * This function is called every time a result has been completed. 1409 * 1410 * <p>It keeps a track of all the partial results already created for a particular 1411 * frame number.</p> 1412 * 1413 * @param frameNumber the frame number corresponding to the result 1414 * @param result the total or partial result 1415 * @param partial {@true} if the result is partial, {@code false} if total 1416 * @param isReprocess true if it is a reprocess result, false if it is a regular result. 1417 */ 1418 public void updateTracker(long frameNumber, CaptureResult result, boolean partial, 1419 boolean isReprocess) { 1420 if (!partial) { 1421 // Update the total result's frame status as being successful 1422 updateTracker(frameNumber, /*isError*/false, isReprocess); 1423 // Don't keep a list of total results, we don't need to track them 1424 return; 1425 } 1426 1427 if (result == null) { 1428 // Do not record blank results; this also means there will be no total result 1429 // so it doesn't matter that the partials were not recorded 1430 return; 1431 } 1432 1433 // Partial results must be aggregated in-order for that frame number 1434 List<CaptureResult> partials = mPartialResults.get(frameNumber); 1435 if (partials == null) { 1436 partials = new ArrayList<>(); 1437 mPartialResults.put(frameNumber, partials); 1438 } 1439 1440 partials.add(result); 1441 } 1442 1443 /** 1444 * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}. 1445 * 1446 * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker} 1447 * is called again with new partials for that frame number).</p> 1448 * 1449 * @param frameNumber the frame number corresponding to the result 1450 * @return a list of partial results for that frame with at least 1 element, 1451 * or {@code null} if there were no partials recorded for that frame 1452 */ 1453 public List<CaptureResult> popPartialResults(long frameNumber) { 1454 return mPartialResults.remove(frameNumber); 1455 } 1456 1457 public long getCompletedFrameNumber() { 1458 return mCompletedFrameNumber; 1459 } 1460 1461 public long getCompletedReprocessFrameNumber() { 1462 return mCompletedReprocessFrameNumber; 1463 } 1464 1465 /** 1466 * Update the completed frame number for regular results. 1467 * 1468 * It validates that all previous frames have arrived except for reprocess frames. 1469 * 1470 * If there is a gap since previous regular frame number, assume the frames in the gap are 1471 * reprocess frames and store them in the skipped reprocess frame number queue to check 1472 * against when reprocess frames arrive. 1473 */ 1474 private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException { 1475 if (frameNumber <= mCompletedFrameNumber) { 1476 throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); 1477 } else if (frameNumber <= mCompletedReprocessFrameNumber) { 1478 // if frame number is smaller than completed reprocess frame number, 1479 // it must be the head of mSkippedRegularFrameNumbers 1480 if (mSkippedRegularFrameNumbers.isEmpty() == true || 1481 frameNumber < mSkippedRegularFrameNumbers.element()) { 1482 throw new IllegalArgumentException("frame number " + frameNumber + 1483 " is a repeat"); 1484 } else if (frameNumber > mSkippedRegularFrameNumbers.element()) { 1485 throw new IllegalArgumentException("frame number " + frameNumber + 1486 " comes out of order. Expecting " + 1487 mSkippedRegularFrameNumbers.element()); 1488 } 1489 // frame number matches the head of the skipped frame number queue. 1490 mSkippedRegularFrameNumbers.remove(); 1491 } else { 1492 // there is a gap of unseen frame numbers which should belong to reprocess result 1493 // put all the skipped frame numbers in the queue 1494 for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; 1495 i < frameNumber; i++) { 1496 mSkippedReprocessFrameNumbers.add(i); 1497 } 1498 } 1499 1500 mCompletedFrameNumber = frameNumber; 1501 } 1502 1503 /** 1504 * Update the completed frame number for reprocess results. 1505 * 1506 * It validates that all previous frames have arrived except for regular frames. 1507 * 1508 * If there is a gap since previous reprocess frame number, assume the frames in the gap are 1509 * regular frames and store them in the skipped regular frame number queue to check 1510 * against when regular frames arrive. 1511 */ 1512 private void updateCompletedReprocessFrameNumber(long frameNumber) 1513 throws IllegalArgumentException { 1514 if (frameNumber < mCompletedReprocessFrameNumber) { 1515 throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); 1516 } else if (frameNumber < mCompletedFrameNumber) { 1517 // if reprocess frame number is smaller than completed regular frame number, 1518 // it must be the head of the skipped reprocess frame number queue. 1519 if (mSkippedReprocessFrameNumbers.isEmpty() == true || 1520 frameNumber < mSkippedReprocessFrameNumbers.element()) { 1521 throw new IllegalArgumentException("frame number " + frameNumber + 1522 " is a repeat"); 1523 } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) { 1524 throw new IllegalArgumentException("frame number " + frameNumber + 1525 " comes out of order. Expecting " + 1526 mSkippedReprocessFrameNumbers.element()); 1527 } 1528 // frame number matches the head of the skipped frame number queue. 1529 mSkippedReprocessFrameNumbers.remove(); 1530 } else { 1531 // put all the skipped frame numbers in the queue 1532 for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; 1533 i < frameNumber; i++) { 1534 mSkippedRegularFrameNumbers.add(i); 1535 } 1536 } 1537 mCompletedReprocessFrameNumber = frameNumber; 1538 } 1539 } 1540 1541 private void checkAndFireSequenceComplete() { 1542 long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); 1543 long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); 1544 boolean isReprocess = false; 1545 Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); 1546 while (iter.hasNext()) { 1547 final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); 1548 boolean sequenceCompleted = false; 1549 final int requestId = requestLastFrameNumbers.getRequestId(); 1550 final CaptureCallbackHolder holder; 1551 synchronized(mInterfaceLock) { 1552 if (mRemoteDevice == null) { 1553 Log.w(TAG, "Camera closed while checking sequences"); 1554 return; 1555 } 1556 1557 int index = mCaptureCallbackMap.indexOfKey(requestId); 1558 holder = (index >= 0) ? 1559 mCaptureCallbackMap.valueAt(index) : null; 1560 if (holder != null) { 1561 long lastRegularFrameNumber = 1562 requestLastFrameNumbers.getLastRegularFrameNumber(); 1563 long lastReprocessFrameNumber = 1564 requestLastFrameNumbers.getLastReprocessFrameNumber(); 1565 1566 // check if it's okay to remove request from mCaptureCallbackMap 1567 if (lastRegularFrameNumber <= completedFrameNumber && 1568 lastReprocessFrameNumber <= completedReprocessFrameNumber) { 1569 sequenceCompleted = true; 1570 mCaptureCallbackMap.removeAt(index); 1571 if (DEBUG) { 1572 Log.v(TAG, String.format( 1573 "Remove holder for requestId %d, because lastRegularFrame %d " + 1574 "is <= %d and lastReprocessFrame %d is <= %d", requestId, 1575 lastRegularFrameNumber, completedFrameNumber, 1576 lastReprocessFrameNumber, completedReprocessFrameNumber)); 1577 } 1578 } 1579 } 1580 } 1581 1582 // If no callback is registered for this requestId or sequence completed, remove it 1583 // from the frame number->request pair because it's not needed anymore. 1584 if (holder == null || sequenceCompleted) { 1585 iter.remove(); 1586 } 1587 1588 // Call onCaptureSequenceCompleted 1589 if (sequenceCompleted) { 1590 Runnable resultDispatch = new Runnable() { 1591 @Override 1592 public void run() { 1593 if (!CameraDeviceImpl.this.isClosed()){ 1594 if (DEBUG) { 1595 Log.d(TAG, String.format( 1596 "fire sequence complete for request %d", 1597 requestId)); 1598 } 1599 1600 holder.getCallback().onCaptureSequenceCompleted( 1601 CameraDeviceImpl.this, 1602 requestId, 1603 requestLastFrameNumbers.getLastFrameNumber()); 1604 } 1605 } 1606 }; 1607 holder.getHandler().post(resultDispatch); 1608 } 1609 } 1610 } 1611 1612 public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { 1613 1614 @Override 1615 public IBinder asBinder() { 1616 return this; 1617 } 1618 1619 @Override 1620 public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) { 1621 if (DEBUG) { 1622 Log.d(TAG, String.format( 1623 "Device error received, code %d, frame number %d, request ID %d, subseq ID %d", 1624 errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(), 1625 resultExtras.getSubsequenceId())); 1626 } 1627 1628 synchronized(mInterfaceLock) { 1629 if (mRemoteDevice == null) { 1630 return; // Camera already closed 1631 } 1632 1633 switch (errorCode) { 1634 case ERROR_CAMERA_DISCONNECTED: 1635 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); 1636 break; 1637 default: 1638 Log.e(TAG, "Unknown error from camera device: " + errorCode); 1639 // no break 1640 case ERROR_CAMERA_DEVICE: 1641 case ERROR_CAMERA_SERVICE: 1642 mInError = true; 1643 final int publicErrorCode = (errorCode == ERROR_CAMERA_DEVICE) ? 1644 StateCallback.ERROR_CAMERA_DEVICE : 1645 StateCallback.ERROR_CAMERA_SERVICE; 1646 Runnable r = new Runnable() { 1647 @Override 1648 public void run() { 1649 if (!CameraDeviceImpl.this.isClosed()) { 1650 mDeviceCallback.onError(CameraDeviceImpl.this, publicErrorCode); 1651 } 1652 } 1653 }; 1654 CameraDeviceImpl.this.mDeviceHandler.post(r); 1655 break; 1656 case ERROR_CAMERA_REQUEST: 1657 case ERROR_CAMERA_RESULT: 1658 case ERROR_CAMERA_BUFFER: 1659 onCaptureErrorLocked(errorCode, resultExtras); 1660 break; 1661 } 1662 } 1663 } 1664 1665 @Override 1666 public void onRepeatingRequestError(long lastFrameNumber) { 1667 if (DEBUG) { 1668 Log.d(TAG, "Repeating request error received. Last frame number is " + 1669 lastFrameNumber); 1670 } 1671 1672 synchronized(mInterfaceLock) { 1673 // Camera is already closed or no repeating request is present. 1674 if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) { 1675 return; // Camera already closed 1676 } 1677 1678 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber); 1679 mRepeatingRequestId = REQUEST_ID_NONE; 1680 } 1681 } 1682 1683 @Override 1684 public void onDeviceIdle() { 1685 if (DEBUG) { 1686 Log.d(TAG, "Camera now idle"); 1687 } 1688 synchronized(mInterfaceLock) { 1689 if (mRemoteDevice == null) return; // Camera already closed 1690 1691 if (!CameraDeviceImpl.this.mIdle) { 1692 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle); 1693 } 1694 CameraDeviceImpl.this.mIdle = true; 1695 } 1696 } 1697 1698 @Override 1699 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { 1700 int requestId = resultExtras.getRequestId(); 1701 final long frameNumber = resultExtras.getFrameNumber(); 1702 1703 if (DEBUG) { 1704 Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber); 1705 } 1706 final CaptureCallbackHolder holder; 1707 1708 synchronized(mInterfaceLock) { 1709 if (mRemoteDevice == null) return; // Camera already closed 1710 1711 // Get the callback for this frame ID, if there is one 1712 holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); 1713 1714 if (holder == null) { 1715 return; 1716 } 1717 1718 if (isClosed()) return; 1719 1720 // Dispatch capture start notice 1721 holder.getHandler().post( 1722 new Runnable() { 1723 @Override 1724 public void run() { 1725 if (!CameraDeviceImpl.this.isClosed()) { 1726 holder.getCallback().onCaptureStarted( 1727 CameraDeviceImpl.this, 1728 holder.getRequest(resultExtras.getSubsequenceId()), 1729 timestamp, frameNumber); 1730 } 1731 } 1732 }); 1733 1734 } 1735 } 1736 1737 @Override 1738 public void onResultReceived(CameraMetadataNative result, 1739 CaptureResultExtras resultExtras) throws RemoteException { 1740 1741 int requestId = resultExtras.getRequestId(); 1742 long frameNumber = resultExtras.getFrameNumber(); 1743 1744 if (DEBUG) { 1745 Log.v(TAG, "Received result frame " + frameNumber + " for id " 1746 + requestId); 1747 } 1748 1749 synchronized(mInterfaceLock) { 1750 if (mRemoteDevice == null) return; // Camera already closed 1751 1752 // TODO: Handle CameraCharacteristics access from CaptureResult correctly. 1753 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, 1754 getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); 1755 1756 final CaptureCallbackHolder holder = 1757 CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); 1758 final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); 1759 1760 boolean isPartialResult = 1761 (resultExtras.getPartialResultCount() < mTotalPartialCount); 1762 boolean isReprocess = request.isReprocess(); 1763 1764 // Check if we have a callback for this 1765 if (holder == null) { 1766 if (DEBUG) { 1767 Log.d(TAG, 1768 "holder is null, early return at frame " 1769 + frameNumber); 1770 } 1771 1772 mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, 1773 isReprocess); 1774 1775 return; 1776 } 1777 1778 if (isClosed()) { 1779 if (DEBUG) { 1780 Log.d(TAG, 1781 "camera is closed, early return at frame " 1782 + frameNumber); 1783 } 1784 1785 mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, 1786 isReprocess); 1787 return; 1788 } 1789 1790 1791 Runnable resultDispatch = null; 1792 1793 CaptureResult finalResult; 1794 1795 // Either send a partial result or the final capture completed result 1796 if (isPartialResult) { 1797 final CaptureResult resultAsCapture = 1798 new CaptureResult(result, request, resultExtras); 1799 1800 // Partial result 1801 resultDispatch = new Runnable() { 1802 @Override 1803 public void run() { 1804 if (!CameraDeviceImpl.this.isClosed()){ 1805 holder.getCallback().onCaptureProgressed( 1806 CameraDeviceImpl.this, 1807 request, 1808 resultAsCapture); 1809 } 1810 } 1811 }; 1812 1813 finalResult = resultAsCapture; 1814 } else { 1815 List<CaptureResult> partialResults = 1816 mFrameNumberTracker.popPartialResults(frameNumber); 1817 1818 final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, 1819 request, resultExtras, partialResults, holder.getSessionId()); 1820 1821 // Final capture result 1822 resultDispatch = new Runnable() { 1823 @Override 1824 public void run() { 1825 if (!CameraDeviceImpl.this.isClosed()){ 1826 holder.getCallback().onCaptureCompleted( 1827 CameraDeviceImpl.this, 1828 request, 1829 resultAsCapture); 1830 } 1831 } 1832 }; 1833 1834 finalResult = resultAsCapture; 1835 } 1836 1837 holder.getHandler().post(resultDispatch); 1838 1839 // Collect the partials for a total result; or mark the frame as totally completed 1840 mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, 1841 isReprocess); 1842 1843 // Fire onCaptureSequenceCompleted 1844 if (!isPartialResult) { 1845 checkAndFireSequenceComplete(); 1846 } 1847 } 1848 } 1849 1850 @Override 1851 public void onPrepared(int streamId) { 1852 final OutputConfiguration output; 1853 final StateCallbackKK sessionCallback; 1854 1855 if (DEBUG) { 1856 Log.v(TAG, "Stream " + streamId + " is prepared"); 1857 } 1858 1859 synchronized(mInterfaceLock) { 1860 output = mConfiguredOutputs.get(streamId); 1861 sessionCallback = mSessionStateCallback; 1862 } 1863 1864 if (sessionCallback == null) return; 1865 1866 if (output == null) { 1867 Log.w(TAG, "onPrepared invoked for unknown output Surface"); 1868 return; 1869 } 1870 final Surface surface = output.getSurface(); 1871 1872 sessionCallback.onSurfacePrepared(surface); 1873 } 1874 1875 /** 1876 * Called by onDeviceError for handling single-capture failures. 1877 */ 1878 private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) { 1879 1880 final int requestId = resultExtras.getRequestId(); 1881 final int subsequenceId = resultExtras.getSubsequenceId(); 1882 final long frameNumber = resultExtras.getFrameNumber(); 1883 final CaptureCallbackHolder holder = 1884 CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); 1885 1886 final CaptureRequest request = holder.getRequest(subsequenceId); 1887 1888 Runnable failureDispatch = null; 1889 if (errorCode == ERROR_CAMERA_BUFFER) { 1890 final Surface outputSurface = 1891 mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurface(); 1892 if (DEBUG) { 1893 Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s", 1894 frameNumber, outputSurface)); 1895 } 1896 failureDispatch = new Runnable() { 1897 @Override 1898 public void run() { 1899 if (!CameraDeviceImpl.this.isClosed()){ 1900 holder.getCallback().onCaptureBufferLost( 1901 CameraDeviceImpl.this, 1902 request, 1903 outputSurface, 1904 frameNumber); 1905 } 1906 } 1907 }; 1908 } else { 1909 boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT); 1910 1911 // This is only approximate - exact handling needs the camera service and HAL to 1912 // disambiguate between request failures to due abort and due to real errors. For 1913 // now, assume that if the session believes we're mid-abort, then the error is due 1914 // to abort. 1915 int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ? 1916 CaptureFailure.REASON_FLUSHED : 1917 CaptureFailure.REASON_ERROR; 1918 1919 final CaptureFailure failure = new CaptureFailure( 1920 request, 1921 reason, 1922 /*dropped*/ mayHaveBuffers, 1923 requestId, 1924 frameNumber); 1925 1926 failureDispatch = new Runnable() { 1927 @Override 1928 public void run() { 1929 if (!CameraDeviceImpl.this.isClosed()){ 1930 holder.getCallback().onCaptureFailed( 1931 CameraDeviceImpl.this, 1932 request, 1933 failure); 1934 } 1935 } 1936 }; 1937 1938 // Fire onCaptureSequenceCompleted if appropriate 1939 if (DEBUG) { 1940 Log.v(TAG, String.format("got error frame %d", frameNumber)); 1941 } 1942 mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess()); 1943 checkAndFireSequenceComplete(); 1944 } 1945 1946 // Dispatch the failure callback 1947 holder.getHandler().post(failureDispatch); 1948 } 1949 1950 } // public class CameraDeviceCallbacks 1951 1952 /** 1953 * Default handler management. 1954 * 1955 * <p> 1956 * If handler is null, get the current thread's 1957 * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}. 1958 * </p> 1959 */ 1960 static Handler checkHandler(Handler handler) { 1961 if (handler == null) { 1962 Looper looper = Looper.myLooper(); 1963 if (looper == null) { 1964 throw new IllegalArgumentException( 1965 "No handler given, and current thread has no looper!"); 1966 } 1967 handler = new Handler(looper); 1968 } 1969 return handler; 1970 } 1971 1972 /** 1973 * Default handler management, conditional on there being a callback. 1974 * 1975 * <p>If the callback isn't null, check the handler, otherwise pass it through.</p> 1976 */ 1977 static <T> Handler checkHandler(Handler handler, T callback) { 1978 if (callback != null) { 1979 return checkHandler(handler); 1980 } 1981 return handler; 1982 } 1983 1984 private void checkIfCameraClosedOrInError() throws CameraAccessException { 1985 if (mRemoteDevice == null) { 1986 throw new IllegalStateException("CameraDevice was already closed"); 1987 } 1988 if (mInError) { 1989 throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, 1990 "The camera device has encountered a serious error"); 1991 } 1992 } 1993 1994 /** Whether the camera device has started to close (may not yet have finished) */ 1995 private boolean isClosed() { 1996 return mClosing.get(); 1997 } 1998 1999 private CameraCharacteristics getCharacteristics() { 2000 return mCharacteristics; 2001 } 2002 2003 /** 2004 * Listener for binder death. 2005 * 2006 * <p> Handle binder death for ICameraDeviceUser. Trigger onError.</p> 2007 */ 2008 public void binderDied() { 2009 Log.w(TAG, "CameraDevice " + mCameraId + " died unexpectedly"); 2010 2011 if (mRemoteDevice == null) { 2012 return; // Camera already closed 2013 } 2014 2015 mInError = true; 2016 Runnable r = new Runnable() { 2017 @Override 2018 public void run() { 2019 if (!isClosed()) { 2020 mDeviceCallback.onError(CameraDeviceImpl.this, 2021 StateCallback.ERROR_CAMERA_SERVICE); 2022 } 2023 } 2024 }; 2025 CameraDeviceImpl.this.mDeviceHandler.post(r); 2026 } 2027} 2028