1 /*
2  * Copyright 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.camera2.internal;
18 
19 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_OFF;
20 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON;
21 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
22 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH;
23 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_EXTERNAL_FLASH;
24 import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY;
25 import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO;
26 import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
27 import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF;
28 import static android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_AUTO;
29 import static android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_OFF;
30 import static android.hardware.camera2.CameraMetadata.FLASH_MODE_OFF;
31 import static android.hardware.camera2.CameraMetadata.FLASH_MODE_TORCH;
32 
33 import static com.google.common.truth.Truth.assertThat;
34 
35 import static org.hamcrest.CoreMatchers.equalTo;
36 import static org.junit.Assert.assertTrue;
37 import static org.junit.Assert.fail;
38 import static org.junit.Assume.assumeFalse;
39 import static org.junit.Assume.assumeThat;
40 import static org.junit.Assume.assumeTrue;
41 import static org.mockito.ArgumentMatchers.any;
42 import static org.mockito.ArgumentMatchers.anyInt;
43 import static org.mockito.Mockito.mock;
44 import static org.mockito.Mockito.never;
45 import static org.mockito.Mockito.reset;
46 import static org.mockito.Mockito.timeout;
47 import static org.mockito.Mockito.times;
48 import static org.mockito.Mockito.verify;
49 
50 import android.app.Instrumentation;
51 import android.content.Context;
52 import android.graphics.Rect;
53 import android.hardware.camera2.CameraCaptureSession;
54 import android.hardware.camera2.CameraCharacteristics;
55 import android.hardware.camera2.CameraDevice;
56 import android.hardware.camera2.CaptureRequest;
57 import android.os.Build;
58 import android.os.Handler;
59 import android.os.HandlerThread;
60 
61 import androidx.annotation.RequiresApi;
62 import androidx.camera.camera2.Camera2Config;
63 import androidx.camera.camera2.impl.Camera2ImplConfig;
64 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
65 import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
66 import androidx.camera.camera2.internal.compat.workaround.AutoFlashAEModeDisabler;
67 import androidx.camera.camera2.internal.util.TestUtil;
68 import androidx.camera.camera2.interop.Camera2Interop;
69 import androidx.camera.core.CameraControl;
70 import androidx.camera.core.CameraSelector;
71 import androidx.camera.core.CameraXConfig;
72 import androidx.camera.core.FocusMeteringAction;
73 import androidx.camera.core.ImageAnalysis;
74 import androidx.camera.core.ImageCapture;
75 import androidx.camera.core.ImageProxy;
76 import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
77 import androidx.camera.core.impl.CameraCaptureCallback;
78 import androidx.camera.core.impl.CameraCaptureResult;
79 import androidx.camera.core.impl.CameraControlInternal;
80 import androidx.camera.core.impl.CaptureConfig;
81 import androidx.camera.core.impl.Quirks;
82 import androidx.camera.core.impl.SessionConfig;
83 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
84 import androidx.camera.core.internal.CameraUseCaseAdapter;
85 import androidx.camera.testing.impl.CameraUtil;
86 import androidx.camera.testing.impl.CameraXUtil;
87 import androidx.camera.testing.impl.HandlerUtil;
88 import androidx.concurrent.futures.CallbackToFutureAdapter;
89 import androidx.core.os.HandlerCompat;
90 import androidx.test.core.app.ApplicationProvider;
91 import androidx.test.ext.junit.runners.AndroidJUnit4;
92 import androidx.test.filters.LargeTest;
93 import androidx.test.filters.SdkSuppress;
94 import androidx.test.filters.SmallTest;
95 import androidx.test.platform.app.InstrumentationRegistry;
96 
97 import com.google.common.truth.BooleanSubject;
98 import com.google.common.util.concurrent.ListenableFuture;
99 
100 import org.jspecify.annotations.NonNull;
101 import org.junit.After;
102 import org.junit.Assert;
103 import org.junit.Before;
104 import org.junit.Rule;
105 import org.junit.Test;
106 import org.junit.rules.TestRule;
107 import org.junit.runner.RunWith;
108 import org.mockito.ArgumentCaptor;
109 import org.mockito.Mockito;
110 
111 import java.util.Arrays;
112 import java.util.List;
113 import java.util.concurrent.CountDownLatch;
114 import java.util.concurrent.ExecutionException;
115 import java.util.concurrent.Executor;
116 import java.util.concurrent.ScheduledExecutorService;
117 import java.util.concurrent.TimeUnit;
118 import java.util.concurrent.TimeoutException;
119 
120 @SmallTest
121 @RunWith(AndroidJUnit4.class)
122 @SdkSuppress(minSdkVersion = 21)
123 public final class Camera2CameraControlImplDeviceTest {
124     @Rule
125     public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTestAndPostTest(
126             new CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
127     );
128 
129     private Camera2CameraControlImpl mCamera2CameraControlImpl;
130     private CameraControlInternal.ControlUpdateCallback mControlUpdateCallback;
131     @SuppressWarnings("unchecked")
132     private ArgumentCaptor<List<CaptureConfig>> mCaptureConfigArgumentCaptor =
133             ArgumentCaptor.forClass(List.class);
134     private HandlerThread mHandlerThread;
135     private Handler mHandler;
136     private ScheduledExecutorService mExecutorService;
137 
138     private CameraCharacteristics mCameraCharacteristics;
139     private CameraCharacteristicsCompat mCameraCharacteristicsCompat;
140     private boolean mHasFlashUnit;
141     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
142     private CameraUseCaseAdapter mCamera;
143     private Quirks mCameraQuirks;
144 
145     @Before
setUp()146     public void setUp() throws InterruptedException {
147         mHandlerThread = new HandlerThread("ControlThread");
148         mHandlerThread.start();
149         mHandler = HandlerCompat.createAsync(mHandlerThread.getLooper());
150 
151         Context context = ApplicationProvider.getApplicationContext();
152         CameraXConfig config = Camera2Config.defaultConfig();
153         CameraXUtil.initialize(context, config);
154 
155         setUp(CameraSelector.LENS_FACING_BACK);
156     }
157 
setUp(int lensFacing)158     private void setUp(int lensFacing) throws InterruptedException {
159         assumeTrue(CameraUtil.hasCameraWithLensFacing(lensFacing));
160 
161         mCameraCharacteristics = CameraUtil.getCameraCharacteristics(lensFacing);
162         Boolean hasFlashUnit =
163                 mCameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
164         mHasFlashUnit = hasFlashUnit != null && hasFlashUnit.booleanValue();
165 
166         mControlUpdateCallback = mock(CameraControlInternal.ControlUpdateCallback.class);
167 
168         mExecutorService = CameraXExecutors.newHandlerExecutor(mHandler);
169         String cameraId = CameraUtil.getCameraIdWithLensFacing(lensFacing);
170         mCameraCharacteristicsCompat = CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
171                 mCameraCharacteristics, cameraId);
172         mCamera2CameraControlImpl = new Camera2CameraControlImpl(mCameraCharacteristicsCompat,
173                 mExecutorService, mExecutorService, mControlUpdateCallback);
174         mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristicsCompat);
175 
176         mCamera2CameraControlImpl.incrementUseCount();
177         mCamera2CameraControlImpl.setActive(true);
178         HandlerUtil.waitForLooperToIdle(mHandler);
179     }
180 
181     @After
tearDown()182     public void tearDown() throws InterruptedException, ExecutionException, TimeoutException {
183         if (mCamera != null) {
184             mInstrumentation.runOnMainSync(() ->
185                     //TODO: The removeUseCases() call might be removed after clarifying the
186                     // abortCaptures() issue in b/162314023.
187                     mCamera.removeUseCases(mCamera.getUseCases())
188             );
189         }
190 
191         CameraXUtil.shutdown().get(10000, TimeUnit.MILLISECONDS);
192         if (mHandlerThread != null) {
193             mHandlerThread.quitSafely();
194         }
195     }
196 
isAndroidRZoomEnabled()197     private boolean isAndroidRZoomEnabled() {
198         return ZoomControl.isAndroidRZoomSupported(mCameraCharacteristicsCompat);
199     }
200 
getMaxAfRegionCount()201     private int getMaxAfRegionCount() {
202         return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
203     }
204 
getMaxAeRegionCount()205     private int getMaxAeRegionCount() {
206         return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
207     }
208 
getMaxAwbRegionCount()209     private int getMaxAwbRegionCount() {
210         return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB);
211     }
212 
isAeSupported()213     private boolean isAeSupported() {
214         int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
215         for (int mode : modes) {
216             if (mode == CONTROL_AE_MODE_ON) {
217                 return true;
218             }
219         }
220         return false;
221     }
222 
223     @Test
canIncrementDecrementUseCount()224     public void canIncrementDecrementUseCount() {
225         // incrementUseCount() in setup()
226         assertThat(mCamera2CameraControlImpl.getUseCount()).isEqualTo(1);
227 
228         mCamera2CameraControlImpl.decrementUseCount();
229 
230         assertThat(mCamera2CameraControlImpl.getUseCount()).isEqualTo(0);
231     }
232 
233     @Test(expected = IllegalStateException.class)
decrementUseCountLessThanZero_getException()234     public void decrementUseCountLessThanZero_getException() {
235         // incrementUseCount() in setup()
236         assertThat(mCamera2CameraControlImpl.getUseCount()).isEqualTo(1);
237 
238         mCamera2CameraControlImpl.decrementUseCount();
239         mCamera2CameraControlImpl.decrementUseCount();
240     }
241 
242     @Test
setTemplate_updateCameraControlSessionConfig()243     public void setTemplate_updateCameraControlSessionConfig() {
244         mCamera2CameraControlImpl.setTemplate(CameraDevice.TEMPLATE_RECORD);
245 
246         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
247         assertThat(sessionConfig.getTemplateType()).isEqualTo(CameraDevice.TEMPLATE_RECORD);
248     }
249 
250     @Test
setTemplatePreview_afModeToContinuousPicture()251     public void setTemplatePreview_afModeToContinuousPicture() {
252         mCamera2CameraControlImpl.setTemplate(CameraDevice.TEMPLATE_PREVIEW);
253 
254         Camera2ImplConfig camera2Config =
255                 new Camera2ImplConfig(mCamera2CameraControlImpl.getSessionOptions());
256         assertAfMode(camera2Config, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
257     }
258 
259     @Test
setTemplateRecord_afModeToContinuousVideo()260     public void setTemplateRecord_afModeToContinuousVideo() {
261         mCamera2CameraControlImpl.setTemplate(CameraDevice.TEMPLATE_RECORD);
262 
263         Camera2ImplConfig camera2Config =
264                 new Camera2ImplConfig(mCamera2CameraControlImpl.getSessionOptions());
265         assertAfMode(camera2Config, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
266     }
267 
268     @Test
defaultAFAWBMode_ShouldBeCAFWhenNotFocusLocked()269     public void defaultAFAWBMode_ShouldBeCAFWhenNotFocusLocked() {
270         Camera2ImplConfig singleConfig = new Camera2ImplConfig(
271                 mCamera2CameraControlImpl.getSessionOptions());
272         assertThat(
273                 singleConfig.getCaptureRequestOption(
274                         CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF))
275                 .isEqualTo(CaptureRequest.CONTROL_MODE_AUTO);
276 
277         assertAfMode(singleConfig, CONTROL_AF_MODE_CONTINUOUS_PICTURE);
278         assertAwbMode(singleConfig, CONTROL_AWB_MODE_AUTO);
279     }
280 
281     @Test
setFlashModeAuto_aeModeSetAndRequestUpdated()282     public void setFlashModeAuto_aeModeSetAndRequestUpdated() throws InterruptedException {
283         mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_AUTO);
284 
285         HandlerUtil.waitForLooperToIdle(mHandler);
286 
287         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
288         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
289         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
290                 sessionConfig.getImplementationOptions());
291 
292         assertAeMode(camera2Config, CONTROL_AE_MODE_ON_AUTO_FLASH);
293         assertThat(mCamera2CameraControlImpl.getFlashMode()).isEqualTo(
294                 ImageCapture.FLASH_MODE_AUTO);
295         // ZSL only support API >= 23. ZslControlImpl will be created for API >= 23, otherwise
296         // ZslControlNoOpImpl will be created, which always return false for this flag.
297         if (Build.VERSION.SDK_INT >= 23) {
298             assertThat(
299                     mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isTrue();
300         } else {
301             assertThat(
302                     mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isFalse();
303         }
304     }
305 
306     @Test
setFlashModeOff_aeModeSetAndRequestUpdated()307     public void setFlashModeOff_aeModeSetAndRequestUpdated() throws InterruptedException {
308         mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_OFF);
309 
310         HandlerUtil.waitForLooperToIdle(mHandler);
311 
312         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
313         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
314         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
315                 sessionConfig.getImplementationOptions());
316 
317         assertAeMode(camera2Config, CONTROL_AE_MODE_ON);
318 
319         assertThat(mCamera2CameraControlImpl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_OFF);
320         assertThat(mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isFalse();
321     }
322 
323     @Test
setFlashModeOn_aeModeSetAndRequestUpdated()324     public void setFlashModeOn_aeModeSetAndRequestUpdated() throws InterruptedException {
325         mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_ON);
326 
327         HandlerUtil.waitForLooperToIdle(mHandler);
328 
329         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
330         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
331         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
332                 sessionConfig.getImplementationOptions());
333 
334         assertAeMode(camera2Config, CONTROL_AE_MODE_ON_ALWAYS_FLASH);
335 
336         assertThat(mCamera2CameraControlImpl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_ON);
337         // ZSL only support API >= 23. ZslControlImpl will be created for API >= 23, otherwise
338         // ZslControlNoOpImpl will be created, which always return false for this flag.
339         if (Build.VERSION.SDK_INT >= 23) {
340             assertThat(
341                     mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isTrue();
342         } else {
343             assertThat(
344                     mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isFalse();
345         }
346     }
347 
348     @Test
349     @SdkSuppress(minSdkVersion = 28)
enableExternalFlashAeMode_aeModeSetAndRequestUpdated()350     public void enableExternalFlashAeMode_aeModeSetAndRequestUpdated() throws InterruptedException {
351         setUp(CameraSelector.LENS_FACING_FRONT);
352 
353         assumeThat("CONTROL_AE_MODE_ON_EXTERNAL_FLASH not supported",
354                 mCamera2CameraControlImpl.getSupportedAeMode(CONTROL_AE_MODE_ON_EXTERNAL_FLASH),
355                 equalTo(CONTROL_AE_MODE_ON_EXTERNAL_FLASH));
356         // Other flash modes may override the external flash AE mode
357         mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_SCREEN);
358         Mockito.reset(mControlUpdateCallback);
359 
360         mCamera2CameraControlImpl.getFocusMeteringControl().enableExternalFlashAeMode(true);
361 
362         HandlerUtil.waitForLooperToIdle(mHandler);
363 
364         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
365         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
366         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
367                 sessionConfig.getImplementationOptions());
368 
369         assertAeMode(camera2Config, CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
370     }
371 
372     @Test
373     @SdkSuppress(minSdkVersion = 28)
disableExternalFlashAeMode_aeModeUnsetAndRequestUpdated()374     public void disableExternalFlashAeMode_aeModeUnsetAndRequestUpdated()
375             throws InterruptedException {
376         setUp(CameraSelector.LENS_FACING_FRONT);
377 
378         assumeThat("CONTROL_AE_MODE_ON_EXTERNAL_FLASH not supported",
379                 mCamera2CameraControlImpl.getSupportedAeMode(CONTROL_AE_MODE_ON_EXTERNAL_FLASH),
380                 equalTo(CONTROL_AE_MODE_ON_EXTERNAL_FLASH));
381         mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_SCREEN);
382         Mockito.reset(mControlUpdateCallback);
383 
384         mCamera2CameraControlImpl.getFocusMeteringControl().enableExternalFlashAeMode(true);
385         HandlerUtil.waitForLooperToIdle(mHandler);
386 
387         mCamera2CameraControlImpl.getFocusMeteringControl().enableExternalFlashAeMode(false);
388 
389         HandlerUtil.waitForLooperToIdle(mHandler);
390 
391         verify(mControlUpdateCallback, times(2)).onCameraControlUpdateSessionConfig();
392         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
393         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
394                 sessionConfig.getImplementationOptions());
395 
396         assertThat(camera2Config.getCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE,
397                 null)).isNotEqualTo(CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
398     }
399 
400     @Test
enableTorch_aeModeSetAndRequestUpdated()401     public void enableTorch_aeModeSetAndRequestUpdated() throws InterruptedException {
402         assumeTrue(mHasFlashUnit);
403         mCamera2CameraControlImpl.enableTorch(true);
404         HandlerUtil.waitForLooperToIdle(mHandler);
405         verifyControlAeModeAndFlashMode(CONTROL_AE_MODE_ON, FLASH_MODE_TORCH);
406     }
407 
408     @Test
disableTorchFlashModeAuto_aeModeSetAndRequestUpdated()409     public void disableTorchFlashModeAuto_aeModeSetAndRequestUpdated() throws InterruptedException {
410         assumeTrue(mHasFlashUnit);
411         mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_AUTO);
412         mCamera2CameraControlImpl.enableTorch(false);
413 
414         HandlerUtil.waitForLooperToIdle(mHandler);
415 
416         verify(mControlUpdateCallback, times(2)).onCameraControlUpdateSessionConfig();
417         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
418         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
419                 sessionConfig.getImplementationOptions());
420 
421         assertAeMode(camera2Config, CONTROL_AE_MODE_ON_AUTO_FLASH);
422 
423         assertThat(camera2Config.getCaptureRequestOption(
424                 CaptureRequest.FLASH_MODE, -1))
425                 .isEqualTo(-1);
426 
427         verify(mControlUpdateCallback, times(1)).onCameraControlCaptureRequests(
428                 mCaptureConfigArgumentCaptor.capture());
429         CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
430         Camera2ImplConfig resultCaptureConfig =
431                 new Camera2ImplConfig(captureConfig.getImplementationOptions());
432 
433         assertAeMode(resultCaptureConfig, CONTROL_AE_MODE_ON);
434 
435     }
436 
437     @Test
438     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
setTorchStrengthLevel_valueUpdated()439     public void setTorchStrengthLevel_valueUpdated()
440             throws ExecutionException, InterruptedException {
441         assumeTrue(mCameraCharacteristicsCompat.isTorchStrengthLevelSupported());
442 
443         // Arrange
444         ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
445         imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
446         mCamera = CameraUtil.createCameraAndAttachUseCase(
447                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
448                 imageAnalysis);
449         Camera2CameraControlImpl camera2CameraControlImpl =
450                 TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
451         camera2CameraControlImpl.enableTorch(true).get();
452 
453         // Act
454         int maxStrength = mCamera.getCameraInfo().getMaxTorchStrengthLevel();
455         int defaultStrength = mCamera.getCameraInfo().getTorchStrengthLevel().getValue();
456         // If the default strength is the max, set the strength to 1, otherwise, set to max.
457         int customizedStrength = defaultStrength == maxStrength ? 1 : maxStrength;
458         camera2CameraControlImpl.setTorchStrengthLevel(customizedStrength).get();
459 
460         // Assert: the customized strength is applied
461         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
462                 camera2CameraControlImpl.getSessionConfig().getImplementationOptions());
463         assertThat(camera2Config.getCaptureRequestOption(
464                 CaptureRequest.FLASH_STRENGTH_LEVEL, -1))
465                 .isEqualTo(customizedStrength);
466     }
467 
468     @Test
469     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
setTorchStrengthLevel_throwExceptionIfLessThanOne()470     public void setTorchStrengthLevel_throwExceptionIfLessThanOne()
471             throws ExecutionException, InterruptedException {
472         assumeTrue(mCameraCharacteristicsCompat.isTorchStrengthLevelSupported());
473 
474         // Arrange
475         ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
476         imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
477         mCamera = CameraUtil.createCameraAndAttachUseCase(
478                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
479                 imageAnalysis);
480         Camera2CameraControlImpl camera2CameraControlImpl =
481                 TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
482         camera2CameraControlImpl.enableTorch(true).get();
483 
484         // Act & Assert
485         try {
486             camera2CameraControlImpl.setTorchStrengthLevel(0).get();
487         } catch (ExecutionException e) {
488             assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
489             return;
490         }
491 
492         fail("setTorchStrength didn't fail with an IllegalArgumentException.");
493     }
494 
495     @Test
496     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
setTorchStrengthLevel_throwExceptionIfLargerThanMax()497     public void setTorchStrengthLevel_throwExceptionIfLargerThanMax()
498             throws ExecutionException, InterruptedException {
499         assumeTrue(mCameraCharacteristicsCompat.isTorchStrengthLevelSupported());
500 
501         // Arrange
502         ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
503         imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
504         mCamera = CameraUtil.createCameraAndAttachUseCase(
505                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
506                 imageAnalysis);
507         Camera2CameraControlImpl camera2CameraControlImpl =
508                 TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
509         camera2CameraControlImpl.enableTorch(true).get();
510 
511         // Act & Assert
512         try {
513             camera2CameraControlImpl.setTorchStrengthLevel(
514                     mCamera.getCameraInfo().getMaxTorchStrengthLevel() + 1).get();
515         } catch (ExecutionException e) {
516             assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
517             return;
518         }
519 
520         fail("setTorchStrength didn't fail with an IllegalArgumentException.");
521     }
522 
523     @SdkSuppress(minSdkVersion = 35)
524     @Test
enableLowLightBoost_aeModeSetAndRequestUpdated()525     public void enableLowLightBoost_aeModeSetAndRequestUpdated() throws InterruptedException {
526         assumeTrue(mCamera2CameraControlImpl.getLowLightBoostControl().isLowLightBoostSupported());
527         mCamera2CameraControlImpl.enableLowLightBoostAsync(true);
528         HandlerUtil.waitForLooperToIdle(mHandler);
529         verifyControlAeModeAndFlashMode(CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY,
530                 FLASH_MODE_OFF);
531     }
532 
533     @SdkSuppress(minSdkVersion = 35)
534     @Test
enableLowLightBoostCanOverrideTorch_aeModeSetAndRequestUpdated()535     public void enableLowLightBoostCanOverrideTorch_aeModeSetAndRequestUpdated()
536             throws InterruptedException {
537         assumeTrue(mCamera2CameraControlImpl.getLowLightBoostControl().isLowLightBoostSupported());
538         assumeTrue(mHasFlashUnit);
539 
540         mCamera2CameraControlImpl.enableTorch(true);
541         HandlerUtil.waitForLooperToIdle(mHandler);
542         verifyControlAeModeAndFlashMode(CONTROL_AE_MODE_ON, FLASH_MODE_TORCH);
543 
544         mCamera2CameraControlImpl.enableLowLightBoostAsync(true);
545         HandlerUtil.waitForLooperToIdle(mHandler);
546         verifyControlAeModeAndFlashMode(CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY,
547                 FLASH_MODE_OFF);
548     }
549 
verifyControlAeModeAndFlashMode(int expectedAeMode, int expectedFlashMode)550     private void verifyControlAeModeAndFlashMode(int expectedAeMode, int expectedFlashMode) {
551         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
552         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
553         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
554                 sessionConfig.getImplementationOptions());
555 
556         assertAeMode(camera2Config, expectedAeMode);
557 
558         assertThat(
559                 camera2Config.getCaptureRequestOption(
560                         CaptureRequest.FLASH_MODE, FLASH_MODE_OFF))
561                 .isEqualTo(expectedFlashMode);
562         Mockito.reset(mControlUpdateCallback);
563     }
564 
565     @Test
566     @LargeTest
triggerAf_futureSucceeds()567     public void triggerAf_futureSucceeds() throws Exception {
568         Camera2CameraControlImpl camera2CameraControlImpl =
569                 createCamera2CameraControlWithPhysicalCamera();
570 
571         ListenableFuture<CameraCaptureResult> future = CallbackToFutureAdapter.getFuture(c -> {
572             camera2CameraControlImpl.mExecutor.execute(() ->
573                     camera2CameraControlImpl.getFocusMeteringControl().triggerAf(
574                             c, /* overrideAeMode */ false));
575             return "triggerAf";
576         });
577 
578         future.get(5, TimeUnit.SECONDS);
579     }
580 
581     @Test
captureMaxQuality_shouldSuccess()582     public void captureMaxQuality_shouldSuccess()
583             throws ExecutionException, InterruptedException, TimeoutException {
584         captureTest(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
585                 ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
586     }
587 
588     @Test
captureMiniLatency_shouldSuccess()589     public void captureMiniLatency_shouldSuccess()
590             throws ExecutionException, InterruptedException, TimeoutException {
591         captureTest(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
592                 ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
593     }
594 
595     @Test
captureMaxQuality_torchAsFlash_shouldSuccess()596     public void captureMaxQuality_torchAsFlash_shouldSuccess()
597             throws ExecutionException, InterruptedException, TimeoutException {
598         captureTest(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
599                 ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH);
600     }
601 
602     @Test
captureMiniLatency_torchAsFlash_shouldSuccess()603     public void captureMiniLatency_torchAsFlash_shouldSuccess()
604             throws ExecutionException, InterruptedException, TimeoutException {
605         captureTest(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
606                 ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH);
607     }
608 
609     @Test
610     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
capture_torchAsFlash_shouldUseDefaultTorchStrength()611     public void capture_torchAsFlash_shouldUseDefaultTorchStrength()
612             throws ExecutionException, InterruptedException, TimeoutException {
613         assumeTrue(mCameraCharacteristicsCompat.isTorchStrengthLevelSupported());
614 
615         // Arrange: explicitly set flash type to use torch as flash
616         ImageCapture.Builder imageCaptureBuilder = new ImageCapture.Builder().setFlashType(
617                 ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH).setFlashMode(
618                 ImageCapture.FLASH_MODE_ON);
619         CameraCaptureSession.CaptureCallback captureCallback = mock(
620                 CameraCaptureSession.CaptureCallback.class);
621         new Camera2Interop.Extender<>(imageCaptureBuilder).setSessionCaptureCallback(
622                 captureCallback);
623         ImageCapture imageCapture = imageCaptureBuilder.build();
624         mCamera = CameraUtil.createCameraAndAttachUseCase(
625                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
626                 imageCapture);
627         Camera2CameraControlImpl camera2CameraControlImpl =
628                 TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
629 
630         // Act
631         int maxStrength = mCamera.getCameraInfo().getMaxTorchStrengthLevel();
632         int defaultStrength = mCamera.getCameraInfo().getTorchStrengthLevel().getValue();
633         // If the default strength is the max, set the strength to 1, otherwise, set to max.
634         int customizedStrength = defaultStrength == maxStrength ? 1 : maxStrength;
635         camera2CameraControlImpl.setTorchStrengthLevel(customizedStrength).get();
636 
637         // Assert: the capture uses default torch strength
638         CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
639         captureConfigBuilder.setTemplateType(CameraDevice.TEMPLATE_STILL_CAPTURE);
640         captureConfigBuilder.addSurface(imageCapture.getSessionConfig().getSurfaces().get(0));
641 
642         camera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_ON);
643         camera2CameraControlImpl.submitStillCaptureRequests(
644                         Arrays.asList(captureConfigBuilder.build()),
645                         ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
646                         ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH)
647                 .get(5, TimeUnit.SECONDS);
648         ArgumentCaptor<CaptureRequest> captureRequestCaptor =
649                 ArgumentCaptor.forClass(CaptureRequest.class);
650         verify(captureCallback, timeout(5000).atLeastOnce())
651                 .onCaptureCompleted(any(), captureRequestCaptor.capture(), any());
652         List<CaptureRequest> results = captureRequestCaptor.getAllValues();
653         for (CaptureRequest result : results) {
654             // None of the capture capture should be sent with the customized strength.
655             assertThat(result.get(CaptureRequest.FLASH_STRENGTH_LEVEL)).isNotEqualTo(
656                     customizedStrength);
657         }
658     }
659 
captureTest(int captureMode, int flashType)660     private void captureTest(int captureMode, int flashType)
661             throws ExecutionException, InterruptedException, TimeoutException {
662         ImageCapture imageCapture = new ImageCapture.Builder().build();
663 
664         mCamera = CameraUtil.createCameraAndAttachUseCase(
665                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
666                 imageCapture);
667 
668         Camera2CameraControlImpl camera2CameraControlImpl =
669                 TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
670 
671         CameraCaptureCallback captureCallback = mock(CameraCaptureCallback.class);
672         CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
673         captureConfigBuilder.setTemplateType(CameraDevice.TEMPLATE_STILL_CAPTURE);
674         captureConfigBuilder.addSurface(imageCapture.getSessionConfig().getSurfaces().get(0));
675         captureConfigBuilder.addCameraCaptureCallback(captureCallback);
676 
677         ListenableFuture<List<Void>> future = camera2CameraControlImpl.submitStillCaptureRequests(
678                 Arrays.asList(captureConfigBuilder.build()), captureMode, flashType);
679 
680         // The future should successfully complete
681         future.get(10, TimeUnit.SECONDS);
682         // CameraCaptureCallback.onCaptureCompleted() should be called to signal a capture attempt.
683         verify(captureCallback, timeout(3000).times(1))
684                 .onCaptureCompleted(anyInt(), any(CameraCaptureResult.class));
685     }
686 
createCamera2CameraControlWithPhysicalCamera()687     private Camera2CameraControlImpl createCamera2CameraControlWithPhysicalCamera() {
688         ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
689         // Make ImageAnalysis active.
690         imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), (image) -> image.close());
691 
692         mCamera = CameraUtil.createCameraAndAttachUseCase(
693                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
694                 imageAnalysis);
695 
696         return TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
697     }
698 
assertArraySize(T[] array, int expectedSize)699     private <T> void assertArraySize(T[] array, int expectedSize) {
700         if (expectedSize == 0) {
701             assertTrue(array == null || array.length == 0);
702         } else {
703             assertThat(array).hasLength(expectedSize);
704         }
705     }
706 
707     @Test
startFocusAndMetering_3ARegionsUpdatedInSessionAndSessionOptions()708     public void startFocusAndMetering_3ARegionsUpdatedInSessionAndSessionOptions()
709             throws InterruptedException {
710         assumeTrue(getMaxAfRegionCount() > 0 || getMaxAeRegionCount() > 0
711                 || getMaxAwbRegionCount() > 0);
712 
713         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
714                 1.0f);
715         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0))
716                 .build();
717         mCamera2CameraControlImpl.startFocusAndMetering(action);
718 
719         HandlerUtil.waitForLooperToIdle(mHandler);
720 
721         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
722         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
723         Camera2ImplConfig repeatingConfig = new Camera2ImplConfig(
724                 sessionConfig.getImplementationOptions());
725 
726         // Here we verify only 3A region count is correct.  Values correctness are left to
727         // FocusMeteringControlTest.
728         int expectedAfCount = Math.min(getMaxAfRegionCount(), 1);
729         int expectedAeCount = Math.min(getMaxAeRegionCount(), 1);
730         int expectedAwbCount = Math.min(getMaxAwbRegionCount(), 1);
731         assertArraySize(repeatingConfig.getCaptureRequestOption(
732                 CaptureRequest.CONTROL_AF_REGIONS, null), expectedAfCount);
733         assertArraySize(repeatingConfig.getCaptureRequestOption(
734                 CaptureRequest.CONTROL_AE_REGIONS, null), expectedAeCount);
735         assertArraySize(repeatingConfig.getCaptureRequestOption(
736                 CaptureRequest.CONTROL_AWB_REGIONS, null), expectedAwbCount);
737 
738         Camera2ImplConfig singleConfig = new Camera2ImplConfig(
739                 mCamera2CameraControlImpl.getSessionOptions());
740         assertArraySize(singleConfig.getCaptureRequestOption(
741                 CaptureRequest.CONTROL_AF_REGIONS, null), expectedAfCount);
742         assertArraySize(singleConfig.getCaptureRequestOption(
743                 CaptureRequest.CONTROL_AE_REGIONS, null), expectedAeCount);
744         assertArraySize(singleConfig.getCaptureRequestOption(
745                 CaptureRequest.CONTROL_AWB_REGIONS, null), expectedAwbCount);
746     }
747 
748     @Test
startFocusAndMetering_AfIsTriggeredProperly()749     public void startFocusAndMetering_AfIsTriggeredProperly() throws InterruptedException {
750         assumeTrue(getMaxAfRegionCount() > 0);
751 
752         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
753                 1.0f);
754         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0))
755                 .build();
756         mCamera2CameraControlImpl.startFocusAndMetering(action);
757         HandlerUtil.waitForLooperToIdle(mHandler);
758 
759         verifyAfMode(CaptureRequest.CONTROL_AF_MODE_AUTO);
760 
761         verify(mControlUpdateCallback).onCameraControlCaptureRequests(
762                 mCaptureConfigArgumentCaptor.capture());
763 
764         CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
765         Camera2ImplConfig resultCaptureConfig =
766                 new Camera2ImplConfig(captureConfig.getImplementationOptions());
767 
768         // Trigger AF
769         assertThat(resultCaptureConfig.getCaptureRequestOption(
770                 CaptureRequest.CONTROL_AF_TRIGGER, null))
771                 .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_START);
772 
773         // Ensures AE_MODE is overridden to CONTROL_AE_MODE_ON to prevent from flash being fired.
774         if (isAeSupported()) {
775             assertThat(resultCaptureConfig.getCaptureRequestOption(
776                     CaptureRequest.CONTROL_AE_MODE, null))
777                     .isEqualTo(CONTROL_AE_MODE_ON);
778         }
779     }
780 
781     @Test
startFocusAndMetering_AFNotInvolved_AfIsNotTriggered()782     public void startFocusAndMetering_AFNotInvolved_AfIsNotTriggered() throws InterruptedException {
783         assumeTrue(getMaxAeRegionCount() > 0 || getMaxAwbRegionCount() > 0);
784 
785         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
786                 1.0f);
787         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0),
788                 FocusMeteringAction.FLAG_AE | FocusMeteringAction.FLAG_AWB)
789                 .build();
790         mCamera2CameraControlImpl.startFocusAndMetering(action);
791         HandlerUtil.waitForLooperToIdle(mHandler);
792 
793         verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
794 
795         verify(mControlUpdateCallback, never()).onCameraControlCaptureRequests(any());
796     }
797 
798     @Test
cancelFocusAndMetering_3ARegionsReset()799     public void cancelFocusAndMetering_3ARegionsReset() throws InterruptedException {
800         assumeTrue(getMaxAfRegionCount() > 0 || getMaxAeRegionCount() > 0
801                 || getMaxAwbRegionCount() > 0);
802 
803         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
804                 1.0f);
805         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0))
806                 .build();
807         mCamera2CameraControlImpl.startFocusAndMetering(action);
808         HandlerUtil.waitForLooperToIdle(mHandler);
809         Mockito.reset(mControlUpdateCallback);
810 
811         mCamera2CameraControlImpl.cancelFocusAndMetering();
812         HandlerUtil.waitForLooperToIdle(mHandler);
813 
814         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
815         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
816         Camera2ImplConfig repeatingConfig = new Camera2ImplConfig(
817                 sessionConfig.getImplementationOptions());
818 
819         assertThat(
820                 repeatingConfig.getCaptureRequestOption(
821                         CaptureRequest.CONTROL_AF_REGIONS, null)).isNull();
822         assertThat(
823                 repeatingConfig.getCaptureRequestOption(
824                         CaptureRequest.CONTROL_AE_REGIONS, null)).isNull();
825         assertThat(
826                 repeatingConfig.getCaptureRequestOption(
827                         CaptureRequest.CONTROL_AWB_REGIONS, null)).isNull();
828 
829 
830         Camera2ImplConfig singleConfig = new Camera2ImplConfig(
831                 mCamera2CameraControlImpl.getSessionOptions());
832         assertThat(
833                 singleConfig.getCaptureRequestOption(
834                         CaptureRequest.CONTROL_AF_REGIONS, null)).isNull();
835         assertThat(
836                 singleConfig.getCaptureRequestOption(
837                         CaptureRequest.CONTROL_AE_REGIONS, null)).isNull();
838         assertThat(
839                 singleConfig.getCaptureRequestOption(
840                         CaptureRequest.CONTROL_AWB_REGIONS, null)).isNull();
841     }
842 
843     @Test
cancelFocusAndMetering_cancelAfProperly()844     public void cancelFocusAndMetering_cancelAfProperly() throws InterruptedException {
845         assumeTrue(getMaxAfRegionCount() > 0);
846         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
847                 1.0f);
848         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0))
849                 .build();
850         mCamera2CameraControlImpl.startFocusAndMetering(action);
851         HandlerUtil.waitForLooperToIdle(mHandler);
852         Mockito.reset(mControlUpdateCallback);
853         mCamera2CameraControlImpl.cancelFocusAndMetering();
854         HandlerUtil.waitForLooperToIdle(mHandler);
855 
856         verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
857 
858         verify(mControlUpdateCallback).onCameraControlCaptureRequests(
859                 mCaptureConfigArgumentCaptor.capture());
860 
861         CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
862         Camera2ImplConfig resultCaptureConfig =
863                 new Camera2ImplConfig(captureConfig.getImplementationOptions());
864 
865         // Trigger AF
866         assertThat(resultCaptureConfig.getCaptureRequestOption(
867                 CaptureRequest.CONTROL_AF_TRIGGER, null))
868                 .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
869     }
870 
verifyAfMode(int expectAfMode)871     private void verifyAfMode(int expectAfMode) {
872         verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
873         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
874         Camera2ImplConfig repeatingConfig = new Camera2ImplConfig(
875                 sessionConfig.getImplementationOptions());
876         assertAfMode(repeatingConfig, expectAfMode);
877     }
878 
879     @Test
cancelFocusAndMetering_AFNotInvolved_notCancelAF()880     public void cancelFocusAndMetering_AFNotInvolved_notCancelAF() throws InterruptedException {
881         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
882                 1.0f);
883         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0),
884                 FocusMeteringAction.FLAG_AE)
885                 .build();
886         mCamera2CameraControlImpl.startFocusAndMetering(action);
887         HandlerUtil.waitForLooperToIdle(mHandler);
888         Mockito.reset(mControlUpdateCallback);
889         mCamera2CameraControlImpl.cancelFocusAndMetering();
890         HandlerUtil.waitForLooperToIdle(mHandler);
891 
892         verify(mControlUpdateCallback, never()).onCameraControlCaptureRequests(any());
893 
894         verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
895     }
896 
897     @Test
startFocus_afModeIsSetToAuto()898     public void startFocus_afModeIsSetToAuto() throws InterruptedException {
899         assumeTrue(getMaxAfRegionCount() > 0);
900         SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f,
901                 1.0f);
902         FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0))
903                 .build();
904         mCamera2CameraControlImpl.startFocusAndMetering(action);
905         HandlerUtil.waitForLooperToIdle(mHandler);
906 
907         Camera2ImplConfig singleConfig = new Camera2ImplConfig(
908                 mCamera2CameraControlImpl.getSessionOptions());
909         assertAfMode(singleConfig, CaptureRequest.CONTROL_AF_MODE_AUTO);
910 
911         mCamera2CameraControlImpl.cancelFocusAndMetering();
912         HandlerUtil.waitForLooperToIdle(mHandler);
913 
914         Camera2ImplConfig singleConfig2 = new Camera2ImplConfig(
915                 mCamera2CameraControlImpl.getSessionOptions());
916         assertAfMode(singleConfig2, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
917     }
918 
isAfModeSupported(int afMode)919     private boolean isAfModeSupported(int afMode) {
920         int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
921         return isModeInList(afMode, modes);
922     }
923 
isAeModeSupported(int aeMode)924     private boolean isAeModeSupported(int aeMode) {
925         int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
926         return isModeInList(aeMode, modes);
927     }
928 
isAwbModeSupported(int awbMode)929     private boolean isAwbModeSupported(int awbMode) {
930         int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
931         return isModeInList(awbMode, modes);
932     }
933 
934 
isModeInList(int mode, int[] modeList)935     private boolean isModeInList(int mode, int[] modeList) {
936         if (modeList == null) {
937             return false;
938         }
939         for (int m : modeList) {
940             if (mode == m) {
941                 return true;
942             }
943         }
944         return false;
945     }
946 
assertAfMode(Camera2ImplConfig config, int afMode)947     private void assertAfMode(Camera2ImplConfig config, int afMode) {
948         if (isAfModeSupported(afMode)) {
949             assertThat(config.getCaptureRequestOption(
950                     CaptureRequest.CONTROL_AF_MODE, null)).isEqualTo(afMode);
951         } else {
952             int fallbackMode;
953             if (isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
954                 fallbackMode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
955             } else if (isAfModeSupported(CONTROL_AF_MODE_AUTO)) {
956                 fallbackMode = CONTROL_AF_MODE_AUTO;
957             } else {
958                 fallbackMode = CONTROL_AF_MODE_OFF;
959             }
960 
961             assertThat(config.getCaptureRequestOption(
962                     CaptureRequest.CONTROL_AF_MODE, null)).isEqualTo(fallbackMode);
963         }
964     }
965 
assertAeMode(Camera2ImplConfig config, int aeMode)966     private void assertAeMode(Camera2ImplConfig config, int aeMode) {
967         AutoFlashAEModeDisabler aeModeCorrector = new AutoFlashAEModeDisabler(mCameraQuirks);
968         int aeModeCorrected = aeModeCorrector.getCorrectedAeMode(aeMode);
969 
970         if (isAeModeSupported(aeModeCorrected)) {
971             assertThat(config.getCaptureRequestOption(
972                     CaptureRequest.CONTROL_AE_MODE, null)).isEqualTo(aeModeCorrected);
973         } else {
974             int fallbackMode;
975             if (isAeModeSupported(CONTROL_AE_MODE_ON)) {
976                 fallbackMode = CONTROL_AE_MODE_ON;
977             } else {
978                 fallbackMode = CONTROL_AE_MODE_OFF;
979             }
980 
981             assertThat(config.getCaptureRequestOption(
982                     CaptureRequest.CONTROL_AE_MODE, null)).isEqualTo(fallbackMode);
983         }
984     }
985 
assertAwbMode(Camera2ImplConfig config, int awbMode)986     private void assertAwbMode(Camera2ImplConfig config, int awbMode) {
987         if (isAwbModeSupported(awbMode)) {
988             assertThat(config.getCaptureRequestOption(
989                     CaptureRequest.CONTROL_AWB_MODE, null)).isEqualTo(awbMode);
990         } else {
991             int fallbackMode;
992             if (isAwbModeSupported(CONTROL_AWB_MODE_AUTO)) {
993                 fallbackMode = CONTROL_AWB_MODE_AUTO;
994             } else {
995                 fallbackMode = CONTROL_AWB_MODE_OFF;
996             }
997 
998             assertThat(config.getCaptureRequestOption(
999                     CaptureRequest.CONTROL_AWB_MODE, null)).isEqualTo(fallbackMode);
1000         }
1001     }
1002 
isZoomSupported()1003     private boolean isZoomSupported() {
1004         return mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)
1005                 > 1.0f;
1006     }
1007 
getSensorRect()1008     private Rect getSensorRect() {
1009         Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
1010         // Some device like pixel 2 will have (0, 8) as the left-top corner.
1011         return new Rect(0, 0, rect.width(), rect.height());
1012     }
1013 
1014     // Here we just test if setZoomRatio / setLinearZoom is working. For thorough tests, we
1015     // do it on ZoomControlTest and ZoomControlRoboTest.
1016     @Test
setZoomRatio_CropRegionIsUpdatedCorrectly()1017     public void setZoomRatio_CropRegionIsUpdatedCorrectly() throws InterruptedException {
1018         assumeTrue(isZoomSupported());
1019         assumeFalse(isAndroidRZoomEnabled());
1020         mCamera2CameraControlImpl.setZoomRatio(2.0f);
1021 
1022         HandlerUtil.waitForLooperToIdle(mHandler);
1023 
1024         Rect sessionCropRegion = getSessionCropRegion(mControlUpdateCallback);
1025 
1026         Rect sensorRect = getSensorRect();
1027         int cropX = (sensorRect.width() / 4);
1028         int cropY = (sensorRect.height() / 4);
1029         Rect cropRect = new Rect(cropX, cropY, cropX + sensorRect.width() / 2,
1030                 cropY + sensorRect.height() / 2);
1031         assertThat(sessionCropRegion).isEqualTo(cropRect);
1032     }
1033 
1034     @Test
1035     @SdkSuppress(minSdkVersion = 30)
setZoomRatio_androidRZoomRatioIsUpdatedCorrectly()1036     public void setZoomRatio_androidRZoomRatioIsUpdatedCorrectly() throws InterruptedException {
1037         assumeTrue(isAndroidRZoomEnabled());
1038         mCamera2CameraControlImpl.setZoomRatio(2.0f);
1039 
1040         HandlerUtil.waitForLooperToIdle(mHandler);
1041         float sessionZoomRatio = getSessionZoomRatio(mControlUpdateCallback);
1042 
1043         assertThat(sessionZoomRatio).isEqualTo(2.0f);
1044     }
1045 
getSessionCropRegion( CameraControlInternal.ControlUpdateCallback controlUpdateCallback)1046     private @NonNull Rect getSessionCropRegion(
1047             CameraControlInternal.ControlUpdateCallback controlUpdateCallback)
1048             throws InterruptedException {
1049         verify(controlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
1050         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
1051         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
1052                 sessionConfig.getImplementationOptions());
1053 
1054         reset(controlUpdateCallback);
1055         return camera2Config.getCaptureRequestOption(
1056                 CaptureRequest.SCALER_CROP_REGION, null);
1057     }
1058 
1059     @RequiresApi(30)
getSessionZoomRatio( CameraControlInternal.ControlUpdateCallback controlUpdateCallback)1060     private @NonNull Float getSessionZoomRatio(
1061             CameraControlInternal.ControlUpdateCallback controlUpdateCallback)
1062             throws InterruptedException {
1063         verify(controlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig();
1064         SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig();
1065         Camera2ImplConfig camera2Config = new Camera2ImplConfig(
1066                 sessionConfig.getImplementationOptions());
1067 
1068         reset(controlUpdateCallback);
1069         return camera2Config.getCaptureRequestOption(
1070                 CaptureRequest.CONTROL_ZOOM_RATIO, null);
1071     }
1072 
1073     @Test
setLinearZoom_CropRegionIsUpdatedCorrectly()1074     public void setLinearZoom_CropRegionIsUpdatedCorrectly() throws InterruptedException {
1075         assumeTrue(isZoomSupported());
1076         assumeFalse(isAndroidRZoomEnabled());
1077         mCamera2CameraControlImpl.setLinearZoom(1.0f);
1078         HandlerUtil.waitForLooperToIdle(mHandler);
1079 
1080         Rect cropRegionMaxZoom = getSessionCropRegion(mControlUpdateCallback);
1081         Rect cropRegionMinZoom = getSensorRect();
1082 
1083         mCamera2CameraControlImpl.setLinearZoom(0.5f);
1084 
1085         HandlerUtil.waitForLooperToIdle(mHandler);
1086 
1087         Rect cropRegionHalfZoom = getSessionCropRegion(mControlUpdateCallback);
1088 
1089         Assert.assertEquals(cropRegionHalfZoom.width(),
1090                 (cropRegionMinZoom.width() + cropRegionMaxZoom.width()) / 2.0f, 1
1091                 /* 1 pixel tolerance */);
1092     }
1093 
1094     @Test
1095     @SdkSuppress(minSdkVersion = 30)
setLinearZoom_androidRZoomRatioUpdatedCorrectly()1096     public void setLinearZoom_androidRZoomRatioUpdatedCorrectly() throws InterruptedException {
1097         assumeTrue(isAndroidRZoomEnabled());
1098         final float cropWidth = 10000f;
1099 
1100         mCamera2CameraControlImpl.setLinearZoom(1.0f);
1101         HandlerUtil.waitForLooperToIdle(mHandler);
1102         float sessionZoomRatioForLinearMax = getSessionZoomRatio(mControlUpdateCallback);
1103         float cropWidthForLinearMax = cropWidth / sessionZoomRatioForLinearMax;
1104 
1105         mCamera2CameraControlImpl.setLinearZoom(0f);
1106         HandlerUtil.waitForLooperToIdle(mHandler);
1107         float sessionZoomRatioForLinearMin = getSessionZoomRatio(mControlUpdateCallback);
1108         float cropWidthForLinearMin = cropWidth / sessionZoomRatioForLinearMin;
1109 
1110         mCamera2CameraControlImpl.setLinearZoom(0.5f);
1111 
1112         HandlerUtil.waitForLooperToIdle(mHandler);
1113 
1114         float sessionZoomRatioForLinearHalf = getSessionZoomRatio(mControlUpdateCallback);
1115         float cropWidthForLinearHalf = cropWidth / sessionZoomRatioForLinearHalf;
1116 
1117         Assert.assertEquals(cropWidthForLinearHalf,
1118                 (cropWidthForLinearMin + cropWidthForLinearMax) / 2.0f, 1
1119                 /* 1 pixel tolerance */);
1120     }
1121 
1122     @Test
setZoomRatio_cameraControlInactive_operationCanceled()1123     public void setZoomRatio_cameraControlInactive_operationCanceled() {
1124         mCamera2CameraControlImpl.setActive(false);
1125         ListenableFuture<Void> listenableFuture = mCamera2CameraControlImpl.setZoomRatio(1.0f);
1126         try {
1127             listenableFuture.get(1000, TimeUnit.MILLISECONDS);
1128         } catch (ExecutionException e) {
1129             if (e.getCause() instanceof CameraControl.OperationCanceledException) {
1130                 assertTrue(true);
1131                 return;
1132             }
1133         } catch (Exception e) {
1134         }
1135 
1136         fail();
1137     }
1138 
1139     @Test
setLinearZoom_cameraControlInactive_operationCanceled()1140     public void setLinearZoom_cameraControlInactive_operationCanceled() {
1141         mCamera2CameraControlImpl.setActive(false);
1142         ListenableFuture<Void> listenableFuture = mCamera2CameraControlImpl.setLinearZoom(0.0f);
1143         try {
1144             listenableFuture.get(1000, TimeUnit.MILLISECONDS);
1145         } catch (ExecutionException e) {
1146             if (e.getCause() instanceof CameraControl.OperationCanceledException) {
1147                 assertTrue(true);
1148                 return;
1149             }
1150         } catch (Exception e) {
1151         }
1152 
1153         fail();
1154     }
1155 
1156 
1157     @Test
1158     @LargeTest
addSessionCameraCaptureCallback_canAddWithoutUpdateSessionConfig()1159     public void addSessionCameraCaptureCallback_canAddWithoutUpdateSessionConfig()
1160             throws Exception {
1161         Camera2CameraControlImpl camera2CameraControlImpl =
1162                 createCamera2CameraControlWithPhysicalCamera();
1163         camera2CameraControlImpl.updateSessionConfig();
1164         HandlerUtil.waitForLooperToIdle(mHandler);
1165 
1166         TestCameraCaptureCallback callback1 = new TestCameraCaptureCallback();
1167         TestCameraCaptureCallback callback2 = new TestCameraCaptureCallback();
1168         camera2CameraControlImpl.addSessionCameraCaptureCallback(CameraXExecutors.directExecutor(),
1169                 callback1);
1170         camera2CameraControlImpl.addSessionCameraCaptureCallback(CameraXExecutors.directExecutor(),
1171                 callback2);
1172 
1173         callback1.assertCallbackIsCalled(5000);
1174         callback2.assertCallbackIsCalled(5000);
1175     }
1176 
1177     @Test
1178     @LargeTest
removeSessionCameraCaptureCallback()1179     public void removeSessionCameraCaptureCallback() throws Exception {
1180         Camera2CameraControlImpl camera2CameraControlImpl =
1181                 createCamera2CameraControlWithPhysicalCamera();
1182 
1183         camera2CameraControlImpl.updateSessionConfig();
1184         HandlerUtil.waitForLooperToIdle(mHandler);
1185 
1186         TestCameraCaptureCallback callback1 = new TestCameraCaptureCallback();
1187 
1188         camera2CameraControlImpl.addSessionCameraCaptureCallback(CameraXExecutors.directExecutor(),
1189                 callback1);
1190         callback1.assertCallbackIsCalled(5000);
1191 
1192         camera2CameraControlImpl.removeSessionCameraCaptureCallback(callback1);
1193         HandlerUtil.waitForLooperToIdle(mHandler);
1194 
1195         callback1.assertCallbackIsNotCalled(200);
1196     }
1197 
1198     @Test
1199     @LargeTest
sessionCameraCaptureCallback_invokedOnSpecifiedExecutor()1200     public void sessionCameraCaptureCallback_invokedOnSpecifiedExecutor()
1201             throws Exception {
1202         Camera2CameraControlImpl camera2CameraControlImpl =
1203                 createCamera2CameraControlWithPhysicalCamera();
1204         camera2CameraControlImpl.updateSessionConfig();
1205         HandlerUtil.waitForLooperToIdle(mHandler);
1206 
1207         TestCameraCaptureCallback callback = new TestCameraCaptureCallback();
1208         TestExecutor executor = new TestExecutor();
1209 
1210         camera2CameraControlImpl.addSessionCameraCaptureCallback(executor, callback);
1211 
1212         callback.assertCallbackIsCalled(5000);
1213         executor.assertExecutorIsCalled(5000);
1214     }
1215 
1216     @Test
canUseVideoUsage()1217     public void canUseVideoUsage() {
1218         // No recording initially.
1219         verifyIfInVideoUsage(false);
1220 
1221         // Case 1: Single video usage.
1222         mCamera2CameraControlImpl.incrementVideoUsage();
1223         verifyIfInVideoUsage(true);
1224 
1225         mCamera2CameraControlImpl.decrementVideoUsage();
1226         verifyIfInVideoUsage(false);
1227 
1228         // Case 2: Multiple video usages.
1229         mCamera2CameraControlImpl.incrementVideoUsage();
1230         mCamera2CameraControlImpl.incrementVideoUsage();
1231         verifyIfInVideoUsage(true);
1232 
1233         mCamera2CameraControlImpl.decrementVideoUsage();
1234         // There should still be a video usage remaining two were set as true before
1235         verifyIfInVideoUsage(true);
1236 
1237         mCamera2CameraControlImpl.decrementVideoUsage();
1238         verifyIfInVideoUsage(false);
1239 
1240         // Case 3: video usage clearing when inactive.
1241         mCamera2CameraControlImpl.incrementVideoUsage();
1242 
1243         mExecutorService.execute(() -> mCamera2CameraControlImpl.setActive(false));
1244         verifyIfInVideoUsage(false);
1245     }
1246 
verifyIfInVideoUsage(boolean expected)1247     private void verifyIfInVideoUsage(boolean expected) {
1248         try {
1249             HandlerUtil.waitForLooperToIdle(mHandler);
1250         } catch (InterruptedException e) {
1251             throw new AssertionError("Waiting for background thread idle failed!", e);
1252         }
1253 
1254         BooleanSubject assertSubject = assertThat(mCamera2CameraControlImpl.isInVideoUsage());
1255 
1256         if (expected) {
1257             assertSubject.isTrue();
1258         } else {
1259             assertSubject.isFalse();
1260         }
1261     }
1262 
1263     private static class TestCameraCaptureCallback extends CameraCaptureCallback {
1264         private CountDownLatch mLatchForOnCaptureCompleted;
1265 
1266         @Override
onCaptureCompleted(int captureConfigId, @NonNull CameraCaptureResult cameraCaptureResult)1267         public void onCaptureCompleted(int captureConfigId,
1268                 @NonNull CameraCaptureResult cameraCaptureResult) {
1269             synchronized (this) {
1270                 if (mLatchForOnCaptureCompleted != null) {
1271                     mLatchForOnCaptureCompleted.countDown();
1272                 }
1273             }
1274         }
1275 
assertCallbackIsCalled(long timeoutInMs)1276         public void assertCallbackIsCalled(long timeoutInMs) throws InterruptedException {
1277             CountDownLatch latch;
1278             synchronized (this) {
1279                 mLatchForOnCaptureCompleted = new CountDownLatch(1);
1280                 latch = mLatchForOnCaptureCompleted;
1281             }
1282 
1283             assertThat(latch.await(timeoutInMs, TimeUnit.MILLISECONDS))
1284                     .isTrue();
1285         }
1286 
assertCallbackIsNotCalled(long timeoutInMs)1287         public void assertCallbackIsNotCalled(long timeoutInMs) throws InterruptedException {
1288             CountDownLatch latch;
1289             synchronized (this) {
1290                 mLatchForOnCaptureCompleted = new CountDownLatch(1);
1291                 latch = mLatchForOnCaptureCompleted;
1292             }
1293             assertThat(latch.await(timeoutInMs, TimeUnit.MILLISECONDS))
1294                     .isFalse();
1295         }
1296     }
1297 
1298     private static class TestExecutor implements Executor {
1299         private CountDownLatch mLatch = new CountDownLatch(1);
1300 
1301         @Override
execute(@onNull Runnable command)1302         public void execute(@NonNull Runnable command) {
1303             command.run();
1304             mLatch.countDown();
1305         }
1306 
assertExecutorIsCalled(long timeoutInMS)1307         public void assertExecutorIsCalled(long timeoutInMS) throws InterruptedException {
1308             assertThat(mLatch.await(timeoutInMS, TimeUnit.MILLISECONDS)).isTrue();
1309         }
1310     }
1311 }
1312