1 /* 2 * Copyright 2020 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 17 package androidx.camera.camera2.internal; 18 19 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY; 20 21 import static androidx.camera.core.ImageCapture.FLASH_MODE_AUTO; 22 import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF; 23 import static androidx.camera.core.ImageCapture.FLASH_MODE_ON; 24 25 import android.graphics.Rect; 26 import android.hardware.camera2.CameraCaptureSession; 27 import android.hardware.camera2.CameraCaptureSession.CaptureCallback; 28 import android.hardware.camera2.CameraCharacteristics; 29 import android.hardware.camera2.CameraDevice; 30 import android.hardware.camera2.CaptureRequest; 31 import android.hardware.camera2.TotalCaptureResult; 32 import android.os.Build; 33 import android.util.ArrayMap; 34 import android.util.Rational; 35 36 import androidx.annotation.GuardedBy; 37 import androidx.annotation.IntRange; 38 import androidx.annotation.OptIn; 39 import androidx.annotation.VisibleForTesting; 40 import androidx.camera.camera2.impl.Camera2ImplConfig; 41 import androidx.camera.camera2.internal.annotation.CameraExecutor; 42 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat; 43 import androidx.camera.camera2.internal.compat.workaround.AutoFlashAEModeDisabler; 44 import androidx.camera.camera2.interop.Camera2CameraControl; 45 import androidx.camera.camera2.interop.CaptureRequestOptions; 46 import androidx.camera.camera2.interop.ExperimentalCamera2Interop; 47 import androidx.camera.core.FocusMeteringAction; 48 import androidx.camera.core.FocusMeteringResult; 49 import androidx.camera.core.ImageCapture; 50 import androidx.camera.core.ImageCapture.ScreenFlash; 51 import androidx.camera.core.Logger; 52 import androidx.camera.core.imagecapture.CameraCapturePipeline; 53 import androidx.camera.core.impl.CameraCaptureCallback; 54 import androidx.camera.core.impl.CameraCaptureFailure; 55 import androidx.camera.core.impl.CameraCaptureResult; 56 import androidx.camera.core.impl.CameraControlInternal; 57 import androidx.camera.core.impl.CaptureConfig; 58 import androidx.camera.core.impl.Config; 59 import androidx.camera.core.impl.Quirks; 60 import androidx.camera.core.impl.SessionConfig; 61 import androidx.camera.core.impl.TagBundle; 62 import androidx.camera.core.impl.annotation.ExecutedBy; 63 import androidx.camera.core.impl.utils.executor.CameraXExecutors; 64 import androidx.camera.core.impl.utils.futures.FutureChain; 65 import androidx.camera.core.impl.utils.futures.Futures; 66 import androidx.concurrent.futures.CallbackToFutureAdapter; 67 import androidx.core.util.Preconditions; 68 69 import com.google.common.util.concurrent.ListenableFuture; 70 71 import org.jspecify.annotations.NonNull; 72 import org.jspecify.annotations.Nullable; 73 74 import java.util.ArrayList; 75 import java.util.Collections; 76 import java.util.HashSet; 77 import java.util.List; 78 import java.util.Map; 79 import java.util.Set; 80 import java.util.concurrent.Executor; 81 import java.util.concurrent.RejectedExecutionException; 82 import java.util.concurrent.ScheduledExecutorService; 83 import java.util.concurrent.atomic.AtomicLong; 84 85 /** 86 * A Camera2 implementation for CameraControlInternal interface 87 * 88 * <p>There are 2 states in the control, use count and active boolean. Use count controls 89 * whether the user can submit new requests, it can be increased or decreased via 90 * {@link #incrementUseCount()} and {@link #decrementUseCount()}. Before sending the request to 91 * the control, it must increase use count, otherwise the request will be dropped. Active state 92 * controls whether the requests are sent to the camera. It can be set via 93 * {@link #setActive(boolean)}. The transition of active boolean from {@code true} to {@code 94 * false} may also reset state. 95 * 96 * <p>There are 4 possible state combinations when processing a request. 97 * 98 * <ul> 99 * <li>Use count >= 1 but active boolean == false: the control can accept new requests for 100 * changing parameters, but won't attempt to send them to the camera device yet. New requests can 101 * be either cached and replace old requests, or may end with {@code ImmediateFailedFuture} 102 * directly, depending on whether the type of request needs to be cached reasonably.</li> 103 * <li>Use count >= 1 and active boolean is true: the control now sends cached requests to the 104 * camera. Any new requests are also sent directly to the camera.</li> 105 * <li>Use count == 0 and active boolean is true: This state may not be possible or may be very 106 * short lived depending on how we want to use it. the control does not accept new requests; 107 * all requests end in {@code ImmediateFailedFuture}. Previously cached requests may continue 108 * processing.</li> 109 * <li>Use count == 0 and active boolean is false: the control does not accept new requests; all 110 * requests end in {@code ImmediateFailedFuture}. Any cached requests are dropped.</li> 111 * </ul> 112 */ 113 @OptIn(markerClass = ExperimentalCamera2Interop.class) 114 public class Camera2CameraControlImpl implements CameraControlInternal { 115 private static final String TAG = "Camera2CameraControlImp"; 116 private static final int DEFAULT_TEMPLATE = CameraDevice.TEMPLATE_PREVIEW; 117 @VisibleForTesting 118 final CameraControlSessionCallback mSessionCallback; 119 @SuppressWarnings("WeakerAccess") /* synthetic accessor */ 120 @CameraExecutor 121 final Executor mExecutor; 122 private final Object mLock = new Object(); 123 private final CameraCharacteristicsCompat mCameraCharacteristics; 124 private final ControlUpdateCallback mControlUpdateCallback; 125 126 private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder(); 127 private final FocusMeteringControl mFocusMeteringControl; 128 private final ZoomControl mZoomControl; 129 private final TorchControl mTorchControl; 130 private final LowLightBoostControl mLowLightBoostControl; 131 private final ExposureControl mExposureControl; 132 @VisibleForTesting 133 ZslControl mZslControl; 134 private final Camera2CameraControl mCamera2CameraControl; 135 private final Camera2CapturePipeline mCamera2CapturePipeline; 136 private final VideoUsageControl mVideoUsageControl; 137 @GuardedBy("mLock") 138 private int mUseCount = 0; 139 140 private ImageCapture.ScreenFlash mScreenFlash; 141 142 // use volatile modifier to make these variables in sync in all threads. 143 @TorchControl.TorchStateInternal 144 private volatile int mTorchState = TorchControl.OFF; 145 @IntRange(from = 1) 146 private volatile int mTorchStrength; 147 private volatile boolean mIsLowLightBoostOn = false; 148 @ImageCapture.FlashMode 149 private volatile int mFlashMode = FLASH_MODE_OFF; 150 151 // Workarounds 152 private final AutoFlashAEModeDisabler mAutoFlashAEModeDisabler; 153 154 static final String TAG_SESSION_UPDATE_ID = "CameraControlSessionUpdateId"; 155 private final AtomicLong mNextSessionUpdateId = new AtomicLong(0); 156 private volatile @NonNull ListenableFuture<Void> mFlashModeChangeSessionUpdateFuture = 157 Futures.immediateFuture(null); 158 159 //******************** Should only be accessed by executor *****************************// 160 private int mTemplate = DEFAULT_TEMPLATE; 161 // SessionUpdateId will auto-increment every time session updates. 162 private long mCurrentSessionUpdateId = 0; 163 private final CameraCaptureCallbackSet mCameraCaptureCallbackSet = 164 new CameraCaptureCallbackSet(); 165 //**************************************************************************************// 166 167 @VisibleForTesting Camera2CameraControlImpl(@onNull CameraCharacteristicsCompat cameraCharacteristics, @NonNull ScheduledExecutorService scheduler, @CameraExecutor @NonNull Executor executor, @NonNull ControlUpdateCallback controlUpdateCallback)168 Camera2CameraControlImpl(@NonNull CameraCharacteristicsCompat cameraCharacteristics, 169 @NonNull ScheduledExecutorService scheduler, 170 @CameraExecutor @NonNull Executor executor, 171 @NonNull ControlUpdateCallback controlUpdateCallback) { 172 this(cameraCharacteristics, scheduler, executor, controlUpdateCallback, 173 new Quirks(new ArrayList<>())); 174 } 175 176 /** 177 * Constructor for a Camera2CameraControlImpl. 178 * 179 * <p>All {@code controlUpdateListener} invocations will be on the provided {@code executor}. 180 * 181 * <p>All tasks scheduled by {@code scheduler} will be immediately executed by {@code executor}. 182 * 183 * @param cameraCharacteristics Characteristics for the camera being controlled. 184 * @param scheduler Scheduler used for scheduling tasks in the future. 185 * @param executor Camera executor for synchronizing and offloading all commands. 186 * @param controlUpdateCallback Listener which will be notified of control changes. 187 * @param cameraQuirks Camera-related quirks of the camera being controlled 188 */ Camera2CameraControlImpl(@onNull CameraCharacteristicsCompat cameraCharacteristics, @NonNull ScheduledExecutorService scheduler, @CameraExecutor @NonNull Executor executor, @NonNull ControlUpdateCallback controlUpdateCallback, final @NonNull Quirks cameraQuirks)189 Camera2CameraControlImpl(@NonNull CameraCharacteristicsCompat cameraCharacteristics, 190 @NonNull ScheduledExecutorService scheduler, 191 @CameraExecutor @NonNull Executor executor, 192 @NonNull ControlUpdateCallback controlUpdateCallback, 193 final @NonNull Quirks cameraQuirks) { 194 mCameraCharacteristics = cameraCharacteristics; 195 mControlUpdateCallback = controlUpdateCallback; 196 mExecutor = executor; 197 mVideoUsageControl = new VideoUsageControl(executor); 198 mSessionCallback = new CameraControlSessionCallback(mExecutor); 199 mSessionConfigBuilder.setTemplateType(mTemplate); 200 mSessionConfigBuilder.addRepeatingCameraCaptureCallback( 201 CaptureCallbackContainer.create(mSessionCallback)); 202 // Adding a callback via SessionConfigBuilder requires a expensive updateSessionConfig 203 // call. mCameraCaptureCallbackset is for enabling dynamically add/remove 204 // CameraCaptureCallback efficiently. 205 mSessionConfigBuilder.addRepeatingCameraCaptureCallback(mCameraCaptureCallbackSet); 206 207 mExposureControl = new ExposureControl(this, mCameraCharacteristics, mExecutor); 208 mFocusMeteringControl = new FocusMeteringControl( 209 this, scheduler, mExecutor, cameraQuirks); 210 mZoomControl = new ZoomControl(this, mCameraCharacteristics, mExecutor); 211 mTorchControl = new TorchControl(this, mCameraCharacteristics, mExecutor); 212 mTorchStrength = mCameraCharacteristics.getDefaultTorchStrengthLevel(); 213 mLowLightBoostControl = new LowLightBoostControl(this, mCameraCharacteristics, mExecutor); 214 if (Build.VERSION.SDK_INT >= 23) { 215 mZslControl = new ZslControlImpl(mCameraCharacteristics, mExecutor); 216 } else { 217 mZslControl = new ZslControlNoOpImpl(); 218 } 219 220 // Workarounds 221 mAutoFlashAEModeDisabler = new AutoFlashAEModeDisabler(cameraQuirks); 222 mCamera2CameraControl = new Camera2CameraControl(this, mExecutor); 223 mCamera2CapturePipeline = new Camera2CapturePipeline(this, mCameraCharacteristics, 224 cameraQuirks, mExecutor, scheduler); 225 } 226 227 /** Increments the use count of the control. */ incrementUseCount()228 void incrementUseCount() { 229 synchronized (mLock) { 230 mUseCount++; 231 } 232 } 233 234 /** 235 * Decrements the use count of the control. 236 * 237 * @throws IllegalStateException if try to decrement the use count to less than zero 238 */ decrementUseCount()239 void decrementUseCount() { 240 synchronized (mLock) { 241 if (mUseCount == 0) { 242 throw new IllegalStateException("Decrementing use count occurs more times than " 243 + "incrementing"); 244 } 245 mUseCount--; 246 } 247 } 248 249 /** 250 * Returns the use count of the control. 251 * 252 * <p>Use count can be increased and decreased via {@link #incrementUseCount()} and 253 * {@link #decrementUseCount()}. Camera control only accepts requests when the use count is 254 * greater than 0. 255 */ 256 @VisibleForTesting getUseCount()257 int getUseCount() { 258 synchronized (mLock) { 259 return mUseCount; 260 } 261 } 262 getZoomControl()263 public @NonNull ZoomControl getZoomControl() { 264 return mZoomControl; 265 } 266 getFocusMeteringControl()267 public @NonNull FocusMeteringControl getFocusMeteringControl() { 268 return mFocusMeteringControl; 269 } 270 getTorchControl()271 public @NonNull TorchControl getTorchControl() { 272 return mTorchControl; 273 } 274 getLowLightBoostControl()275 public @NonNull LowLightBoostControl getLowLightBoostControl() { 276 return mLowLightBoostControl; 277 } 278 getExposureControl()279 public @NonNull ExposureControl getExposureControl() { 280 return mExposureControl; 281 } 282 getZslControl()283 public @NonNull ZslControl getZslControl() { 284 return mZslControl; 285 } 286 getCamera2CameraControl()287 public @NonNull Camera2CameraControl getCamera2CameraControl() { 288 return mCamera2CameraControl; 289 } 290 291 @Override addInteropConfig(@onNull Config config)292 public void addInteropConfig(@NonNull Config config) { 293 ListenableFuture<Void> future = mCamera2CameraControl.addCaptureRequestOptions( 294 CaptureRequestOptions.Builder.from(config).build()); 295 future.addListener(() -> { 296 }, CameraXExecutors.directExecutor()); 297 } 298 299 @Override clearInteropConfig()300 public void clearInteropConfig() { 301 ListenableFuture<Void> future = mCamera2CameraControl.clearCaptureRequestOptions(); 302 future.addListener(() -> { 303 }, CameraXExecutors.directExecutor()); 304 } 305 306 @Override getInteropConfig()307 public @NonNull Config getInteropConfig() { 308 return mCamera2CameraControl.getCamera2ImplConfig(); 309 } 310 311 /** 312 * Set current active state. Set active if it is ready to trigger camera control operation. 313 * 314 * <p>Most operations during inactive state do nothing. Some states are reset to default 315 * once it is changed to inactive state. 316 * 317 * <p>This method should be executed by {@link #mExecutor} only. 318 */ 319 @ExecutedBy("mExecutor") setActive(boolean isActive)320 void setActive(boolean isActive) { 321 Logger.d(TAG, "setActive: isActive = " + isActive); 322 mFocusMeteringControl.setActive(isActive); 323 mZoomControl.setActive(isActive); 324 mLowLightBoostControl.setActive(isActive); 325 mTorchControl.setActive(isActive); 326 mExposureControl.setActive(isActive); 327 mCamera2CameraControl.setActive(isActive); 328 if (!isActive) { 329 mScreenFlash = null; 330 // Since the camera is no longer active, there should not be any recording ongoing with 331 // this camera. If something like persistent recording wants to resume recording with 332 // this camera again, it should update recording status again when being attached. 333 mVideoUsageControl.resetDirectly(); // already in mExecutor i.e. camera thread 334 } 335 } 336 337 @ExecutedBy("mExecutor") setPreviewAspectRatio(@ullable Rational previewAspectRatio)338 public void setPreviewAspectRatio(@Nullable Rational previewAspectRatio) { 339 mFocusMeteringControl.setPreviewAspectRatio(previewAspectRatio); 340 } 341 342 @Override startFocusAndMetering( @onNull FocusMeteringAction action)343 public @NonNull ListenableFuture<FocusMeteringResult> startFocusAndMetering( 344 @NonNull FocusMeteringAction action) { 345 if (!isControlInUse()) { 346 return Futures.immediateFailedFuture( 347 new OperationCanceledException("Camera is not active.")); 348 } 349 return Futures.nonCancellationPropagating( 350 mFocusMeteringControl.startFocusAndMetering(action)); 351 } 352 353 @Override cancelFocusAndMetering()354 public @NonNull ListenableFuture<Void> cancelFocusAndMetering() { 355 if (!isControlInUse()) { 356 return Futures.immediateFailedFuture( 357 new OperationCanceledException("Camera is not active.")); 358 } 359 return Futures.nonCancellationPropagating(mFocusMeteringControl.cancelFocusAndMetering()); 360 } 361 362 @Override setZoomRatio(float ratio)363 public @NonNull ListenableFuture<Void> setZoomRatio(float ratio) { 364 if (!isControlInUse()) { 365 return Futures.immediateFailedFuture( 366 new OperationCanceledException("Camera is not active.")); 367 } 368 return Futures.nonCancellationPropagating(mZoomControl.setZoomRatio(ratio)); 369 } 370 371 @Override setLinearZoom(float linearZoom)372 public @NonNull ListenableFuture<Void> setLinearZoom(float linearZoom) { 373 if (!isControlInUse()) { 374 return Futures.immediateFailedFuture( 375 new OperationCanceledException("Camera is not active.")); 376 } 377 return Futures.nonCancellationPropagating(mZoomControl.setLinearZoom(linearZoom)); 378 } 379 380 @ImageCapture.FlashMode 381 @Override getFlashMode()382 public int getFlashMode() { 383 return mFlashMode; 384 } 385 386 /** {@inheritDoc} */ 387 @Override setFlashMode(@mageCapture.FlashMode int flashMode)388 public void setFlashMode(@ImageCapture.FlashMode int flashMode) { 389 if (!isControlInUse()) { 390 Logger.w(TAG, "Camera is not active."); 391 return; 392 } 393 // update mFlashMode immediately so that following getFlashMode() returns correct value. 394 mFlashMode = flashMode; 395 Logger.d(TAG, "setFlashMode: mFlashMode = " + mFlashMode); 396 397 // Disable ZSL when flash mode is ON or AUTO. 398 mZslControl.setZslDisabledByFlashMode(mFlashMode == FLASH_MODE_ON 399 || mFlashMode == FLASH_MODE_AUTO); 400 401 // On some devices, AE precapture may not work properly if the repeating request to change 402 // the flash mode is not completed. We need to store the future so that AE precapture can 403 // wait for it. 404 mFlashModeChangeSessionUpdateFuture = updateSessionConfigAsync(); 405 } 406 407 /** {@inheritDoc} */ 408 @Override setScreenFlash(@ullable ScreenFlash screenFlash)409 public void setScreenFlash(@Nullable ScreenFlash screenFlash) { 410 mScreenFlash = screenFlash; 411 } 412 getScreenFlash()413 public @Nullable ScreenFlash getScreenFlash() { 414 return mScreenFlash; 415 } 416 417 @Override addZslConfig(SessionConfig.@onNull Builder sessionConfigBuilder)418 public void addZslConfig(SessionConfig.@NonNull Builder sessionConfigBuilder) { 419 mZslControl.addZslConfig(sessionConfigBuilder); 420 } 421 422 @Override clearZslConfig()423 public void clearZslConfig() { 424 mZslControl.clearZslConfig(); 425 } 426 427 @Override setZslDisabledByUserCaseConfig(boolean disabled)428 public void setZslDisabledByUserCaseConfig(boolean disabled) { 429 mZslControl.setZslDisabledByUserCaseConfig(disabled); 430 } 431 432 @Override isZslDisabledByByUserCaseConfig()433 public boolean isZslDisabledByByUserCaseConfig() { 434 return mZslControl.isZslDisabledByUserCaseConfig(); 435 } 436 437 /** {@inheritDoc} */ 438 @Override enableTorch(final boolean torch)439 public @NonNull ListenableFuture<Void> enableTorch(final boolean torch) { 440 if (!isControlInUse()) { 441 return Futures.immediateFailedFuture( 442 new OperationCanceledException("Camera is not active.")); 443 } 444 return Futures.nonCancellationPropagating(mTorchControl.enableTorch(torch)); 445 } 446 447 @Override 448 @ExecutedBy("mExecutor") setLowLightBoostDisabledByUseCaseSessionConfig(boolean disabled)449 public void setLowLightBoostDisabledByUseCaseSessionConfig(boolean disabled) { 450 mLowLightBoostControl.setLowLightBoostDisabledByUseCaseSessionConfig(disabled); 451 } 452 453 /** {@inheritDoc} */ 454 @Override enableLowLightBoostAsync(final boolean lowLightBoost)455 public @NonNull ListenableFuture<Void> enableLowLightBoostAsync(final boolean lowLightBoost) { 456 if (!isControlInUse()) { 457 return Futures.immediateFailedFuture( 458 new OperationCanceledException("Camera is not active.")); 459 } 460 return Futures.nonCancellationPropagating( 461 mLowLightBoostControl.enableLowLightBoost(lowLightBoost)); 462 } 463 464 @ExecutedBy("mExecutor") waitForSessionUpdateId(long sessionUpdateIdToWait)465 private @NonNull ListenableFuture<Void> waitForSessionUpdateId(long sessionUpdateIdToWait) { 466 return CallbackToFutureAdapter.getFuture(completer -> { 467 addCaptureResultListener(captureResult -> { 468 boolean updated = isSessionUpdated(captureResult, sessionUpdateIdToWait); 469 if (updated) { 470 completer.set(null); 471 return true; // remove the callback 472 } 473 return false; // continue checking 474 }); 475 return "waitForSessionUpdateId:" + sessionUpdateIdToWait; 476 }); 477 } 478 479 /** 480 * Check if the sessionUpdateId in capture result is larger than the given sessionUpdateId. 481 */ 482 static boolean isSessionUpdated(@NonNull TotalCaptureResult captureResult, 483 long sessionUpdateId) { 484 if (captureResult.getRequest() == null) { 485 return false; 486 } 487 Object tag = captureResult.getRequest().getTag(); 488 if (tag instanceof TagBundle) { 489 Long tagLong = 490 (Long) ((TagBundle) tag).getTag(Camera2CameraControlImpl.TAG_SESSION_UPDATE_ID); 491 if (tagLong == null) { 492 return false; 493 } 494 long sessionUpdateIdInCaptureResult = tagLong.longValue(); 495 // Check if session update is already done. 496 if (sessionUpdateIdInCaptureResult >= sessionUpdateId) { 497 return true; 498 } 499 } 500 return false; 501 } 502 503 @Override 504 public @NonNull ListenableFuture<Integer> setExposureCompensationIndex(int exposure) { 505 if (!isControlInUse()) { 506 return Futures.immediateFailedFuture( 507 new OperationCanceledException("Camera is not active.")); 508 } 509 return mExposureControl.setExposureCompensationIndex(exposure); 510 } 511 512 @Override 513 public @NonNull ListenableFuture<Void> setTorchStrengthLevel( 514 @IntRange(from = 1) int torchStrengthLevel) { 515 if (!isControlInUse()) { 516 return Futures.immediateFailedFuture( 517 new OperationCanceledException("Camera is not active.")); 518 } 519 if (!mCameraCharacteristics.isTorchStrengthLevelSupported()) { 520 return Futures.immediateFailedFuture(new UnsupportedOperationException( 521 "The device doesn't support configuring torch strength level.")); 522 } 523 if (torchStrengthLevel < 1 524 || torchStrengthLevel > mCameraCharacteristics.getMaxTorchStrengthLevel()) { 525 return Futures.immediateFailedFuture(new IllegalArgumentException( 526 "The specified torch strength is not within the valid range.")); 527 } 528 return Futures.nonCancellationPropagating(mTorchControl.setTorchStrengthLevel( 529 Math.min(torchStrengthLevel, mCameraCharacteristics.getMaxTorchStrengthLevel()))); 530 } 531 532 @ExecutedBy("mExecutor") 533 void setTorchStrengthLevelInternal(@IntRange(from = 1) int torchStrengthLevel) { 534 mTorchStrength = torchStrengthLevel; 535 if (isTorchOn()) { 536 updateSessionConfigSynchronous(); 537 } 538 } 539 540 /** {@inheritDoc} */ 541 @Override 542 public @NonNull ListenableFuture<List<Void>> submitStillCaptureRequests( 543 @NonNull List<CaptureConfig> captureConfigs, 544 @ImageCapture.CaptureMode int captureMode, 545 @ImageCapture.FlashType int flashType) { 546 if (!isControlInUse()) { 547 Logger.w(TAG, "Camera is not active."); 548 return Futures.immediateFailedFuture( 549 new OperationCanceledException("Camera is not active.")); 550 } 551 552 // Prior to submitStillCaptures, wait until the pending flash mode session change is 553 // completed. On some devices, AE precapture triggered in submitStillCaptures may not 554 // work properly if the repeating request to change the flash mode is not completed. 555 int flashMode = getFlashMode(); 556 return FutureChain.from(Futures.nonCancellationPropagating( 557 mFlashModeChangeSessionUpdateFuture)).transformAsync( 558 v -> mCamera2CapturePipeline.submitStillCaptures(captureConfigs, captureMode, 559 flashMode, flashType), mExecutor); 560 } 561 562 @Override 563 public @NonNull ListenableFuture<CameraCapturePipeline> getCameraCapturePipelineAsync( 564 @ImageCapture.CaptureMode int captureMode, @ImageCapture.FlashType int flashType) { 565 if (!isControlInUse()) { 566 Logger.w(TAG, "Camera is not active."); 567 return Futures.immediateFailedFuture( 568 new OperationCanceledException("Camera is not active.")); 569 } 570 571 int flashMode = getFlashMode(); 572 return FutureChain.from( 573 Futures.nonCancellationPropagating(mFlashModeChangeSessionUpdateFuture) 574 ).transformAsync( 575 v -> Futures.immediateFuture(mCamera2CapturePipeline.getCameraCapturePipeline( 576 captureMode, flashMode, flashType 577 )), 578 mExecutor 579 ); 580 } 581 582 /** {@inheritDoc} */ 583 @Override 584 @ExecutedBy("mExecutor") 585 public @NonNull SessionConfig getSessionConfig() { 586 mSessionConfigBuilder.setTemplateType(mTemplate); 587 mSessionConfigBuilder.setImplementationOptions(getSessionOptions()); 588 mSessionConfigBuilder.addTag(TAG_SESSION_UPDATE_ID, mCurrentSessionUpdateId); 589 return mSessionConfigBuilder.build(); 590 } 591 592 @ExecutedBy("mExecutor") 593 void setTemplate(int template) { 594 mTemplate = template; 595 596 mFocusMeteringControl.setTemplate(mTemplate); 597 mCamera2CapturePipeline.setTemplate(mTemplate); 598 } 599 600 @ExecutedBy("mExecutor") 601 void resetTemplate() { 602 setTemplate(DEFAULT_TEMPLATE); 603 } 604 605 private boolean isControlInUse() { 606 return getUseCount() > 0; 607 } 608 609 /** 610 * Triggers an update to the session. 611 */ 612 public void updateSessionConfig() { 613 mExecutor.execute(this::updateSessionConfigSynchronous); 614 } 615 616 /** 617 * Triggers an update to the session and returns a ListenableFuture which completes when the 618 * session is updated successfully. 619 */ 620 public @NonNull ListenableFuture<Void> updateSessionConfigAsync() { 621 ListenableFuture<Void> future = CallbackToFutureAdapter.getFuture(completer -> { 622 mExecutor.execute(() -> { 623 long sessionUpdateId = updateSessionConfigSynchronous(); 624 Futures.propagate(waitForSessionUpdateId(sessionUpdateId), completer); 625 }); 626 return "updateSessionConfigAsync"; 627 }); 628 629 return Futures.nonCancellationPropagating(future); 630 } 631 632 /** 633 * Triggers an update to the session synchronously. 634 * 635 * <p>It will return an auto-incremented ID representing the session update request. The ID 636 * will be put in the tag of SessionConfig using key {@link #TAG_SESSION_UPDATE_ID}. It can 637 * then retrieve the ID in {@link TotalCaptureResult} to check if the session update is done or 638 * not. 639 */ 640 @ExecutedBy("mExecutor") 641 long updateSessionConfigSynchronous() { 642 mCurrentSessionUpdateId = mNextSessionUpdateId.getAndIncrement(); 643 mControlUpdateCallback.onCameraControlUpdateSessionConfig(); 644 return mCurrentSessionUpdateId; 645 } 646 647 @ExecutedBy("mExecutor") 648 @NonNull Rect getCropSensorRegion() { 649 return mZoomControl.getCropSensorRegion(); 650 } 651 652 @Override 653 @ExecutedBy("mExecutor") 654 public @NonNull Rect getSensorRect() { 655 Rect sensorRect = 656 mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); 657 if ("robolectric".equals(Build.FINGERPRINT) && sensorRect == null) { 658 return new Rect(0, 0, 4000, 3000); 659 } 660 return Preconditions.checkNotNull(sensorRect); 661 } 662 663 @ExecutedBy("mExecutor") 664 void removeCaptureResultListener(@NonNull CaptureResultListener listener) { 665 mSessionCallback.removeListener(listener); 666 } 667 668 @ExecutedBy("mExecutor") 669 void addCaptureResultListener(@NonNull CaptureResultListener listener) { 670 mSessionCallback.addListener(listener); 671 } 672 673 /** Adds a session {@link CameraCaptureCallback dynamically */ 674 void addSessionCameraCaptureCallback(@NonNull Executor executor, 675 @NonNull CameraCaptureCallback cameraCaptureCallback) { 676 mExecutor.execute(() -> { 677 mCameraCaptureCallbackSet.addCaptureCallback(executor, cameraCaptureCallback); 678 }); 679 } 680 681 /** Removes the {@link CameraCaptureCallback} that was added previously */ 682 void removeSessionCameraCaptureCallback(@NonNull CameraCaptureCallback cameraCaptureCallback) { 683 mExecutor.execute(() -> { 684 mCameraCaptureCallbackSet.removeCaptureCallback(cameraCaptureCallback); 685 }); 686 } 687 688 @SuppressWarnings("WeakerAccess") /* synthetic accessor */ 689 @ExecutedBy("mExecutor") 690 void enableTorchInternal(@TorchControl.TorchStateInternal int torchState) { 691 // When low-light boost is on, any torch related operations will be ignored. 692 if (mIsLowLightBoostOn) { 693 return; 694 } 695 696 mTorchState = torchState; 697 if (torchState == TorchControl.OFF) { 698 // On some devices, needs to reset the AE/flash state to ensure that the torch can be 699 // turned off. 700 resetAeFlashState(); 701 } 702 updateSessionConfigSynchronous(); 703 } 704 705 @SuppressWarnings("WeakerAccess") /* synthetic accessor */ 706 @ExecutedBy("mExecutor") 707 void enableLowLightBoostInternal(boolean lowLightBoost) { 708 if (mIsLowLightBoostOn == lowLightBoost) { 709 return; 710 } 711 712 // Forces turn off torch before enabling low-light boost. 713 if (lowLightBoost && isTorchOn()) { 714 // On some devices, needs to reset the AE/flash state to ensure that the torch can be 715 // turned off. 716 resetAeFlashState(); 717 mTorchState = TorchControl.OFF; 718 mTorchControl.forceUpdateTorchStateToOff(); 719 } 720 721 mIsLowLightBoostOn = lowLightBoost; 722 updateSessionConfigSynchronous(); 723 } 724 725 private void resetAeFlashState() { 726 // Send capture request with AE_MODE_ON + FLASH_MODE_OFF to reset the AE/flash state. 727 CaptureConfig.Builder singleRequestBuilder = new CaptureConfig.Builder(); 728 singleRequestBuilder.setTemplateType(mTemplate); 729 singleRequestBuilder.setUseRepeatingSurface(true); 730 Camera2ImplConfig.Builder configBuilder = new Camera2ImplConfig.Builder(); 731 configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, 732 getSupportedAeMode(CaptureRequest.CONTROL_AE_MODE_ON)); 733 configBuilder.setCaptureRequestOption(CaptureRequest.FLASH_MODE, 734 CaptureRequest.FLASH_MODE_OFF); 735 singleRequestBuilder.addImplementationOptions(configBuilder.build()); 736 submitCaptureRequestsInternal( 737 Collections.singletonList(singleRequestBuilder.build())); 738 } 739 740 @ExecutedBy("mExecutor") 741 boolean isTorchOn() { 742 return mTorchState != TorchControl.OFF; 743 } 744 745 @ExecutedBy("mExecutor") 746 boolean isLowLightBoostOn() { 747 return mIsLowLightBoostOn; 748 } 749 750 @ExecutedBy("mExecutor") 751 void submitCaptureRequestsInternal(final List<CaptureConfig> captureConfigs) { 752 mControlUpdateCallback.onCameraControlCaptureRequests(captureConfigs); 753 } 754 755 /** 756 * Gets session options by current status. 757 * 758 * <p>The session options are based on the current torch status, flash mode, focus area, crop 759 * area, etc... They should be appended to the repeat request. 760 */ 761 @VisibleForTesting 762 @ExecutedBy("mExecutor") 763 Config getSessionOptions() { 764 Camera2ImplConfig.Builder builder = new Camera2ImplConfig.Builder(); 765 builder.setCaptureRequestOptionWithPriority(CaptureRequest.CONTROL_MODE, 766 CaptureRequest.CONTROL_MODE_AUTO, Config.OptionPriority.REQUIRED); 767 768 // AF Mode is assigned in mFocusMeteringControl. 769 mFocusMeteringControl.addFocusMeteringOptions(builder); 770 771 mZoomControl.addZoomOption(builder); 772 773 int aeMode = CaptureRequest.CONTROL_AE_MODE_ON; 774 775 // Flash modes other than screen flash will override this AE mode later 776 if (mFocusMeteringControl.isExternalFlashAeModeEnabled()) { 777 aeMode = CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH; 778 } 779 780 if (mIsLowLightBoostOn) { 781 aeMode = CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY; 782 } else if (isTorchOn()) { 783 builder.setCaptureRequestOptionWithPriority(CaptureRequest.FLASH_MODE, 784 CaptureRequest.FLASH_MODE_TORCH, Config.OptionPriority.REQUIRED); 785 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 786 if (mTorchState == TorchControl.ON) { 787 builder.setCaptureRequestOptionWithPriority(CaptureRequest.FLASH_STRENGTH_LEVEL, 788 mTorchStrength, Config.OptionPriority.REQUIRED); 789 } else if (mTorchState == TorchControl.USED_AS_FLASH) { 790 // If torch is used as flash, use the default torch strength instead. 791 builder.setCaptureRequestOptionWithPriority(CaptureRequest.FLASH_STRENGTH_LEVEL, 792 mCameraCharacteristics.getDefaultTorchStrengthLevel(), 793 Config.OptionPriority.REQUIRED); 794 } 795 } 796 } else { 797 switch (mFlashMode) { 798 case FLASH_MODE_OFF: 799 aeMode = CaptureRequest.CONTROL_AE_MODE_ON; 800 break; 801 case FLASH_MODE_ON: 802 aeMode = CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH; 803 break; 804 case FLASH_MODE_AUTO: 805 aeMode = mAutoFlashAEModeDisabler.getCorrectedAeMode( 806 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 807 break; 808 } 809 } 810 builder.setCaptureRequestOptionWithPriority(CaptureRequest.CONTROL_AE_MODE, 811 getSupportedAeMode(aeMode), Config.OptionPriority.REQUIRED); 812 813 builder.setCaptureRequestOptionWithPriority(CaptureRequest.CONTROL_AWB_MODE, 814 getSupportedAwbMode(CaptureRequest.CONTROL_AWB_MODE_AUTO), 815 Config.OptionPriority.REQUIRED); 816 817 mExposureControl.setCaptureRequestOption(builder); 818 819 mCamera2CameraControl.applyOptionsToBuilder(builder); 820 821 return builder.build(); 822 } 823 824 /** 825 * Returns a supported AF mode which will be preferredMode if it is supported. 826 * 827 * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to 828 * lowest). 829 * 1) {@link CaptureRequest#CONTROL_AF_MODE_CONTINUOUS_PICTURE} 830 * 2) {@link CaptureRequest#CONTROL_AF_MODE_AUTO)} 831 * 3) {@link CaptureRequest#CONTROL_AF_MODE_OFF} 832 * </pre> 833 */ 834 @ExecutedBy("mExecutor") 835 int getSupportedAfMode(int preferredMode) { 836 int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); 837 if (modes == null) { 838 return CaptureRequest.CONTROL_AF_MODE_OFF; 839 } 840 841 // if preferredMode is supported, use it 842 if (isModeInList(preferredMode, modes)) { 843 return preferredMode; 844 } 845 846 // if not found, priority is CONTINUOUS_PICTURE > AUTO > OFF 847 if (isModeInList(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE, modes)) { 848 return CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE; 849 } else if (isModeInList(CaptureRequest.CONTROL_AF_MODE_AUTO, modes)) { 850 return CaptureRequest.CONTROL_AF_MODE_AUTO; 851 } 852 853 return CaptureRequest.CONTROL_AF_MODE_OFF; 854 } 855 856 /** 857 * Returns a supported AE mode which will be preferredMode if it is supported. 858 * 859 * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to 860 * lowest). 861 * 1) {@link CaptureRequest#CONTROL_AE_MODE_ON} 862 * 2) {@link CaptureRequest#CONTROL_AE_MODE_OFF} 863 * </pre> 864 */ 865 @ExecutedBy("mExecutor") 866 int getSupportedAeMode(int preferredMode) { 867 return getSupportedAeMode(mCameraCharacteristics, preferredMode); 868 } 869 870 /** 871 * Returns a supported AE mode which will be preferredMode if it is supported. 872 * 873 * @see #getSupportedAeMode(int preferredMode) 874 */ 875 public static int getSupportedAeMode( 876 @NonNull CameraCharacteristicsCompat cameraCharacteristics, 877 int preferredMode 878 ) { 879 int[] modes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES); 880 881 if (modes == null) { 882 return CaptureRequest.CONTROL_AE_MODE_OFF; 883 } 884 885 // if preferredMode is supported, use it 886 if (isModeInList(preferredMode, modes)) { 887 return preferredMode; 888 } 889 890 // if not found, priority is AE_ON > AE_OFF 891 if (isModeInList(CaptureRequest.CONTROL_AE_MODE_ON, modes)) { 892 return CaptureRequest.CONTROL_AE_MODE_ON; 893 } 894 895 return CaptureRequest.CONTROL_AE_MODE_OFF; 896 } 897 898 /** 899 * Returns a supported AWB mode which will be preferredMode if it is supported. 900 * 901 * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to 902 * lowest). 903 * 1) {@link CaptureRequest#CONTROL_AWB_MODE_AUTO} 904 * 2) {@link CaptureRequest#CONTROL_AWB_MODE_OFF)} 905 * </pre> 906 */ 907 @ExecutedBy("mExecutor") 908 private int getSupportedAwbMode(int preferredMode) { 909 int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES); 910 911 if (modes == null) { 912 return CaptureRequest.CONTROL_AWB_MODE_OFF; 913 } 914 915 // if preferredMode is supported, use it 916 if (isModeInList(preferredMode, modes)) { 917 return preferredMode; 918 } 919 920 // if not found, priority is AWB_AUTO > AWB_OFF 921 if (isModeInList(CaptureRequest.CONTROL_AWB_MODE_AUTO, modes)) { 922 return CaptureRequest.CONTROL_AWB_MODE_AUTO; 923 } 924 925 return CaptureRequest.CONTROL_AWB_MODE_OFF; 926 } 927 928 @ExecutedBy("mExecutor") 929 private static boolean isModeInList(int mode, int[] modeList) { 930 for (int m : modeList) { 931 if (mode == m) { 932 return true; 933 } 934 } 935 return false; 936 } 937 938 int getMaxAfRegionCount() { 939 Integer count = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); 940 return count == null ? 0 : count; 941 } 942 943 int getMaxAeRegionCount() { 944 Integer count = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); 945 return count == null ? 0 : count; 946 } 947 948 int getMaxAwbRegionCount() { 949 Integer count = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB); 950 return count == null ? 0 : count; 951 } 952 953 @VisibleForTesting 954 long getCurrentSessionUpdateId() { 955 return mCurrentSessionUpdateId; 956 } 957 958 @Override 959 public void incrementVideoUsage() { 960 mVideoUsageControl.incrementUsage(); 961 } 962 963 @Override 964 public void decrementVideoUsage() { 965 mVideoUsageControl.decrementUsage(); 966 } 967 968 @Override 969 public boolean isInVideoUsage() { 970 int currentVal = mVideoUsageControl.getUsage(); 971 Logger.d(TAG, "isInVideoUsage: mVideoUsageControl value = " + currentVal); 972 return currentVal > 0; 973 } 974 975 /** An interface to listen to camera capture results. */ 976 public interface CaptureResultListener { 977 /** 978 * Callback to handle camera capture results. 979 * 980 * @param captureResult camera capture result. 981 * @return true to finish listening, false to continue listening. 982 */ 983 boolean onCaptureResult(@NonNull TotalCaptureResult captureResult); 984 } 985 986 static final class CameraControlSessionCallback extends CaptureCallback { 987 988 /* synthetic accessor */final Set<CaptureResultListener> mResultListeners = new HashSet<>(); 989 @CameraExecutor 990 private final Executor mExecutor; 991 992 CameraControlSessionCallback(@CameraExecutor @NonNull Executor executor) { 993 mExecutor = executor; 994 } 995 996 @ExecutedBy("mExecutor") 997 void addListener(@NonNull CaptureResultListener listener) { 998 mResultListeners.add(listener); 999 } 1000 1001 @ExecutedBy("mExecutor") 1002 void removeListener(@NonNull CaptureResultListener listener) { 1003 mResultListeners.remove(listener); 1004 } 1005 1006 @Override 1007 public void onCaptureCompleted( 1008 @NonNull CameraCaptureSession session, 1009 @NonNull CaptureRequest request, 1010 final @NonNull TotalCaptureResult result) { 1011 1012 mExecutor.execute(() -> { 1013 Set<CaptureResultListener> removeSet = new HashSet<>(); 1014 for (CaptureResultListener listener : mResultListeners) { 1015 boolean isFinished = listener.onCaptureResult(result); 1016 if (isFinished) { 1017 removeSet.add(listener); 1018 } 1019 } 1020 1021 if (!removeSet.isEmpty()) { 1022 mResultListeners.removeAll(removeSet); 1023 } 1024 }); 1025 } 1026 } 1027 1028 /** 1029 * A set of {@link CameraCaptureCallback}s which is capable of adding/removing callbacks 1030 * dynamically. 1031 */ 1032 static final class CameraCaptureCallbackSet extends CameraCaptureCallback { 1033 Set<CameraCaptureCallback> mCallbacks = new HashSet<>(); 1034 Map<CameraCaptureCallback, Executor> mCallbackExecutors = new ArrayMap<>(); 1035 1036 @ExecutedBy("mExecutor") 1037 void addCaptureCallback(@NonNull Executor executor, 1038 @NonNull CameraCaptureCallback callback) { 1039 mCallbacks.add(callback); 1040 mCallbackExecutors.put(callback, executor); 1041 } 1042 1043 @ExecutedBy("mExecutor") 1044 void removeCaptureCallback(@NonNull CameraCaptureCallback callback) { 1045 mCallbacks.remove(callback); 1046 mCallbackExecutors.remove(callback); 1047 } 1048 1049 @ExecutedBy("mExecutor") 1050 @Override 1051 public void onCaptureCompleted(int captureConfigId, 1052 @NonNull CameraCaptureResult cameraCaptureResult) { 1053 for (CameraCaptureCallback callback : mCallbacks) { 1054 try { 1055 mCallbackExecutors.get(callback).execute(() -> { 1056 callback.onCaptureCompleted(captureConfigId, cameraCaptureResult); 1057 }); 1058 } catch (RejectedExecutionException e) { 1059 Logger.e(TAG, "Executor rejected to invoke onCaptureCompleted.", e); 1060 } 1061 } 1062 } 1063 1064 @ExecutedBy("mExecutor") 1065 @Override 1066 public void onCaptureFailed(int captureConfigId, @NonNull CameraCaptureFailure failure) { 1067 for (CameraCaptureCallback callback : mCallbacks) { 1068 try { 1069 mCallbackExecutors.get(callback).execute(() -> { 1070 callback.onCaptureFailed(captureConfigId, failure); 1071 }); 1072 } catch (RejectedExecutionException e) { 1073 Logger.e(TAG, "Executor rejected to invoke onCaptureFailed.", e); 1074 } 1075 } 1076 } 1077 1078 @ExecutedBy("mExecutor") 1079 @Override 1080 public void onCaptureCancelled(int captureConfigId) { 1081 for (CameraCaptureCallback callback : mCallbacks) { 1082 try { 1083 mCallbackExecutors.get(callback).execute(() -> { 1084 callback.onCaptureCancelled(captureConfigId); 1085 }); 1086 } catch (RejectedExecutionException e) { 1087 Logger.e(TAG, "Executor rejected to invoke onCaptureCancelled.", e); 1088 } 1089 } 1090 } 1091 } 1092 } 1093