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