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