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