| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.camera.camera2.internal; |
| |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_OFF; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_EXTERNAL_FLASH; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_AUTO; |
| import static android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_OFF; |
| import static android.hardware.camera2.CameraMetadata.FLASH_MODE_OFF; |
| import static android.hardware.camera2.CameraMetadata.FLASH_MODE_TORCH; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeFalse; |
| import static org.junit.Assume.assumeThat; |
| import static org.junit.Assume.assumeTrue; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| |
| import android.app.Instrumentation; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.hardware.camera2.CameraCharacteristics; |
| import android.hardware.camera2.CameraDevice; |
| import android.hardware.camera2.CaptureRequest; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.RequiresApi; |
| import androidx.camera.camera2.Camera2Config; |
| import androidx.camera.camera2.impl.Camera2ImplConfig; |
| import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat; |
| import androidx.camera.camera2.internal.compat.quirk.CameraQuirks; |
| import androidx.camera.camera2.internal.compat.workaround.AutoFlashAEModeDisabler; |
| import androidx.camera.camera2.internal.util.TestUtil; |
| import androidx.camera.core.CameraControl; |
| import androidx.camera.core.CameraSelector; |
| import androidx.camera.core.CameraXConfig; |
| import androidx.camera.core.FocusMeteringAction; |
| import androidx.camera.core.ImageAnalysis; |
| import androidx.camera.core.ImageCapture; |
| import androidx.camera.core.SurfaceOrientedMeteringPointFactory; |
| import androidx.camera.core.impl.CameraCaptureCallback; |
| import androidx.camera.core.impl.CameraCaptureResult; |
| import androidx.camera.core.impl.CameraControlInternal; |
| import androidx.camera.core.impl.CaptureConfig; |
| import androidx.camera.core.impl.Quirks; |
| import androidx.camera.core.impl.SessionConfig; |
| import androidx.camera.core.impl.utils.executor.CameraXExecutors; |
| import androidx.camera.core.internal.CameraUseCaseAdapter; |
| import androidx.camera.testing.impl.CameraUtil; |
| import androidx.camera.testing.impl.CameraXUtil; |
| import androidx.camera.testing.impl.HandlerUtil; |
| import androidx.concurrent.futures.CallbackToFutureAdapter; |
| import androidx.core.os.HandlerCompat; |
| import androidx.test.core.app.ApplicationProvider; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.LargeTest; |
| import androidx.test.filters.SdkSuppress; |
| import androidx.test.filters.SmallTest; |
| import androidx.test.platform.app.InstrumentationRegistry; |
| |
| import com.google.common.util.concurrent.ListenableFuture; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mockito; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| @SdkSuppress(minSdkVersion = 21) |
| public final class Camera2CameraControlImplDeviceTest { |
| @Rule |
| public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest( |
| new CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig()) |
| ); |
| |
| private Camera2CameraControlImpl mCamera2CameraControlImpl; |
| private CameraControlInternal.ControlUpdateCallback mControlUpdateCallback; |
| @SuppressWarnings("unchecked") |
| private ArgumentCaptor<List<CaptureConfig>> mCaptureConfigArgumentCaptor = |
| ArgumentCaptor.forClass(List.class); |
| private HandlerThread mHandlerThread; |
| private Handler mHandler; |
| private CameraCharacteristics mCameraCharacteristics; |
| private CameraCharacteristicsCompat mCameraCharacteristicsCompat; |
| private boolean mHasFlashUnit; |
| private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); |
| private CameraUseCaseAdapter mCamera; |
| private Quirks mCameraQuirks; |
| |
| @Before |
| public void setUp() throws InterruptedException { |
| mHandlerThread = new HandlerThread("ControlThread"); |
| mHandlerThread.start(); |
| mHandler = HandlerCompat.createAsync(mHandlerThread.getLooper()); |
| |
| Context context = ApplicationProvider.getApplicationContext(); |
| CameraXConfig config = Camera2Config.defaultConfig(); |
| CameraXUtil.initialize(context, config); |
| |
| setUp(CameraSelector.LENS_FACING_BACK); |
| } |
| |
| private void setUp(int lensFacing) throws InterruptedException { |
| assumeTrue(CameraUtil.hasCameraWithLensFacing(lensFacing)); |
| |
| mCameraCharacteristics = CameraUtil.getCameraCharacteristics(lensFacing); |
| Boolean hasFlashUnit = |
| mCameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); |
| mHasFlashUnit = hasFlashUnit != null && hasFlashUnit.booleanValue(); |
| |
| mControlUpdateCallback = mock(CameraControlInternal.ControlUpdateCallback.class); |
| |
| ScheduledExecutorService executorService = CameraXExecutors.newHandlerExecutor(mHandler); |
| String cameraId = CameraUtil.getCameraIdWithLensFacing(lensFacing); |
| mCameraCharacteristicsCompat = CameraCharacteristicsCompat.toCameraCharacteristicsCompat( |
| mCameraCharacteristics, cameraId); |
| mCamera2CameraControlImpl = new Camera2CameraControlImpl(mCameraCharacteristicsCompat, |
| executorService, executorService, mControlUpdateCallback); |
| mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristicsCompat); |
| |
| mCamera2CameraControlImpl.incrementUseCount(); |
| mCamera2CameraControlImpl.setActive(true); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| } |
| |
| @After |
| public void tearDown() throws InterruptedException, ExecutionException, TimeoutException { |
| if (mCamera != null) { |
| mInstrumentation.runOnMainSync(() -> |
| //TODO: The removeUseCases() call might be removed after clarifying the |
| // abortCaptures() issue in b/162314023. |
| mCamera.removeUseCases(mCamera.getUseCases()) |
| ); |
| } |
| |
| CameraXUtil.shutdown().get(10000, TimeUnit.MILLISECONDS); |
| if (mHandlerThread != null) { |
| mHandlerThread.quitSafely(); |
| } |
| } |
| |
| private boolean isAndroidRZoomEnabled() { |
| return ZoomControl.isAndroidRZoomSupported(mCameraCharacteristicsCompat); |
| } |
| |
| private int getMaxAfRegionCount() { |
| return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); |
| } |
| |
| private int getMaxAeRegionCount() { |
| return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); |
| } |
| |
| private int getMaxAwbRegionCount() { |
| return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB); |
| } |
| |
| private boolean isAeSupported() { |
| int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES); |
| for (int mode : modes) { |
| if (mode == CONTROL_AE_MODE_ON) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Test |
| public void canIncrementDecrementUseCount() { |
| // incrementUseCount() in setup() |
| assertThat(mCamera2CameraControlImpl.getUseCount()).isEqualTo(1); |
| |
| mCamera2CameraControlImpl.decrementUseCount(); |
| |
| assertThat(mCamera2CameraControlImpl.getUseCount()).isEqualTo(0); |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| public void decrementUseCountLessThanZero_getException() { |
| // incrementUseCount() in setup() |
| assertThat(mCamera2CameraControlImpl.getUseCount()).isEqualTo(1); |
| |
| mCamera2CameraControlImpl.decrementUseCount(); |
| mCamera2CameraControlImpl.decrementUseCount(); |
| } |
| |
| @Test |
| public void setTemplate_updateCameraControlSessionConfig() { |
| mCamera2CameraControlImpl.setTemplate(CameraDevice.TEMPLATE_RECORD); |
| |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| assertThat(sessionConfig.getTemplateType()).isEqualTo(CameraDevice.TEMPLATE_RECORD); |
| } |
| |
| @Test |
| public void setTemplatePreview_afModeToContinuousPicture() { |
| mCamera2CameraControlImpl.setTemplate(CameraDevice.TEMPLATE_PREVIEW); |
| |
| Camera2ImplConfig camera2Config = |
| new Camera2ImplConfig(mCamera2CameraControlImpl.getSessionOptions()); |
| assertAfMode(camera2Config, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
| } |
| |
| @Test |
| public void setTemplateRecord_afModeToContinuousVideo() { |
| mCamera2CameraControlImpl.setTemplate(CameraDevice.TEMPLATE_RECORD); |
| |
| Camera2ImplConfig camera2Config = |
| new Camera2ImplConfig(mCamera2CameraControlImpl.getSessionOptions()); |
| assertAfMode(camera2Config, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); |
| } |
| |
| @Test |
| public void defaultAFAWBMode_ShouldBeCAFWhenNotFocusLocked() { |
| Camera2ImplConfig singleConfig = new Camera2ImplConfig( |
| mCamera2CameraControlImpl.getSessionOptions()); |
| assertThat( |
| singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF)) |
| .isEqualTo(CaptureRequest.CONTROL_MODE_AUTO); |
| |
| assertAfMode(singleConfig, CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
| assertAwbMode(singleConfig, CONTROL_AWB_MODE_AUTO); |
| } |
| |
| @Test |
| public void setFlashModeAuto_aeModeSetAndRequestUpdated() throws InterruptedException { |
| mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_AUTO); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertAeMode(camera2Config, CONTROL_AE_MODE_ON_AUTO_FLASH); |
| assertThat(mCamera2CameraControlImpl.getFlashMode()).isEqualTo( |
| ImageCapture.FLASH_MODE_AUTO); |
| // ZSL only support API >= 23. ZslControlImpl will be created for API >= 23, otherwise |
| // ZslControlNoOpImpl will be created, which always return false for this flag. |
| if (Build.VERSION.SDK_INT >= 23) { |
| assertThat( |
| mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isTrue(); |
| } else { |
| assertThat( |
| mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isFalse(); |
| } |
| } |
| |
| @Test |
| public void setFlashModeOff_aeModeSetAndRequestUpdated() throws InterruptedException { |
| mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_OFF); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertAeMode(camera2Config, CONTROL_AE_MODE_ON); |
| |
| assertThat(mCamera2CameraControlImpl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_OFF); |
| assertThat(mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isFalse(); |
| } |
| |
| @Test |
| public void setFlashModeOn_aeModeSetAndRequestUpdated() throws InterruptedException { |
| mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_ON); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertAeMode(camera2Config, CONTROL_AE_MODE_ON_ALWAYS_FLASH); |
| |
| assertThat(mCamera2CameraControlImpl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_ON); |
| // ZSL only support API >= 23. ZslControlImpl will be created for API >= 23, otherwise |
| // ZslControlNoOpImpl will be created, which always return false for this flag. |
| if (Build.VERSION.SDK_INT >= 23) { |
| assertThat( |
| mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isTrue(); |
| } else { |
| assertThat( |
| mCamera2CameraControlImpl.getZslControl().isZslDisabledByFlashMode()).isFalse(); |
| } |
| } |
| |
| @Test |
| @SdkSuppress(minSdkVersion = 28) |
| public void enableExternalFlashAeMode_aeModeSetAndRequestUpdated() throws InterruptedException { |
| setUp(CameraSelector.LENS_FACING_FRONT); |
| |
| assumeThat("CONTROL_AE_MODE_ON_EXTERNAL_FLASH not supported", |
| mCamera2CameraControlImpl.getSupportedAeMode(CONTROL_AE_MODE_ON_EXTERNAL_FLASH), |
| equalTo(CONTROL_AE_MODE_ON_EXTERNAL_FLASH)); |
| |
| mCamera2CameraControlImpl.getFocusMeteringControl().enableExternalFlashAeMode(true); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertAeMode(camera2Config, CONTROL_AE_MODE_ON_EXTERNAL_FLASH); |
| } |
| |
| @Test |
| @SdkSuppress(minSdkVersion = 28) |
| public void disableExternalFlashAeMode_aeModeUnsetAndRequestUpdated() |
| throws InterruptedException { |
| setUp(CameraSelector.LENS_FACING_FRONT); |
| |
| assumeThat("CONTROL_AE_MODE_ON_EXTERNAL_FLASH not supported", |
| mCamera2CameraControlImpl.getSupportedAeMode(CONTROL_AE_MODE_ON_EXTERNAL_FLASH), |
| equalTo(CONTROL_AE_MODE_ON_EXTERNAL_FLASH)); |
| |
| mCamera2CameraControlImpl.getFocusMeteringControl().enableExternalFlashAeMode(true); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| mCamera2CameraControlImpl.getFocusMeteringControl().enableExternalFlashAeMode(false); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(2)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertThat(camera2Config.getCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, |
| null)).isNotEqualTo(CONTROL_AE_MODE_ON_EXTERNAL_FLASH); |
| } |
| |
| @Test |
| public void enableTorch_aeModeSetAndRequestUpdated() throws InterruptedException { |
| assumeTrue(mHasFlashUnit); |
| mCamera2CameraControlImpl.enableTorch(true); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertAeMode(camera2Config, CONTROL_AE_MODE_ON); |
| |
| assertThat( |
| camera2Config.getCaptureRequestOption( |
| CaptureRequest.FLASH_MODE, FLASH_MODE_OFF)) |
| .isEqualTo(FLASH_MODE_TORCH); |
| } |
| |
| @Test |
| public void disableTorchFlashModeAuto_aeModeSetAndRequestUpdated() throws InterruptedException { |
| assumeTrue(mHasFlashUnit); |
| mCamera2CameraControlImpl.setFlashMode(ImageCapture.FLASH_MODE_AUTO); |
| mCamera2CameraControlImpl.enableTorch(false); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(2)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertAeMode(camera2Config, CONTROL_AE_MODE_ON_AUTO_FLASH); |
| |
| assertThat(camera2Config.getCaptureRequestOption( |
| CaptureRequest.FLASH_MODE, -1)) |
| .isEqualTo(-1); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlCaptureRequests( |
| mCaptureConfigArgumentCaptor.capture()); |
| CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0); |
| Camera2ImplConfig resultCaptureConfig = |
| new Camera2ImplConfig(captureConfig.getImplementationOptions()); |
| |
| assertAeMode(resultCaptureConfig, CONTROL_AE_MODE_ON); |
| |
| } |
| |
| @Test |
| @LargeTest |
| public void triggerAf_futureSucceeds() throws Exception { |
| Camera2CameraControlImpl camera2CameraControlImpl = |
| createCamera2CameraControlWithPhysicalCamera(); |
| |
| ListenableFuture<CameraCaptureResult> future = CallbackToFutureAdapter.getFuture(c -> { |
| camera2CameraControlImpl.mExecutor.execute(() -> |
| camera2CameraControlImpl.getFocusMeteringControl().triggerAf( |
| c, /* overrideAeMode */ false)); |
| return "triggerAf"; |
| }); |
| |
| future.get(5, TimeUnit.SECONDS); |
| } |
| |
| @Test |
| public void captureMaxQuality_shouldSuccess() |
| throws ExecutionException, InterruptedException, TimeoutException { |
| captureTest(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, |
| ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH); |
| } |
| |
| @Test |
| public void captureMiniLatency_shouldSuccess() |
| throws ExecutionException, InterruptedException, TimeoutException { |
| captureTest(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, |
| ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH); |
| } |
| |
| @Test |
| public void captureMaxQuality_torchAsFlash_shouldSuccess() |
| throws ExecutionException, InterruptedException, TimeoutException { |
| captureTest(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, |
| ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH); |
| } |
| |
| @Test |
| public void captureMiniLatency_torchAsFlash_shouldSuccess() |
| throws ExecutionException, InterruptedException, TimeoutException { |
| captureTest(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY, |
| ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH); |
| } |
| |
| private void captureTest(int captureMode, int flashType) |
| throws ExecutionException, InterruptedException, TimeoutException { |
| ImageCapture imageCapture = new ImageCapture.Builder().build(); |
| |
| mCamera = CameraUtil.createCameraAndAttachUseCase( |
| ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA, |
| imageCapture); |
| |
| Camera2CameraControlImpl camera2CameraControlImpl = |
| TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl()); |
| |
| CameraCaptureCallback captureCallback = mock(CameraCaptureCallback.class); |
| CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder(); |
| captureConfigBuilder.setTemplateType(CameraDevice.TEMPLATE_STILL_CAPTURE); |
| captureConfigBuilder.addSurface(imageCapture.getSessionConfig().getSurfaces().get(0)); |
| captureConfigBuilder.addCameraCaptureCallback(captureCallback); |
| |
| ListenableFuture<List<Void>> future = camera2CameraControlImpl.submitStillCaptureRequests( |
| Arrays.asList(captureConfigBuilder.build()), captureMode, flashType); |
| |
| // The future should successfully complete |
| future.get(10, TimeUnit.SECONDS); |
| // CameraCaptureCallback.onCaptureCompleted() should be called to signal a capture attempt. |
| verify(captureCallback, timeout(3000).times(1)) |
| .onCaptureCompleted(any(CameraCaptureResult.class)); |
| } |
| |
| private Camera2CameraControlImpl createCamera2CameraControlWithPhysicalCamera() { |
| ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build(); |
| // Make ImageAnalysis active. |
| imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), (image) -> image.close()); |
| |
| mCamera = CameraUtil.createCameraAndAttachUseCase( |
| ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA, |
| imageAnalysis); |
| |
| return TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl()); |
| } |
| |
| private <T> void assertArraySize(T[] array, int expectedSize) { |
| if (expectedSize == 0) { |
| assertTrue(array == null || array.length == 0); |
| } else { |
| assertThat(array).hasLength(expectedSize); |
| } |
| } |
| |
| @Test |
| public void startFocusAndMetering_3ARegionsUpdatedInSessionAndSessionOptions() |
| throws InterruptedException { |
| assumeTrue(getMaxAfRegionCount() > 0 || getMaxAeRegionCount() > 0 |
| || getMaxAwbRegionCount() > 0); |
| |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0)) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig repeatingConfig = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| // Here we verify only 3A region count is correct. Values correctness are left to |
| // FocusMeteringControlTest. |
| int expectedAfCount = Math.min(getMaxAfRegionCount(), 1); |
| int expectedAeCount = Math.min(getMaxAeRegionCount(), 1); |
| int expectedAwbCount = Math.min(getMaxAwbRegionCount(), 1); |
| assertArraySize(repeatingConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_REGIONS, null), expectedAfCount); |
| assertArraySize(repeatingConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_REGIONS, null), expectedAeCount); |
| assertArraySize(repeatingConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AWB_REGIONS, null), expectedAwbCount); |
| |
| Camera2ImplConfig singleConfig = new Camera2ImplConfig( |
| mCamera2CameraControlImpl.getSessionOptions()); |
| assertArraySize(singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_REGIONS, null), expectedAfCount); |
| assertArraySize(singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_REGIONS, null), expectedAeCount); |
| assertArraySize(singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AWB_REGIONS, null), expectedAwbCount); |
| } |
| |
| @Test |
| public void startFocusAndMetering_AfIsTriggeredProperly() throws InterruptedException { |
| assumeTrue(getMaxAfRegionCount() > 0); |
| |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0)) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verifyAfMode(CaptureRequest.CONTROL_AF_MODE_AUTO); |
| |
| verify(mControlUpdateCallback).onCameraControlCaptureRequests( |
| mCaptureConfigArgumentCaptor.capture()); |
| |
| CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0); |
| Camera2ImplConfig resultCaptureConfig = |
| new Camera2ImplConfig(captureConfig.getImplementationOptions()); |
| |
| // Trigger AF |
| assertThat(resultCaptureConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_TRIGGER, null)) |
| .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_START); |
| |
| // Ensures AE_MODE is overridden to CONTROL_AE_MODE_ON to prevent from flash being fired. |
| if (isAeSupported()) { |
| assertThat(resultCaptureConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_MODE, null)) |
| .isEqualTo(CONTROL_AE_MODE_ON); |
| } |
| } |
| |
| @Test |
| public void startFocusAndMetering_AFNotInvolved_AfIsNotTriggered() throws InterruptedException { |
| assumeTrue(getMaxAeRegionCount() > 0 || getMaxAwbRegionCount() > 0); |
| |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0), |
| FocusMeteringAction.FLAG_AE | FocusMeteringAction.FLAG_AWB) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
| |
| verify(mControlUpdateCallback, never()).onCameraControlCaptureRequests(any()); |
| } |
| |
| @Test |
| public void cancelFocusAndMetering_3ARegionsReset() throws InterruptedException { |
| assumeTrue(getMaxAfRegionCount() > 0 || getMaxAeRegionCount() > 0 |
| || getMaxAwbRegionCount() > 0); |
| |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0)) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| Mockito.reset(mControlUpdateCallback); |
| |
| mCamera2CameraControlImpl.cancelFocusAndMetering(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig repeatingConfig = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| assertThat( |
| repeatingConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_REGIONS, null)).isNull(); |
| assertThat( |
| repeatingConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_REGIONS, null)).isNull(); |
| assertThat( |
| repeatingConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AWB_REGIONS, null)).isNull(); |
| |
| |
| Camera2ImplConfig singleConfig = new Camera2ImplConfig( |
| mCamera2CameraControlImpl.getSessionOptions()); |
| assertThat( |
| singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_REGIONS, null)).isNull(); |
| assertThat( |
| singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_REGIONS, null)).isNull(); |
| assertThat( |
| singleConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AWB_REGIONS, null)).isNull(); |
| } |
| |
| @Test |
| public void cancelFocusAndMetering_cancelAfProperly() throws InterruptedException { |
| assumeTrue(getMaxAfRegionCount() > 0); |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0)) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| Mockito.reset(mControlUpdateCallback); |
| mCamera2CameraControlImpl.cancelFocusAndMetering(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
| |
| verify(mControlUpdateCallback).onCameraControlCaptureRequests( |
| mCaptureConfigArgumentCaptor.capture()); |
| |
| CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0); |
| Camera2ImplConfig resultCaptureConfig = |
| new Camera2ImplConfig(captureConfig.getImplementationOptions()); |
| |
| // Trigger AF |
| assertThat(resultCaptureConfig.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_TRIGGER, null)) |
| .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); |
| } |
| |
| private void verifyAfMode(int expectAfMode) { |
| verify(mControlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig repeatingConfig = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| assertAfMode(repeatingConfig, expectAfMode); |
| } |
| |
| @Test |
| public void cancelFocusAndMetering_AFNotInvolved_notCancelAF() throws InterruptedException { |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0), |
| FocusMeteringAction.FLAG_AE) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| Mockito.reset(mControlUpdateCallback); |
| mCamera2CameraControlImpl.cancelFocusAndMetering(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| verify(mControlUpdateCallback, never()).onCameraControlCaptureRequests(any()); |
| |
| verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
| } |
| |
| @Test |
| public void startFocus_afModeIsSetToAuto() throws InterruptedException { |
| assumeTrue(getMaxAfRegionCount() > 0); |
| SurfaceOrientedMeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, |
| 1.0f); |
| FocusMeteringAction action = new FocusMeteringAction.Builder(factory.createPoint(0, 0)) |
| .build(); |
| mCamera2CameraControlImpl.startFocusAndMetering(action); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| Camera2ImplConfig singleConfig = new Camera2ImplConfig( |
| mCamera2CameraControlImpl.getSessionOptions()); |
| assertAfMode(singleConfig, CaptureRequest.CONTROL_AF_MODE_AUTO); |
| |
| mCamera2CameraControlImpl.cancelFocusAndMetering(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| Camera2ImplConfig singleConfig2 = new Camera2ImplConfig( |
| mCamera2CameraControlImpl.getSessionOptions()); |
| assertAfMode(singleConfig2, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
| } |
| |
| private boolean isAfModeSupported(int afMode) { |
| int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); |
| return isModeInList(afMode, modes); |
| } |
| |
| private boolean isAeModeSupported(int aeMode) { |
| int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES); |
| return isModeInList(aeMode, modes); |
| } |
| |
| private boolean isAwbModeSupported(int awbMode) { |
| int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES); |
| return isModeInList(awbMode, modes); |
| } |
| |
| |
| private boolean isModeInList(int mode, int[] modeList) { |
| if (modeList == null) { |
| return false; |
| } |
| for (int m : modeList) { |
| if (mode == m) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void assertAfMode(Camera2ImplConfig config, int afMode) { |
| if (isAfModeSupported(afMode)) { |
| assertThat(config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_MODE, null)).isEqualTo(afMode); |
| } else { |
| int fallbackMode; |
| if (isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE)) { |
| fallbackMode = CONTROL_AF_MODE_CONTINUOUS_PICTURE; |
| } else if (isAfModeSupported(CONTROL_AF_MODE_AUTO)) { |
| fallbackMode = CONTROL_AF_MODE_AUTO; |
| } else { |
| fallbackMode = CONTROL_AF_MODE_OFF; |
| } |
| |
| assertThat(config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AF_MODE, null)).isEqualTo(fallbackMode); |
| } |
| } |
| |
| private void assertAeMode(Camera2ImplConfig config, int aeMode) { |
| AutoFlashAEModeDisabler aeModeCorrector = new AutoFlashAEModeDisabler(mCameraQuirks); |
| int aeModeCorrected = aeModeCorrector.getCorrectedAeMode(aeMode); |
| |
| if (isAeModeSupported(aeModeCorrected)) { |
| assertThat(config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_MODE, null)).isEqualTo(aeModeCorrected); |
| } else { |
| int fallbackMode; |
| if (isAeModeSupported(CONTROL_AE_MODE_ON)) { |
| fallbackMode = CONTROL_AE_MODE_ON; |
| } else { |
| fallbackMode = CONTROL_AE_MODE_OFF; |
| } |
| |
| assertThat(config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AE_MODE, null)).isEqualTo(fallbackMode); |
| } |
| } |
| |
| private void assertAwbMode(Camera2ImplConfig config, int awbMode) { |
| if (isAwbModeSupported(awbMode)) { |
| assertThat(config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AWB_MODE, null)).isEqualTo(awbMode); |
| } else { |
| int fallbackMode; |
| if (isAwbModeSupported(CONTROL_AWB_MODE_AUTO)) { |
| fallbackMode = CONTROL_AWB_MODE_AUTO; |
| } else { |
| fallbackMode = CONTROL_AWB_MODE_OFF; |
| } |
| |
| assertThat(config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_AWB_MODE, null)).isEqualTo(fallbackMode); |
| } |
| } |
| |
| private boolean isZoomSupported() { |
| return mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) |
| > 1.0f; |
| } |
| |
| private Rect getSensorRect() { |
| Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); |
| // Some device like pixel 2 will have (0, 8) as the left-top corner. |
| return new Rect(0, 0, rect.width(), rect.height()); |
| } |
| |
| // Here we just test if setZoomRatio / setLinearZoom is working. For thorough tests, we |
| // do it on ZoomControlTest and ZoomControlRoboTest. |
| @Test |
| public void setZoomRatio_CropRegionIsUpdatedCorrectly() throws InterruptedException { |
| assumeTrue(isZoomSupported()); |
| assumeFalse(isAndroidRZoomEnabled()); |
| mCamera2CameraControlImpl.setZoomRatio(2.0f); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| Rect sessionCropRegion = getSessionCropRegion(mControlUpdateCallback); |
| |
| Rect sensorRect = getSensorRect(); |
| int cropX = (sensorRect.width() / 4); |
| int cropY = (sensorRect.height() / 4); |
| Rect cropRect = new Rect(cropX, cropY, cropX + sensorRect.width() / 2, |
| cropY + sensorRect.height() / 2); |
| assertThat(sessionCropRegion).isEqualTo(cropRect); |
| } |
| |
| @Test |
| @SdkSuppress(minSdkVersion = 30) |
| public void setZoomRatio_androidRZoomRatioIsUpdatedCorrectly() throws InterruptedException { |
| assumeTrue(isAndroidRZoomEnabled()); |
| mCamera2CameraControlImpl.setZoomRatio(2.0f); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| float sessionZoomRatio = getSessionZoomRatio(mControlUpdateCallback); |
| |
| assertThat(sessionZoomRatio).isEqualTo(2.0f); |
| } |
| |
| @NonNull |
| private Rect getSessionCropRegion( |
| CameraControlInternal.ControlUpdateCallback controlUpdateCallback) |
| throws InterruptedException { |
| verify(controlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| reset(controlUpdateCallback); |
| return camera2Config.getCaptureRequestOption( |
| CaptureRequest.SCALER_CROP_REGION, null); |
| } |
| |
| @NonNull |
| @RequiresApi(30) |
| private Float getSessionZoomRatio( |
| CameraControlInternal.ControlUpdateCallback controlUpdateCallback) |
| throws InterruptedException { |
| verify(controlUpdateCallback, times(1)).onCameraControlUpdateSessionConfig(); |
| SessionConfig sessionConfig = mCamera2CameraControlImpl.getSessionConfig(); |
| Camera2ImplConfig camera2Config = new Camera2ImplConfig( |
| sessionConfig.getImplementationOptions()); |
| |
| reset(controlUpdateCallback); |
| return camera2Config.getCaptureRequestOption( |
| CaptureRequest.CONTROL_ZOOM_RATIO, null); |
| } |
| |
| @Test |
| public void setLinearZoom_CropRegionIsUpdatedCorrectly() throws InterruptedException { |
| assumeTrue(isZoomSupported()); |
| assumeFalse(isAndroidRZoomEnabled()); |
| mCamera2CameraControlImpl.setLinearZoom(1.0f); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| Rect cropRegionMaxZoom = getSessionCropRegion(mControlUpdateCallback); |
| Rect cropRegionMinZoom = getSensorRect(); |
| |
| mCamera2CameraControlImpl.setLinearZoom(0.5f); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| Rect cropRegionHalfZoom = getSessionCropRegion(mControlUpdateCallback); |
| |
| Assert.assertEquals(cropRegionHalfZoom.width(), |
| (cropRegionMinZoom.width() + cropRegionMaxZoom.width()) / 2.0f, 1 |
| /* 1 pixel tolerance */); |
| } |
| |
| @Test |
| @SdkSuppress(minSdkVersion = 30) |
| public void setLinearZoom_androidRZoomRatioUpdatedCorrectly() throws InterruptedException { |
| assumeTrue(isAndroidRZoomEnabled()); |
| final float cropWidth = 10000f; |
| |
| mCamera2CameraControlImpl.setLinearZoom(1.0f); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| float sessionZoomRatioForLinearMax = getSessionZoomRatio(mControlUpdateCallback); |
| float cropWidthForLinearMax = cropWidth / sessionZoomRatioForLinearMax; |
| |
| mCamera2CameraControlImpl.setLinearZoom(0f); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| float sessionZoomRatioForLinearMin = getSessionZoomRatio(mControlUpdateCallback); |
| float cropWidthForLinearMin = cropWidth / sessionZoomRatioForLinearMin; |
| |
| mCamera2CameraControlImpl.setLinearZoom(0.5f); |
| |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| float sessionZoomRatioForLinearHalf = getSessionZoomRatio(mControlUpdateCallback); |
| float cropWidthForLinearHalf = cropWidth / sessionZoomRatioForLinearHalf; |
| |
| Assert.assertEquals(cropWidthForLinearHalf, |
| (cropWidthForLinearMin + cropWidthForLinearMax) / 2.0f, 1 |
| /* 1 pixel tolerance */); |
| } |
| |
| @Test |
| public void setZoomRatio_cameraControlInactive_operationCanceled() { |
| mCamera2CameraControlImpl.setActive(false); |
| ListenableFuture<Void> listenableFuture = mCamera2CameraControlImpl.setZoomRatio(1.0f); |
| try { |
| listenableFuture.get(1000, TimeUnit.MILLISECONDS); |
| } catch (ExecutionException e) { |
| if (e.getCause() instanceof CameraControl.OperationCanceledException) { |
| assertTrue(true); |
| return; |
| } |
| } catch (Exception e) { |
| } |
| |
| fail(); |
| } |
| |
| @Test |
| public void setLinearZoom_cameraControlInactive_operationCanceled() { |
| mCamera2CameraControlImpl.setActive(false); |
| ListenableFuture<Void> listenableFuture = mCamera2CameraControlImpl.setLinearZoom(0.0f); |
| try { |
| listenableFuture.get(1000, TimeUnit.MILLISECONDS); |
| } catch (ExecutionException e) { |
| if (e.getCause() instanceof CameraControl.OperationCanceledException) { |
| assertTrue(true); |
| return; |
| } |
| } catch (Exception e) { |
| } |
| |
| fail(); |
| } |
| |
| |
| @Test |
| @LargeTest |
| public void addSessionCameraCaptureCallback_canAddWithoutUpdateSessionConfig() |
| throws Exception { |
| Camera2CameraControlImpl camera2CameraControlImpl = |
| createCamera2CameraControlWithPhysicalCamera(); |
| camera2CameraControlImpl.updateSessionConfig(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| TestCameraCaptureCallback callback1 = new TestCameraCaptureCallback(); |
| TestCameraCaptureCallback callback2 = new TestCameraCaptureCallback(); |
| camera2CameraControlImpl.addSessionCameraCaptureCallback(CameraXExecutors.directExecutor(), |
| callback1); |
| camera2CameraControlImpl.addSessionCameraCaptureCallback(CameraXExecutors.directExecutor(), |
| callback2); |
| |
| callback1.assertCallbackIsCalled(5000); |
| callback2.assertCallbackIsCalled(5000); |
| } |
| |
| @Test |
| @LargeTest |
| public void removeSessionCameraCaptureCallback() throws Exception { |
| Camera2CameraControlImpl camera2CameraControlImpl = |
| createCamera2CameraControlWithPhysicalCamera(); |
| |
| camera2CameraControlImpl.updateSessionConfig(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| TestCameraCaptureCallback callback1 = new TestCameraCaptureCallback(); |
| |
| camera2CameraControlImpl.addSessionCameraCaptureCallback(CameraXExecutors.directExecutor(), |
| callback1); |
| callback1.assertCallbackIsCalled(5000); |
| |
| camera2CameraControlImpl.removeSessionCameraCaptureCallback(callback1); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| callback1.assertCallbackIsNotCalled(200); |
| } |
| |
| @Test |
| @LargeTest |
| public void sessionCameraCaptureCallback_invokedOnSpecifiedExecutor() |
| throws Exception { |
| Camera2CameraControlImpl camera2CameraControlImpl = |
| createCamera2CameraControlWithPhysicalCamera(); |
| camera2CameraControlImpl.updateSessionConfig(); |
| HandlerUtil.waitForLooperToIdle(mHandler); |
| |
| TestCameraCaptureCallback callback = new TestCameraCaptureCallback(); |
| TestExecutor executor = new TestExecutor(); |
| |
| camera2CameraControlImpl.addSessionCameraCaptureCallback(executor, callback); |
| |
| callback.assertCallbackIsCalled(5000); |
| executor.assertExecutorIsCalled(5000); |
| } |
| |
| private static class TestCameraCaptureCallback extends CameraCaptureCallback { |
| private CountDownLatch mLatchForOnCaptureCompleted; |
| |
| @Override |
| public void onCaptureCompleted(@NonNull CameraCaptureResult cameraCaptureResult) { |
| synchronized (this) { |
| if (mLatchForOnCaptureCompleted != null) { |
| mLatchForOnCaptureCompleted.countDown(); |
| } |
| } |
| } |
| |
| public void assertCallbackIsCalled(long timeoutInMs) throws InterruptedException { |
| CountDownLatch latch; |
| synchronized (this) { |
| mLatchForOnCaptureCompleted = new CountDownLatch(1); |
| latch = mLatchForOnCaptureCompleted; |
| } |
| |
| assertThat(latch.await(timeoutInMs, TimeUnit.MILLISECONDS)) |
| .isTrue(); |
| } |
| |
| public void assertCallbackIsNotCalled(long timeoutInMs) throws InterruptedException { |
| CountDownLatch latch; |
| synchronized (this) { |
| mLatchForOnCaptureCompleted = new CountDownLatch(1); |
| latch = mLatchForOnCaptureCompleted; |
| } |
| assertThat(latch.await(timeoutInMs, TimeUnit.MILLISECONDS)) |
| .isFalse(); |
| } |
| } |
| |
| private static class TestExecutor implements Executor { |
| private CountDownLatch mLatch = new CountDownLatch(1); |
| |
| @Override |
| public void execute(@NonNull Runnable command) { |
| command.run(); |
| mLatch.countDown(); |
| } |
| |
| public void assertExecutorIsCalled(long timeoutInMS) throws InterruptedException { |
| assertThat(mLatch.await(timeoutInMS, TimeUnit.MILLISECONDS)).isTrue(); |
| } |
| } |
| } |