blob: a61ada71980fb56f6aff2b84a925a79c37ef9f1f [file] [log] [blame]
/*
* Copyright 2023 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.testing.fakes;
import static androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Rect;
import android.os.Build;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureFailure;
import androidx.camera.core.impl.CameraCaptureResult;
import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.MutableOptionsBundle;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult;
import androidx.camera.testing.impl.mocks.MockScreenFlash;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.internal.DoNotInstrument;
import org.robolectric.shadows.ShadowLooper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@org.robolectric.annotation.Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public final class FakeCameraControlTest {
private FakeCameraControl mCameraControl;
@Before
public void setUp() {
mCameraControl = new FakeCameraControl();
}
@Test
public void notifiesAllRequestOnCaptureCancelled() {
CountDownLatch latch = new CountDownLatch(3);
CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
@Override
public void onCaptureCancelled(int captureConfigId) {
latch.countDown();
}
}, new CameraCaptureCallback() {
@Override
public void onCaptureCancelled(int captureConfigId) {
latch.countDown();
}
});
CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
@Override
public void onCaptureCancelled(int captureConfigId) {
latch.countDown();
}
});
mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
mCameraControl.notifyAllRequestsOnCaptureCancelled();
awaitLatch(latch);
}
@Test
public void notifiesAllRequestOnCaptureFailed() {
CountDownLatch latch = new CountDownLatch(3);
List<CameraCaptureFailure> failureList = new ArrayList<>();
CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
@Override
public void onCaptureFailed(int captureConfigId,
@NonNull CameraCaptureFailure failure) {
failureList.add(failure);
latch.countDown();
}
}, new CameraCaptureCallback() {
@Override
public void onCaptureFailed(int captureConfigId,
@NonNull CameraCaptureFailure failure) {
failureList.add(failure);
latch.countDown();
}
});
CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
@Override
public void onCaptureFailed(int captureConfigId,
@NonNull CameraCaptureFailure failure) {
failureList.add(failure);
latch.countDown();
}
});
mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
mCameraControl.notifyAllRequestsOnCaptureFailed();
awaitLatch(latch);
}
@Test
public void notifiesAllRequestOnCaptureCompleted() {
CameraCaptureResult captureResult = new FakeCameraCaptureResult();
CountDownLatch latch = new CountDownLatch(3);
List<CameraCaptureResult> resultList = new ArrayList<>();
CaptureConfig captureConfig1 = createCaptureConfig(new CameraCaptureCallback() {
@Override
public void onCaptureCompleted(int captureConfigId,
@NonNull CameraCaptureResult cameraCaptureResult) {
resultList.add(cameraCaptureResult);
latch.countDown();
}
}, new CameraCaptureCallback() {
@Override
public void onCaptureCompleted(int captureConfigId,
@NonNull CameraCaptureResult cameraCaptureResult) {
resultList.add(cameraCaptureResult);
latch.countDown();
}
});
CaptureConfig captureConfig2 = createCaptureConfig(new CameraCaptureCallback() {
@Override
public void onCaptureCompleted(int captureConfigId,
@NonNull CameraCaptureResult cameraCaptureResult) {
resultList.add(cameraCaptureResult);
latch.countDown();
}
});
mCameraControl.submitStillCaptureRequests(Arrays.asList(captureConfig1, captureConfig2),
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
mCameraControl.notifyAllRequestsOnCaptureCompleted(captureResult);
awaitLatch(latch);
assertThat(resultList).containsExactlyElementsIn(Arrays.asList(captureResult, captureResult,
captureResult));
}
@Test
public void canUpdateFlashModeToOff() {
mCameraControl.setFlashMode(ImageCapture.FLASH_MODE_OFF);
assertThat(mCameraControl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_OFF);
}
@Test
public void canUpdateFlashModeToOn() {
mCameraControl.setFlashMode(ImageCapture.FLASH_MODE_ON);
assertThat(mCameraControl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_ON);
}
@Test
public void canUpdateFlashModeToAuto() {
mCameraControl.setFlashMode(ImageCapture.FLASH_MODE_AUTO);
assertThat(mCameraControl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_AUTO);
}
@Test
public void canSetZslDisabledByUserCaseConfig() {
mCameraControl.setZslDisabledByUserCaseConfig(true);
assertThat(mCameraControl.isZslDisabledByByUserCaseConfig()).isEqualTo(true);
}
@Test
public void canUnsetZslDisabledByUserCaseConfig_afterSet() {
mCameraControl.setZslDisabledByUserCaseConfig(true);
mCameraControl.setZslDisabledByUserCaseConfig(false);
assertThat(mCameraControl.isZslDisabledByByUserCaseConfig()).isEqualTo(false);
}
@Test
public void canCheckIfZslConfigAdded() {
mCameraControl.addZslConfig(new SessionConfig.Builder());
assertThat(mCameraControl.isZslConfigAdded()).isEqualTo(true);
}
@Test
public void canEnableTorch() {
mCameraControl.enableTorch(true);
assertThat(mCameraControl.getTorchEnabled()).isEqualTo(true);
}
@Test
public void canDisableTorch_afterEnable() {
mCameraControl.enableTorch(true);
mCameraControl.enableTorch(false);
assertThat(mCameraControl.getTorchEnabled()).isEqualTo(false);
}
@Test
public void canSetScreenFlash() {
ImageCapture.ScreenFlash screenFlash = new MockScreenFlash();
mCameraControl.setScreenFlash(screenFlash);
assertThat(mCameraControl.getScreenFlash()).isEqualTo(screenFlash);
}
@Test
public void canClearScreenFlash_afterEnable() {
ImageCapture.ScreenFlash screenFlash = new MockScreenFlash();
mCameraControl.setScreenFlash(screenFlash);
mCameraControl.setScreenFlash(null);
assertThat(mCameraControl.getScreenFlash()).isEqualTo(null);
}
@Test
public void futureCompletesImmediately_whenTorchEnabled() {
ListenableFuture<?> future = mCameraControl.enableTorch(true);
assertThat(future.isDone()).isTrue();
}
@Test
public void canSetExposureIndex() {
mCameraControl.setExposureCompensationIndex(25);
assertThat(mCameraControl.getExposureCompensationIndex()).isEqualTo(25);
}
@Test
public void futureCompletesImmediately_whenExposureCompensationIndexSet() {
ListenableFuture<?> future = mCameraControl.setExposureCompensationIndex(45);
assertThat(future.isDone()).isTrue();
}
@Test
public void futureNotImmediatelyCompleted_whenStillCaptureRequestsSubmitted() {
ListenableFuture<?> future = submitStillCaptureRequests();
assertThat(future.isDone()).isFalse();
}
@Test
public void futureCompletes_whenStillCaptureRequestsSubmittedAndSuccessNotified() {
ListenableFuture<?> future = submitStillCaptureRequests();
mCameraControl.notifyAllRequestsOnCaptureCompleted(new FakeCameraCaptureResult());
try {
future.get(3, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
@Test
public void canGetSessionConfig() {
assertThat(mCameraControl.getSessionConfig()).isInstanceOf(SessionConfig.class);
}
@Test
public void providesSensorRectAccordingToMaxOutputSize() {
assertThat(mCameraControl.getSensorRect()).isEqualTo(
new Rect(0, 0, MAX_OUTPUT_SIZE.getWidth(), MAX_OUTPUT_SIZE.getHeight()));
}
@Test
public void canProvideLastFocusMeteringAction() {
FocusMeteringAction focusMeteringAction = new FocusMeteringAction.Builder(
new SurfaceOrientedMeteringPointFactory(1f, 1f).createPoint(0f, 0f)).build();
mCameraControl.startFocusAndMetering(focusMeteringAction);
assertThat(mCameraControl.getLastSubmittedFocusMeteringAction()).isEqualTo(
focusMeteringAction);
}
@Test
public void futureCompletesImmediately_forStartFocusAndMetering() {
ListenableFuture<?> future = mCameraControl.startFocusAndMetering(
new FocusMeteringAction.Builder(
new SurfaceOrientedMeteringPointFactory(1f, 1f).createPoint(0f,
0f)).build());
assertThat(future.isDone()).isTrue();
}
@Test
public void futureCompletesImmediately_forCancelFocusAndMetering() {
ListenableFuture<?> future = mCameraControl.cancelFocusAndMetering();
assertThat(future.isDone()).isTrue();
}
@Test
public void captureRequestListenerNotified_whenStillCaptureRequestSubmitted() {
List<CaptureConfig> notifiedCaptureConfigs = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
mCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
notifiedCaptureConfigs.addAll(captureConfigs);
latch.countDown();
});
CaptureConfig captureConfig = createCaptureConfig();
submitStillCaptureRequests(captureConfig);
awaitLatch(latch);
assertThat(notifiedCaptureConfigs).containsExactly(captureConfig);
}
@Test
public void captureRequestListenerNotifiedInCurrentThread_whenExecutorNotSet() {
AtomicReference<Thread> listenerThread = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
mCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
listenerThread.set(Thread.currentThread());
latch.countDown();
});
submitStillCaptureRequests();
awaitLatch(latch);
assertThat(listenerThread.get()).isEqualTo(Thread.currentThread());
}
@Test
public void captureRequestListenerNotifiedInMainThread_whenExecutorSetToMainThread() {
AtomicReference<Thread> listenerThread = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
mCameraControl.setOnNewCaptureRequestListener(CameraXExecutors.mainThreadExecutor(),
captureConfigs -> {
listenerThread.set(Thread.currentThread());
latch.countDown();
});
submitStillCaptureRequests();
ShadowLooper.runUiThreadTasks();
awaitLatch(latch);
assertThat(listenerThread.get()).isEqualTo(Looper.getMainLooper().getThread());
}
@Test
public void controlUpdateCallbackNotifiedInCurrentThread_whenExecutorNotSet() {
AtomicReference<Thread> callbackThread = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
mCameraControl = new FakeCameraControl(
new CameraControlInternal.ControlUpdateCallback() {
@Override
public void onCameraControlUpdateSessionConfig() {
}
@Override
public void onCameraControlCaptureRequests(
@NonNull List<CaptureConfig> captureConfigs) {
callbackThread.set(Thread.currentThread());
latch.countDown();
}
});
submitStillCaptureRequests();
awaitLatch(latch);
assertThat(callbackThread.get()).isEqualTo(Thread.currentThread());
}
@Test
public void controlUpdateCallbackNotifiedInMainThread_whenExecutorSetToMainThread() {
AtomicReference<Thread> callbackThread = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
mCameraControl = new FakeCameraControl(CameraXExecutors.mainThreadExecutor(),
new CameraControlInternal.ControlUpdateCallback() {
@Override
public void onCameraControlUpdateSessionConfig() {
}
@Override
public void onCameraControlCaptureRequests(
@NonNull List<CaptureConfig> captureConfigs) {
callbackThread.set(Thread.currentThread());
latch.countDown();
}
});
submitStillCaptureRequests();
ShadowLooper.runUiThreadTasks();
awaitLatch(latch);
assertThat(callbackThread.get()).isEqualTo(Looper.getMainLooper().getThread());
}
@Test
public void addInteropConfigOverridesSameOption() {
Config.Option<Integer> option1 = Config.Option.create("OPTION_ID_1", Integer.class);
MutableOptionsBundle config1 = MutableOptionsBundle.create();
config1.insertOption(option1, 1);
mCameraControl.addInteropConfig(config1);
MutableOptionsBundle config2 = MutableOptionsBundle.create();
config2.insertOption(option1, 2);
mCameraControl.addInteropConfig(config2);
Config finalConfig = mCameraControl.getInteropConfig();
assertThat(finalConfig.retrieveOption(option1)).isEqualTo(2);
}
@Test
public void addInteropConfigKeepsDifferentOptions() {
Config.Option<Integer> option1 = Config.Option.create("OPTION_ID_1", Integer.class);
Config.Option<Integer> option2 = Config.Option.create("OPTION_ID_2", Integer.class);
MutableOptionsBundle config1 = MutableOptionsBundle.create();
config1.insertOption(option1, 1);
mCameraControl.addInteropConfig(config1);
MutableOptionsBundle config2 = MutableOptionsBundle.create();
config2.insertOption(option2, 2);
mCameraControl.addInteropConfig(config2);
Config finalConfig = mCameraControl.getInteropConfig();
assertThat(finalConfig.retrieveOption(option1)).isEqualTo(1);
assertThat(finalConfig.retrieveOption(option2)).isEqualTo(2);
}
@Test
public void canClearInteropConfig() {
Config.Option<Integer> option1 = Config.Option.create("OPTION_ID_1", Integer.class);
MutableOptionsBundle config1 = MutableOptionsBundle.create();
config1.insertOption(option1, 1);
mCameraControl.addInteropConfig(config1);
mCameraControl.clearInteropConfig();
Config finalConfig = mCameraControl.getInteropConfig();
assertThat(finalConfig.containsOption(option1)).isFalse();
}
private void awaitLatch(CountDownLatch latch) {
try {
assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private ListenableFuture<List<Void>> submitStillCaptureRequests() {
return mCameraControl.submitStillCaptureRequests(
Collections.singletonList(createCaptureConfig()),
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
}
private ListenableFuture<List<Void>> submitStillCaptureRequests(CaptureConfig captureConfig) {
return mCameraControl.submitStillCaptureRequests(
Collections.singletonList(captureConfig),
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH);
}
private CaptureConfig createCaptureConfig(CameraCaptureCallback... cameraCaptureCallback) {
CaptureConfig.Builder builder = new CaptureConfig.Builder();
builder.addAllCameraCaptureCallbacks(Arrays.asList(cameraCaptureCallback));
return builder.build();
}
}