[go: nahoru, domu]

blob: 808b469275b0f5bb317e39a7b336c3559416330c [file] [log] [blame]
/*
* 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.extensions;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.ignoreStubs;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.util.Pair;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.camera.camera2.Camera2AppConfig;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.camera.extensions.ExtensionsManager.EffectMode;
import androidx.camera.extensions.impl.CaptureStageImpl;
import androidx.camera.extensions.impl.PreviewExtenderImpl;
import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
import androidx.camera.extensions.util.ExtensionsTestUtil;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.fakes.FakeLifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
public class PreviewExtenderTest {
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private FakeLifecycleOwner mFakeLifecycle;
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
@Before
public void setUp() throws InterruptedException, ExecutionException, TimeoutException {
assumeTrue(CameraUtil.deviceHasCamera());
assumeTrue(CameraUtil.hasCameraWithLensFacing(LensFacing.BACK));
Context context = ApplicationProvider.getApplicationContext();
CameraX.initialize(context, Camera2AppConfig.create(context));
mFakeLifecycle = new FakeLifecycleOwner();
mFakeLifecycle.startAndResume();
assumeTrue(ExtensionsTestUtil.initExtensions());
}
@After
public void cleanUp() throws ExecutionException, InterruptedException {
if (CameraX.isInitialized()) {
mInstrumentation.runOnMainSync(CameraX::unbindAll);
}
CameraX.shutdown().get();
}
@Test
@MediumTest
public void extenderLifeCycleTest_noMoreInvokeBeforeAndAfterInitDeInit() {
PreviewExtenderImpl mockPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
when(mockPreviewExtenderImpl.getProcessorType()).thenReturn(
PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR);
when(mockPreviewExtenderImpl.getProcessor()).thenReturn(
mock(PreviewImageProcessorImpl.class));
when(mockPreviewExtenderImpl.isExtensionAvailable(any(String.class),
any(CameraCharacteristics.class))).thenReturn(true);
PreviewConfig.Builder configBuilder = new PreviewConfig.Builder().setLensFacing(
LensFacing.BACK);
FakePreviewExtender fakePreviewExtender = new FakePreviewExtender(configBuilder,
mockPreviewExtenderImpl);
fakePreviewExtender.enableExtension();
Preview useCase = new Preview(configBuilder.build());
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mFakeLifecycle, useCase);
// To set the update listener and Preview will change to active state.
useCase.setOnPreviewOutputUpdateListener(
mock(Preview.OnPreviewOutputUpdateListener.class));
}
});
// To verify the call in order after bind to life cycle, and to verification of the
// getCaptureStages() is also used to wait for the capture session created. The test for
// the unbind would come after the capture session was created. Ignore any of the calls
// unrelated to the ExtenderStateListener.
verify(mockPreviewExtenderImpl, timeout(3000)).init(any(String.class),
any(CameraCharacteristics.class));
verify(mockPreviewExtenderImpl, timeout(3000)).getProcessorType();
verify(mockPreviewExtenderImpl, timeout(3000)).getProcessor();
// getSupportedResolutions supported since version 1.1
if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_1) >= 0) {
verify(mockPreviewExtenderImpl, timeout(3000)).getSupportedResolutions();
}
InOrder inOrder = inOrder(ignoreStubs(mockPreviewExtenderImpl));
inOrder.verify(mockPreviewExtenderImpl, timeout(3000)).onInit(any(String.class), any(
CameraCharacteristics.class), any(Context.class));
inOrder.verify(mockPreviewExtenderImpl, timeout(3000)).onPresetSession();
inOrder.verify(mockPreviewExtenderImpl, timeout(3000)).onEnableSession();
inOrder.verify(mockPreviewExtenderImpl, timeout(3000)).getCaptureStage();
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
// Unbind the use case to test the onDisableSession and onDeInit.
CameraX.unbind(useCase);
}
});
// To verify the onDisableSession and onDeInit.
inOrder.verify(mockPreviewExtenderImpl, timeout(3000)).onDisableSession();
inOrder.verify(mockPreviewExtenderImpl, timeout(3000)).onDeInit();
// To verify there is no any other calls on the mock.
verifyNoMoreInteractions(mockPreviewExtenderImpl);
}
@Test
@MediumTest
public void getCaptureStagesTest_shouldSetToRepeatingRequest() {
// Set up a result for getCaptureStages() testing.
CaptureStageImpl fakeCaptureStageImpl = new FakeCaptureStageImpl();
PreviewExtenderImpl mockPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
RequestUpdateProcessorImpl mockRequestUpdateProcessorImpl = mock(
RequestUpdateProcessorImpl.class);
// The mock an RequestUpdateProcessorImpl to capture the returned TotalCaptureResult
when(mockPreviewExtenderImpl.getProcessorType()).thenReturn(
PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY);
when(mockPreviewExtenderImpl.getProcessor()).thenReturn(mockRequestUpdateProcessorImpl);
when(mockPreviewExtenderImpl.isExtensionAvailable(any(String.class),
any(CameraCharacteristics.class))).thenReturn(true);
when(mockPreviewExtenderImpl.getCaptureStage()).thenReturn(fakeCaptureStageImpl);
PreviewConfig.Builder configBuilder = new PreviewConfig.Builder().setLensFacing(
LensFacing.BACK);
FakePreviewExtender fakePreviewExtender = new FakePreviewExtender(configBuilder,
mockPreviewExtenderImpl);
fakePreviewExtender.enableExtension();
Preview preview = new Preview(configBuilder.build());
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mFakeLifecycle, preview);
// To set the update listener and Preview will change to active state.
preview.setOnPreviewOutputUpdateListener(
mock(Preview.OnPreviewOutputUpdateListener.class));
}
});
ArgumentCaptor<TotalCaptureResult> captureResultArgumentCaptor = ArgumentCaptor.forClass(
TotalCaptureResult.class);
verify(mockRequestUpdateProcessorImpl, timeout(3000).atLeastOnce()).process(
captureResultArgumentCaptor.capture());
// TotalCaptureResult might be captured multiple times. Only care to get one instance of
// it, since they should all have the same value for the tested key
TotalCaptureResult totalCaptureResult = captureResultArgumentCaptor.getValue();
// To verify the capture result should include the parameter of the getCaptureStages().
List<Pair<CaptureRequest.Key, Object>> parameters = fakeCaptureStageImpl.getParameters();
for (Pair<CaptureRequest.Key, Object> parameter : parameters) {
assertThat(totalCaptureResult.getRequest().get(
(CaptureRequest.Key<Object>) parameter.first).equals(
parameter.second));
}
}
@Test
@MediumTest
public void processShouldBeInvoked_typeImageProcessor() {
// The type image processor will invoke PreviewImageProcessor.process()
PreviewImageProcessorImpl mockPreviewImageProcessorImpl = mock(
PreviewImageProcessorImpl.class);
PreviewExtenderImpl mockPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
when(mockPreviewExtenderImpl.getProcessor()).thenReturn(mockPreviewImageProcessorImpl);
when(mockPreviewExtenderImpl.getProcessorType()).thenReturn(
PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR);
when(mockPreviewExtenderImpl.isExtensionAvailable(any(String.class),
any(CameraCharacteristics.class))).thenReturn(true);
PreviewConfig.Builder configBuilder = new PreviewConfig.Builder().setLensFacing(
LensFacing.BACK);
FakePreviewExtender fakePreviewExtender = new FakePreviewExtender(configBuilder,
mockPreviewExtenderImpl);
fakePreviewExtender.enableExtension();
Preview preview = new Preview(configBuilder.build());
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mFakeLifecycle, preview);
// To set the update listener and Preview will change to active state.
preview.setOnPreviewOutputUpdateListener(
mock(Preview.OnPreviewOutputUpdateListener.class));
}
});
// To verify the process() method was invoked with non-null TotalCaptureResult input.
verify(mockPreviewImageProcessorImpl, timeout(3000).atLeastOnce()).process(any(Image.class),
any(TotalCaptureResult.class));
}
@Test
@MediumTest
public void canSetSupportedResolutionsToConfigTest() throws CameraInfoUnavailableException {
assumeTrue(CameraUtil.deviceHasCamera());
// getSupportedResolutions supported since version 1.1
assumeTrue(ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_1) >= 0);
LensFacing lensFacing = CameraX.getDefaultLensFacing();
PreviewConfig.Builder configBuilder = new PreviewConfig.Builder().setLensFacing(lensFacing);
PreviewExtenderImpl mockPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
when(mockPreviewExtenderImpl.isExtensionAvailable(any(), any())).thenReturn(true);
when(mockPreviewExtenderImpl.getProcessorType()).thenReturn(
PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_NONE);
List<Pair<Integer, Size[]>> targetFormatResolutionsPairList =
generatePreviewSupportedResolutions(lensFacing);
when(mockPreviewExtenderImpl.getSupportedResolutions()).thenReturn(
targetFormatResolutionsPairList);
PreviewExtender fakeExtender = new FakePreviewExtender(configBuilder,
mockPreviewExtenderImpl);
// Checks the config does not include supported resolutions before applying effect mode.
assertThat(configBuilder.build().getSupportedResolutions(null)).isNull();
// Checks the config includes supported resolutions after applying effect mode.
fakeExtender.enableExtension();
List<Pair<Integer, Size[]>> resultFormatResolutionsPairList =
configBuilder.build().getSupportedResolutions(null);
assertThat(resultFormatResolutionsPairList).isNotNull();
// Checks the result and target pair lists are the same
for (Pair<Integer, Size[]> resultPair : resultFormatResolutionsPairList) {
Size[] targetSizes = null;
for (Pair<Integer, Size[]> targetPair : targetFormatResolutionsPairList) {
if (targetPair.first.equals(resultPair.first)) {
targetSizes = targetPair.second;
break;
}
}
assertThat(
Arrays.asList(resultPair.second).equals(Arrays.asList(targetSizes))).isTrue();
}
}
private List<Pair<Integer, Size[]>> generatePreviewSupportedResolutions(
@NonNull LensFacing lensFacing)
throws CameraInfoUnavailableException {
List<Pair<Integer, Size[]>> formatResolutionsPairList = new ArrayList<>();
String cameraId =
androidx.camera.extensions.CameraUtil.getCameraIdSetWithLensFacing(
lensFacing).iterator().next();
StreamConfigurationMap map =
androidx.camera.extensions.CameraUtil.getCameraCharacteristics(cameraId).get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map != null) {
// Retrieves originally supported resolutions from CameraCharacteristics for PRIVATE
// format to return.
Size[] outputSizes = map.getOutputSizes(ImageFormat.PRIVATE);
if (outputSizes != null) {
formatResolutionsPairList.add(Pair.create(ImageFormat.PRIVATE, outputSizes));
}
}
return formatResolutionsPairList;
}
private class FakePreviewExtender extends PreviewExtender {
FakePreviewExtender(PreviewConfig.Builder builder, PreviewExtenderImpl impl) {
init(builder, impl, EffectMode.NORMAL);
}
}
private class FakeCaptureStageImpl implements CaptureStageImpl {
@Override
public int getId() {
return 0;
}
@Override
public List<Pair<CaptureRequest.Key, Object>> getParameters() {
List<Pair<CaptureRequest.Key, Object>> parameters = new ArrayList<>();
parameters.add(Pair.create(CaptureRequest.CONTROL_EFFECT_MODE,
CaptureRequest.CONTROL_EFFECT_MODE_SEPIA));
return parameters;
}
}
}