1 /* 2 * Copyright (C) 2019 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.testing.fakes; 18 19 import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF; 20 import static androidx.camera.testing.imagecapture.CaptureResult.CAPTURE_STATUS_CANCELLED; 21 import static androidx.camera.testing.imagecapture.CaptureResult.CAPTURE_STATUS_FAILED; 22 import static androidx.camera.testing.imagecapture.CaptureResult.CAPTURE_STATUS_SUCCESSFUL; 23 import static androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE; 24 25 import static java.util.Objects.requireNonNull; 26 27 import android.graphics.Rect; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Process; 31 32 import androidx.annotation.GuardedBy; 33 import androidx.annotation.RestrictTo; 34 import androidx.camera.core.CameraControl; 35 import androidx.camera.core.CameraXThreads; 36 import androidx.camera.core.FocusMeteringAction; 37 import androidx.camera.core.FocusMeteringResult; 38 import androidx.camera.core.ImageCapture; 39 import androidx.camera.core.ImageCapture.ScreenFlash; 40 import androidx.camera.core.ImageCaptureException; 41 import androidx.camera.core.Logger; 42 import androidx.camera.core.imagecapture.CameraCapturePipeline; 43 import androidx.camera.core.impl.CameraCaptureCallback; 44 import androidx.camera.core.impl.CameraCaptureFailure; 45 import androidx.camera.core.impl.CameraCaptureResult; 46 import androidx.camera.core.impl.CameraControlInternal; 47 import androidx.camera.core.impl.CaptureConfig; 48 import androidx.camera.core.impl.Config; 49 import androidx.camera.core.impl.MutableOptionsBundle; 50 import androidx.camera.core.impl.SessionConfig; 51 import androidx.camera.core.impl.utils.executor.CameraXExecutors; 52 import androidx.camera.core.impl.utils.futures.Futures; 53 import androidx.camera.testing.imagecapture.CaptureResult; 54 import androidx.camera.testing.impl.FakeCameraCapturePipeline; 55 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager; 56 import androidx.concurrent.futures.CallbackToFutureAdapter; 57 import androidx.core.os.HandlerCompat; 58 import androidx.core.util.Pair; 59 60 import com.google.common.util.concurrent.ListenableFuture; 61 62 import org.jspecify.annotations.NonNull; 63 import org.jspecify.annotations.Nullable; 64 65 import java.util.ArrayDeque; 66 import java.util.ArrayList; 67 import java.util.HashMap; 68 import java.util.List; 69 import java.util.Map; 70 import java.util.concurrent.CopyOnWriteArrayList; 71 import java.util.concurrent.Executor; 72 import java.util.concurrent.atomic.AtomicReference; 73 74 /** 75 * A fake implementation for the {@link CameraControlInternal} interface which is capable of 76 * notifying submitted requests using the associated {@link CameraCaptureCallback} instances or 77 * {@link ControlUpdateCallback}. 78 */ 79 public final class FakeCameraControl implements CameraControlInternal { 80 private static final String TAG = "FakeCameraControl"; 81 static final long AUTO_FOCUS_TIMEOUT_DURATION = 5000; 82 83 private static final ControlUpdateCallback NO_OP_CALLBACK = new ControlUpdateCallback() { 84 @Override 85 public void onCameraControlUpdateSessionConfig() { 86 // No-op 87 } 88 89 @Override 90 public void onCameraControlCaptureRequests( 91 @NonNull List<CaptureConfig> captureConfigs) { 92 // No-op 93 } 94 }; 95 96 private final Object mLock = new Object(); 97 98 /** 99 * The executor used to invoke any callback/listener which doesn't have a dedicated executor 100 * for it. 101 * <p> {@link CameraXExecutors#directExecutor} via default, unless some other executor is set 102 * via {@link #FakeCameraControl(Executor, ControlUpdateCallback)}. 103 */ 104 private final @NonNull Executor mExecutor; 105 private final ControlUpdateCallback mControlUpdateCallback; 106 private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder(); 107 @ImageCapture.FlashMode 108 private int mFlashMode = FLASH_MODE_OFF; 109 private Pair<Executor, OnNewCaptureRequestListener> mOnNewCaptureRequestListener; 110 private MutableOptionsBundle mInteropConfig = MutableOptionsBundle.create(); 111 112 @GuardedBy("mLock") 113 private final ArrayDeque<CaptureConfig> mSubmittedCaptureRequests = new ArrayDeque<>(); 114 @GuardedBy("mLock") 115 private final ArrayDeque<CallbackToFutureAdapter.Completer<Void>> mSubmittedCompleterList = 116 new ArrayDeque<>(); 117 @GuardedBy("mLock") 118 private final ArrayDeque<CaptureResult> mCaptureResults = new ArrayDeque<>(); 119 120 private boolean mIsFocusMeteringAutoCompleted = true; 121 @GuardedBy("mLock") 122 private final ArrayDeque<FocusMeteringAction> mRequestedFocusMeteringActions = 123 new ArrayDeque<>(); 124 @GuardedBy("mLock") 125 private final Map<FocusMeteringAction, CallbackToFutureAdapter.Completer<FocusMeteringResult>> 126 mFocusMeteringActionToResultMap = new HashMap<>(); 127 @GuardedBy("mLock") 128 private final ArrayDeque<FocusMeteringResult> mFocusMeteringResults = new ArrayDeque<>(); 129 130 private final List<CaptureSuccessListener> mCaptureSuccessListeners = 131 new CopyOnWriteArrayList<>(); 132 133 private boolean mIsZslDisabledByUseCaseConfig = false; 134 private boolean mIsZslConfigAdded = false; 135 private float mZoomRatio = -1; 136 private float mLinearZoom = -1; 137 private boolean mTorchEnabled = false; 138 private int mExposureCompensation = -1; 139 private ScreenFlash mScreenFlash; 140 141 private final FakeCameraCapturePipeline mFakeCameraCapturePipeline = 142 new FakeCameraCapturePipeline(); 143 144 private @Nullable FocusMeteringAction mLastSubmittedFocusMeteringAction = null; 145 146 /** 147 * Constructs an instance of {@link FakeCameraControl} with a no-op 148 * {@link ControlUpdateCallback}. 149 * 150 * @see #FakeCameraControl(ControlUpdateCallback) 151 * @see #FakeCameraControl(Executor, ControlUpdateCallback) 152 */ FakeCameraControl()153 public FakeCameraControl() { 154 this(NO_OP_CALLBACK); 155 } 156 157 /** 158 * Constructs an instance of {@link FakeCameraControl} with the 159 * provided {@link ControlUpdateCallback}. 160 * 161 * <p> Note that callbacks will be executed on the calling thread directly via 162 * {@link CameraXExecutors#directExecutor}. To specify the execution thread, use 163 * {@link #FakeCameraControl(Executor, ControlUpdateCallback)}. 164 * 165 * @param controlUpdateCallback {@link ControlUpdateCallback} to notify events. 166 */ FakeCameraControl(@onNull ControlUpdateCallback controlUpdateCallback)167 public FakeCameraControl(@NonNull ControlUpdateCallback controlUpdateCallback) { 168 this(CameraXExecutors.directExecutor(), controlUpdateCallback); 169 } 170 171 /** 172 * Constructs an instance of {@link FakeCameraControl} with the 173 * provided {@link ControlUpdateCallback}. 174 * 175 * @param executor {@link Executor} used to invoke the {@code 176 * controlUpdateCallback}. 177 * @param controlUpdateCallback {@link ControlUpdateCallback} to notify events. 178 */ FakeCameraControl(@onNull Executor executor, @NonNull ControlUpdateCallback controlUpdateCallback)179 public FakeCameraControl(@NonNull Executor executor, 180 @NonNull ControlUpdateCallback controlUpdateCallback) { 181 mExecutor = executor; 182 mControlUpdateCallback = controlUpdateCallback; 183 } 184 185 /** 186 * Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureCancelled}, 187 * which is invoked in the thread denoted by {@link #mExecutor}. 188 * 189 * @deprecated Use {@link #completeAllCaptureRequests(CaptureResult)} instead. 190 */ 191 @Deprecated // TODO: b/366136115 - Remove all usages notifyAllRequestsOnCaptureCancelled()192 public void notifyAllRequestsOnCaptureCancelled() { 193 while (true) { 194 if (!completeFirstPendingCaptureRequest(CAPTURE_STATUS_CANCELLED, null)) { 195 break; 196 } 197 } 198 } 199 200 /** 201 * Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureFailed}, 202 * which is invoked in the thread denoted by {@link #mExecutor}. 203 * 204 * @deprecated Use {@link #completeAllCaptureRequests(CaptureResult)} instead. 205 */ 206 @Deprecated // TODO: b/366136115 - Remove all usages notifyAllRequestsOnCaptureFailed()207 public void notifyAllRequestsOnCaptureFailed() { 208 while (true) { 209 if (!completeFirstPendingCaptureRequest(CAPTURE_STATUS_FAILED, null)) { 210 break; 211 } 212 } 213 } 214 215 /** 216 * Notifies all submitted requests using {@link CameraCaptureCallback#onCaptureCompleted}, 217 * which is invoked in the thread denoted by {@link #mExecutor}. 218 * 219 * @param result The {@link CameraCaptureResult} which is notified to all the callbacks. 220 * @deprecated Use {@link #completeAllCaptureRequests(CaptureResult)} instead. 221 */ 222 @Deprecated // TODO: b/366136115 - Remove all usages notifyAllRequestsOnCaptureCompleted(@onNull CameraCaptureResult result)223 public void notifyAllRequestsOnCaptureCompleted(@NonNull CameraCaptureResult result) { 224 while (true) { 225 if (!completeFirstPendingCaptureRequest(CAPTURE_STATUS_SUCCESSFUL, result)) { 226 break; 227 } 228 } 229 } 230 231 /** 232 * Completes the first submitted but incomplete capture request using one of the 233 * {@link CameraCaptureCallback} methods, which is invoked in the thread denoted by 234 * {@link #mExecutor}. 235 * 236 * @param captureStatus Represents how a capture request should be completed. 237 * @param captureResult The {@link CameraCaptureResult} which is notified to all the 238 * callbacks. Must not be null if captureStatus parameter is 239 * {@link CaptureResult#CAPTURE_STATUS_SUCCESSFUL}. 240 * @return True if a capture request was completed, false otherwise. 241 */ 242 // TODO: b/365519650 - Take FakeCameraCaptureResult as parameter to contain extra user-provided 243 // data like bitmap/image proxy and use that to complete capture. 244 @SuppressWarnings("ObjectToString") // Required for captureConfig hashcode log completeFirstPendingCaptureRequest( @aptureResult.CaptureStatus int captureStatus, @Nullable CameraCaptureResult captureResult)245 private boolean completeFirstPendingCaptureRequest( 246 @CaptureResult.CaptureStatus int captureStatus, 247 @Nullable CameraCaptureResult captureResult) { 248 CaptureConfig captureConfig; 249 CallbackToFutureAdapter.Completer<Void> completer; 250 251 synchronized (mLock) { 252 if (mSubmittedCaptureRequests.isEmpty() || mSubmittedCompleterList.isEmpty()) { 253 Logger.d(TAG, 254 "completeFirstPendingCaptureRequest: returning early since either " 255 + "mSubmittedCaptureRequests or mSubmittedCompleterList is empty, " 256 + "mSubmittedCaptureRequests = " 257 + mSubmittedCaptureRequests + ", mSubmittedCompleterList" 258 + mSubmittedCompleterList); 259 return false; 260 } 261 262 captureConfig = mSubmittedCaptureRequests.removeFirst(); 263 completer = mSubmittedCompleterList.removeFirst(); 264 } 265 Logger.d(TAG, "completeFirstPendingCaptureRequest: captureConfig = " + captureConfig); 266 267 if (captureStatus == CAPTURE_STATUS_SUCCESSFUL) { 268 notifyCaptureSuccess(requireNonNull(captureResult)); 269 } 270 271 for (CameraCaptureCallback cameraCaptureCallback : 272 captureConfig.getCameraCaptureCallbacks()) { 273 mExecutor.execute(() -> { 274 switch (captureStatus) { 275 case CAPTURE_STATUS_SUCCESSFUL: 276 cameraCaptureCallback.onCaptureCompleted(captureConfig.getId(), 277 requireNonNull(captureResult)); 278 break; 279 case CAPTURE_STATUS_FAILED: 280 cameraCaptureCallback.onCaptureFailed(captureConfig.getId(), 281 new CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)); 282 break; 283 case CAPTURE_STATUS_CANCELLED: 284 cameraCaptureCallback.onCaptureCancelled(captureConfig.getId()); 285 break; 286 default: 287 Logger.e(TAG, "completeFirstPendingCaptureRequest: unknown capture status: " 288 + captureStatus); 289 } 290 }); 291 } 292 293 switch (captureStatus) { 294 case CAPTURE_STATUS_SUCCESSFUL: 295 completer.set(null); 296 break; 297 case CAPTURE_STATUS_FAILED: 298 completer.setException(new ImageCaptureException(ImageCapture.ERROR_CAPTURE_FAILED, 299 "Simulate capture fail", null)); 300 break; 301 case CAPTURE_STATUS_CANCELLED: 302 completer.setException(new ImageCaptureException(ImageCapture.ERROR_CAMERA_CLOSED, 303 "Simulate capture cancelled", null)); 304 break; 305 default: 306 Logger.e(TAG, "completeFirstPendingCaptureRequest: unknown capture status: " 307 + captureStatus); 308 } 309 310 return true; 311 } 312 313 @ImageCapture.FlashMode 314 @Override getFlashMode()315 public int getFlashMode() { 316 return mFlashMode; 317 } 318 319 @Override setFlashMode(@mageCapture.FlashMode int flashMode)320 public void setFlashMode(@ImageCapture.FlashMode int flashMode) { 321 mFlashMode = flashMode; 322 Logger.d(TAG, "setFlashMode(" + mFlashMode + ")"); 323 } 324 325 @Override setScreenFlash(@ullable ScreenFlash screenFlash)326 public void setScreenFlash(@Nullable ScreenFlash screenFlash) { 327 mScreenFlash = screenFlash; 328 Logger.d(TAG, "setScreenFlash(" + mScreenFlash + ")"); 329 } 330 getScreenFlash()331 public @Nullable ScreenFlash getScreenFlash() { 332 return mScreenFlash; 333 } 334 335 @Override setZslDisabledByUserCaseConfig(boolean disabled)336 public void setZslDisabledByUserCaseConfig(boolean disabled) { 337 mIsZslDisabledByUseCaseConfig = disabled; 338 } 339 340 @Override isZslDisabledByByUserCaseConfig()341 public boolean isZslDisabledByByUserCaseConfig() { 342 return mIsZslDisabledByUseCaseConfig; 343 } 344 345 @Override addZslConfig(SessionConfig.@onNull Builder sessionConfigBuilder)346 public void addZslConfig(SessionConfig.@NonNull Builder sessionConfigBuilder) { 347 // Override if Zero-Shutter Lag needs to add config to session config. 348 mIsZslConfigAdded = true; 349 } 350 351 @Override 352 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) clearZslConfig()353 public void clearZslConfig() { 354 mIsZslConfigAdded = false; 355 } 356 357 /** 358 * Checks if {@link FakeCameraControl#addZslConfig(SessionConfig.Builder)} has been triggered. 359 */ isZslConfigAdded()360 public boolean isZslConfigAdded() { 361 return mIsZslConfigAdded; 362 } 363 364 /** 365 * Sets the torch status. 366 * 367 * @param torch The torch status is set as enabled if true, disabled if false. 368 * @return Returns a {@link Futures#immediateFuture} which immediately contains a result. 369 */ 370 @Override enableTorch(boolean torch)371 public @NonNull ListenableFuture<Void> enableTorch(boolean torch) { 372 Logger.d(TAG, "enableTorch(" + torch + ")"); 373 mTorchEnabled = torch; 374 return Futures.immediateFuture(null); 375 } 376 377 /** Returns if torch is set as enabled. */ getTorchEnabled()378 public boolean getTorchEnabled() { 379 return mTorchEnabled; 380 } 381 382 /** 383 * Sets the exposure compensation index. 384 * 385 * @param value The exposure compensation value to be set. 386 * @return Returns a {@link Futures#immediateFuture} which immediately contains a result. 387 */ 388 @Override setExposureCompensationIndex(int value)389 public @NonNull ListenableFuture<Integer> setExposureCompensationIndex(int value) { 390 mExposureCompensation = value; 391 return Futures.immediateFuture(null); 392 } 393 394 /** Returns the exposure compensation index. */ getExposureCompensationIndex()395 public int getExposureCompensationIndex() { 396 return mExposureCompensation; 397 } 398 399 @Override submitStillCaptureRequests( @onNull List<CaptureConfig> captureConfigs, int captureMode, int flashType)400 public @NonNull ListenableFuture<List<Void>> submitStillCaptureRequests( 401 @NonNull List<CaptureConfig> captureConfigs, 402 int captureMode, int flashType) { 403 Logger.d(TAG, "submitStillCaptureRequests: captureConfigs = " + captureConfigs); 404 405 List<ListenableFuture<Void>> fakeFutures = new ArrayList<>(); 406 407 synchronized (mLock) { 408 mSubmittedCaptureRequests.addAll(captureConfigs); 409 for (int i = 0; i < captureConfigs.size(); i++) { 410 AtomicReference<CallbackToFutureAdapter.Completer<Void>> completerRef = 411 new AtomicReference<>(); 412 fakeFutures.add(CallbackToFutureAdapter.getFuture(completer -> { 413 // mSubmittedCaptureRequests and mSubmittedCompleterList must be updated under 414 // the same lock to avoid rare out-of-state bugs. So, completer can't be added 415 // to mSubmittedCompleterList here directly even though this line is guaranteed 416 // to be called immediately. 417 completerRef.set(completer); 418 return "fakeFuture"; 419 })); 420 mSubmittedCompleterList.add(requireNonNull(completerRef.get())); 421 } 422 } 423 424 mExecutor.execute( 425 () -> mControlUpdateCallback.onCameraControlCaptureRequests(captureConfigs)); 426 427 if (mOnNewCaptureRequestListener != null) { 428 Executor executor = requireNonNull(mOnNewCaptureRequestListener.first); 429 OnNewCaptureRequestListener listener = 430 requireNonNull(mOnNewCaptureRequestListener.second); 431 432 executor.execute(() -> listener.onNewCaptureRequests(captureConfigs)); 433 } 434 435 mExecutor.execute(this::applyCaptureResults); 436 437 return Futures.allAsList(fakeFutures); 438 } 439 440 @Override 441 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getCameraCapturePipelineAsync( int captureMode, int flashType)442 public @NonNull ListenableFuture<CameraCapturePipeline> getCameraCapturePipelineAsync( 443 int captureMode, int flashType) { 444 return Futures.immediateFuture(mFakeCameraCapturePipeline); 445 } 446 447 @Override getSessionConfig()448 public @NonNull SessionConfig getSessionConfig() { 449 return mSessionConfigBuilder.build(); 450 } 451 452 /** 453 * Returns a {@link Rect} corresponding to 454 * {@link FakeCameraDeviceSurfaceManager#MAX_OUTPUT_SIZE}. 455 */ 456 @Override getSensorRect()457 public @NonNull Rect getSensorRect() { 458 return new Rect(0, 0, MAX_OUTPUT_SIZE.getWidth(), MAX_OUTPUT_SIZE.getHeight()); 459 } 460 461 /** 462 * Stores the last submitted {@link FocusMeteringAction} and returns a result that may still be 463 * incomplete based on {@link #disableFocusMeteringAutoComplete(boolean)}. 464 * 465 * <p> The focus-metering operation is automatically canceled in a background thread after the 466 * duration of {@link FocusMeteringAction#getAutoCancelDurationInMillis()} is elapsed. After 5 467 * seconds, any ongoing focus-metering operation is timed out with unsuccessful 468 * {@link FocusMeteringResult}. 469 * 470 * @param action The {@link FocusMeteringAction} to be used. 471 * @return Returns a {@link Futures#immediateFuture} which immediately contains a empty 472 * {@link FocusMeteringResult}. 473 */ 474 @Override startFocusAndMetering( @onNull FocusMeteringAction action)475 public @NonNull ListenableFuture<FocusMeteringResult> startFocusAndMetering( 476 @NonNull FocusMeteringAction action) { 477 mLastSubmittedFocusMeteringAction = action; 478 479 if (!mIsFocusMeteringAutoCompleted) { 480 synchronized (mLock) { 481 mRequestedFocusMeteringActions.add(action); 482 483 AtomicReference<CallbackToFutureAdapter.Completer<FocusMeteringResult>> 484 resultCompleter = new AtomicReference<>(); 485 486 ListenableFuture<FocusMeteringResult> future = CallbackToFutureAdapter.getFuture( 487 completer -> { 488 resultCompleter.set(completer); 489 return "focusMeteringResultFuture"; 490 }); 491 492 // CallbackToFutureAdapter.getFuture provides completer synchronously, so it should 493 // already be available 494 mFocusMeteringActionToResultMap.put(action, resultCompleter.get()); 495 496 scheduleFocusTimeoutAndAutoCancellation(action, resultCompleter.get()); 497 498 return future; 499 } 500 } 501 502 return Futures.immediateFuture(FocusMeteringResult.emptyInstance()); 503 } 504 scheduleFocusTimeoutAndAutoCancellation( @onNull FocusMeteringAction action, CallbackToFutureAdapter.Completer<FocusMeteringResult> focusMeteringResultCompleter)505 private void scheduleFocusTimeoutAndAutoCancellation( 506 @NonNull FocusMeteringAction action, 507 CallbackToFutureAdapter.Completer<FocusMeteringResult> focusMeteringResultCompleter) { 508 HandlerThread handlerThread = new HandlerThread(CameraXThreads.TAG + TAG, 509 Process.THREAD_PRIORITY_BACKGROUND); 510 handlerThread.start(); 511 Handler handler = HandlerCompat.createAsync(handlerThread.getLooper()); 512 513 // Sets auto focus timeout runnable first so that action will be completed with 514 // unsuccessful focusing when auto cancel is enabled with the same duration as focus 515 // timeout. 516 handler.postDelayed(() -> { 517 Logger.d(TAG, "Completing focus result as unsuccessful after timeout!"); 518 focusMeteringResultCompleter.set(FocusMeteringResult.create(false)); 519 }, AUTO_FOCUS_TIMEOUT_DURATION); 520 521 long cancelDurationMillis = action.getAutoCancelDurationInMillis(); 522 Logger.d(TAG, "Focus action auto cancel duration: " + cancelDurationMillis + " ms"); 523 524 if (cancelDurationMillis > 0L) { 525 handler.postDelayed(() -> { 526 Logger.d(TAG, "Cancelling focus operation after " + cancelDurationMillis + " ms"); 527 focusMeteringResultCompleter.setException(new OperationCanceledException( 528 "Auto-cancelled after " + cancelDurationMillis + " ms")); 529 }, cancelDurationMillis); 530 } 531 532 // Note that quitSafely isn't immediate and doesn't block, the thread will be closed after 533 // all already submitted tasks are done. 534 handler.postDelayed(handlerThread::quitSafely, 535 Math.max(AUTO_FOCUS_TIMEOUT_DURATION, cancelDurationMillis)); 536 } 537 538 /** Returns a {@link Futures#immediateFuture} which immediately contains a result. */ 539 @Override cancelFocusAndMetering()540 public @NonNull ListenableFuture<Void> cancelFocusAndMetering() { 541 return Futures.immediateFuture(null); 542 } 543 544 /** 545 * Sets a listener to be notified when there are new capture requests submitted. 546 * 547 * <p> Note that the listener will be executed on the calling thread directly using 548 * {@link CameraXExecutors#directExecutor}. To specify the execution thread, use 549 * {@link #setOnNewCaptureRequestListener(Executor, OnNewCaptureRequestListener)}. 550 * 551 * @param listener {@link OnNewCaptureRequestListener} that is notified with the submitted 552 * {@link CaptureConfig} parameters when new capture requests are submitted. 553 */ setOnNewCaptureRequestListener(@onNull OnNewCaptureRequestListener listener)554 public void setOnNewCaptureRequestListener(@NonNull OnNewCaptureRequestListener listener) { 555 setOnNewCaptureRequestListener(CameraXExecutors.directExecutor(), listener); 556 } 557 558 /** 559 * Sets a listener to be notified when there are new capture requests submitted. 560 * 561 * @param executor {@link Executor} used to notify the {@code listener}. 562 * @param listener {@link OnNewCaptureRequestListener} that is notified with the submitted 563 * {@link CaptureConfig} parameters when new capture requests are submitted. 564 */ setOnNewCaptureRequestListener(@onNull Executor executor, @NonNull OnNewCaptureRequestListener listener)565 public void setOnNewCaptureRequestListener(@NonNull Executor executor, 566 @NonNull OnNewCaptureRequestListener listener) { 567 mOnNewCaptureRequestListener = new Pair<>(executor, listener); 568 } 569 570 /** 571 * Clears any listener set via {@link #setOnNewCaptureRequestListener}. 572 */ clearNewCaptureRequestListener()573 public void clearNewCaptureRequestListener() { 574 mOnNewCaptureRequestListener = null; 575 } 576 577 @Override setZoomRatio(float ratio)578 public @NonNull ListenableFuture<Void> setZoomRatio(float ratio) { 579 mZoomRatio = ratio; 580 return Futures.immediateFuture(null); 581 } 582 583 /** Gets the linear zoom value set with {@link #setZoomRatio}. */ getZoomRatio()584 public float getZoomRatio() { 585 return mZoomRatio; 586 } 587 588 @Override setLinearZoom(float linearZoom)589 public @NonNull ListenableFuture<Void> setLinearZoom(float linearZoom) { 590 mLinearZoom = linearZoom; 591 return Futures.immediateFuture(null); 592 } 593 594 /** Gets the linear zoom value set with {@link #setLinearZoom}. */ getLinearZoom()595 public float getLinearZoom() { 596 return mLinearZoom; 597 } 598 599 /** Gets the last focus metering action submitted with {@link #startFocusAndMetering}. */ getLastSubmittedFocusMeteringAction()600 public @Nullable FocusMeteringAction getLastSubmittedFocusMeteringAction() { 601 return mLastSubmittedFocusMeteringAction; 602 } 603 604 @Override addInteropConfig(@onNull Config config)605 public void addInteropConfig(@NonNull Config config) { 606 for (Config.Option<?> option : config.listOptions()) { 607 @SuppressWarnings("unchecked") 608 Config.Option<Object> objectOpt = (Config.Option<Object>) option; 609 mInteropConfig.insertOption(objectOpt, config.retrieveOption(objectOpt)); 610 } 611 } 612 613 @Override clearInteropConfig()614 public void clearInteropConfig() { 615 mInteropConfig = MutableOptionsBundle.create(); 616 } 617 618 @Override getInteropConfig()619 public @NonNull Config getInteropConfig() { 620 return MutableOptionsBundle.from(mInteropConfig); 621 } 622 623 /** 624 * Submits a {@link CaptureResult} to be used for the first pending capture request. 625 * 626 * <p> If there are no pending capture requests, the `CaptureResult` is kept in a queue to be 627 * used in future capture requests. 628 * 629 * <p> This method will complete a corresponding capture request according to the provided 630 * capture result. 631 * 632 * <p> For applying a capture result to all already submitted capture requests, use the 633 * {@link #completeAllCaptureRequests} method instead. 634 */ submitCaptureResult(@onNull CaptureResult captureResult)635 public void submitCaptureResult(@NonNull CaptureResult captureResult) { 636 synchronized (mLock) { 637 mCaptureResults.add(captureResult); 638 } 639 applyCaptureResults(); 640 } 641 642 /** 643 * Completes all the incomplete capture requests with the provided {@link CaptureResult}. 644 * 645 * <p> Note that {@link ImageCapture#takePicture} methods send requests to camera asynchronously 646 * and thus a capture request from {@link ImageCapture} may not be available immediately. 647 * Consider using {@link #setOnNewCaptureRequestListener} to know when a capture request has 648 * been submitted before using this method right after {@code ImageCapture#takePicture}. 649 * Furthermore, {@code ImageCapture} queues capture requests before submitting to camera when 650 * multiple captures are requested. So it is recommended to use {@link #submitCaptureResult} 651 * whenever possible to avoid confusing and complicated scenario in integration tests. 652 */ completeAllCaptureRequests(@onNull CaptureResult captureResult)653 public void completeAllCaptureRequests(@NonNull CaptureResult captureResult) { 654 synchronized (mLock) { 655 // Add CaptureResult instances for all pending requests first. 656 for (int i = 0; i < mSubmittedCaptureRequests.size(); i++) { 657 mCaptureResults.add(captureResult); 658 } 659 } 660 661 applyCaptureResults(); 662 } 663 applyCaptureResults()664 private void applyCaptureResults() { 665 synchronized (mLock) { 666 while (!mCaptureResults.isEmpty()) { 667 CaptureResult captureResult = mCaptureResults.getFirst(); 668 669 if (completeFirstPendingCaptureRequest(captureResult.getCaptureStatus(), 670 captureResult.getCameraCaptureResult())) { 671 mCaptureResults.removeFirst(); 672 } else { 673 Logger.d(TAG, "applyCaptureResults: failed to notify"); 674 break; 675 } 676 } 677 } 678 } 679 680 /** 681 * Disables the auto-completion behavior of {@link #startFocusAndMetering(FocusMeteringAction)}. 682 * 683 * <p> Currently, {@link #startFocusAndMetering(FocusMeteringAction)} immediately provides a 684 * completed result by default and no request result is ever pending. This will allow to disable 685 * that behavior so that focus metering status of started but not completed can be tested. 686 * 687 * @param disable Whether to disable or enable. 688 * 689 * @see #submitFocusMeteringResult(FocusMeteringResult) 690 */ 691 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) disableFocusMeteringAutoComplete(boolean disable)692 public void disableFocusMeteringAutoComplete(boolean disable) { 693 mIsFocusMeteringAutoCompleted = !disable; 694 } 695 696 /** 697 * Submits a {@link FocusMeteringResult} to be used for the first pending focus-metering 698 * request. 699 * 700 * <p> This method will complete a corresponding focus-metering request according to the 701 * provided result. If there are no pending requests, the `FocusMeteringResult` is kept in a 702 * queue to be used in future requests. 703 * 704 * <p> For applying a focus metering result to all already submitted requests, use the 705 * {@link #completeAllFocusMeteringRequests} method instead. 706 * 707 * @see CameraControl#startFocusAndMetering(FocusMeteringAction) 708 * @see #disableFocusMeteringAutoComplete(boolean) 709 */ 710 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) submitFocusMeteringResult(@onNull FocusMeteringResult focusMeteringResult)711 public void submitFocusMeteringResult(@NonNull FocusMeteringResult focusMeteringResult) { 712 synchronized (mLock) { 713 mFocusMeteringResults.add(focusMeteringResult); 714 } 715 applyFocusMeteringResults(); 716 } 717 718 /** 719 * Completes all the incomplete focus-metering requests with the provided 720 * {@link FocusMeteringResult}. 721 * 722 * @see CameraControl#startFocusAndMetering(FocusMeteringAction) 723 * @see #disableFocusMeteringAutoComplete(boolean) 724 */ 725 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) completeAllFocusMeteringRequests(@onNull FocusMeteringResult focusMeteringResult)726 public void completeAllFocusMeteringRequests(@NonNull FocusMeteringResult focusMeteringResult) { 727 synchronized (mLock) { 728 // Add FocusMeteringResult instances for all pending requests first. 729 for (int i = 0; i < mRequestedFocusMeteringActions.size(); i++) { 730 mFocusMeteringResults.add(focusMeteringResult); 731 } 732 } 733 734 applyFocusMeteringResults(); 735 } 736 applyFocusMeteringResults()737 private void applyFocusMeteringResults() { 738 synchronized (mLock) { 739 while (!mFocusMeteringResults.isEmpty()) { 740 FocusMeteringResult focusMeteringResult = mFocusMeteringResults.getFirst(); 741 742 if (completeFirstPendingFocusMeteringRequest(focusMeteringResult)) { 743 mFocusMeteringResults.removeFirst(); 744 } else { 745 Logger.d(TAG, "applyFocusMeteringResults: failed to notify"); 746 break; 747 } 748 } 749 } 750 } 751 completeFirstPendingFocusMeteringRequest( @onNull FocusMeteringResult focusMeteringResult)752 private boolean completeFirstPendingFocusMeteringRequest( 753 @NonNull FocusMeteringResult focusMeteringResult) { 754 FocusMeteringAction focusMeteringAction; 755 CallbackToFutureAdapter.Completer<FocusMeteringResult> completer; 756 757 synchronized (mLock) { 758 if (mRequestedFocusMeteringActions.isEmpty()) { 759 Logger.d(TAG, 760 "completeFirstPendingFocusMeteringRequest: returning early since " 761 + "mRequestedFocusMeteringActions is empty!"); 762 return false; 763 } 764 765 focusMeteringAction = mRequestedFocusMeteringActions.removeFirst(); 766 completer = mFocusMeteringActionToResultMap.get(focusMeteringAction); 767 } 768 769 if (completer == null) { 770 Logger.e(TAG, "completeFirstPendingFocusMeteringRequest: completer is null!"); 771 return false; 772 } 773 774 completer.set(focusMeteringResult); 775 return true; 776 } 777 778 /** 779 * Adds a listener to be notified when there are completed capture requests. 780 * 781 * @param listener {@link CaptureSuccessListener} that is notified with the submitted 782 * {@link CaptureConfig} parameters when capture requests are completed. 783 */ 784 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) addCaptureSuccessListener(@onNull CaptureSuccessListener listener)785 public void addCaptureSuccessListener(@NonNull CaptureSuccessListener listener) { 786 mCaptureSuccessListeners.add(listener); 787 } 788 789 /** 790 * Removes a {@link CaptureSuccessListener} if it exist. 791 */ 792 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) removeCaptureSuccessListener(@onNull CaptureSuccessListener listener)793 public void removeCaptureSuccessListener(@NonNull CaptureSuccessListener listener) { 794 mCaptureSuccessListeners.remove(listener); 795 } 796 notifyCaptureSuccess(@onNull CameraCaptureResult result)797 private void notifyCaptureSuccess(@NonNull CameraCaptureResult result) { 798 Logger.d(TAG, "notifyCaptureComplete: mCaptureCompleteListeners = " 799 + mCaptureSuccessListeners); 800 for (CaptureSuccessListener listener : mCaptureSuccessListeners) { 801 listener.onCompleted(result); 802 } 803 } 804 805 /** A listener which is used to notify when there are new submitted capture requests */ 806 public interface OnNewCaptureRequestListener { 807 /** Called when there are new submitted capture request */ onNewCaptureRequests(@onNull List<CaptureConfig> captureConfigs)808 void onNewCaptureRequests(@NonNull List<CaptureConfig> captureConfigs); 809 } 810 811 /** 812 * A listener which is used to notify when submitted capture requests are completed 813 * successfully. 814 * 815 * <p> The reason we need to listen to success case specifically is because of how CameraX image 816 * capture flow works internally. In case of success, a real android.media.Image instance is 817 * also expected from ImageReader which makes this kind of listener necessary for the proper 818 * implementation of a fake TakePictureManager. 819 */ 820 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 821 public interface CaptureSuccessListener { 822 /** Called when a submitted capture request has been completed successfully. */ onCompleted(@onNull CameraCaptureResult result)823 void onCompleted(@NonNull CameraCaptureResult result); 824 } 825 } 826