[go: nahoru, domu]

blob: 0a7309f654256a62f421fdf158efd5f203886ba3 [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.camera2.impl;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.Manifest;
import android.app.Instrumentation;
import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import androidx.camera.camera2.Camera2AppConfig;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.camera2.impl.util.SemaphoreReleasingCamera2Callbacks.DeviceStateCallback;
import androidx.camera.camera2.impl.util.SemaphoreReleasingCamera2Callbacks.SessionCaptureCallback;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageAnalysisConfig;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.ImageProxy;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.fakes.FakeLifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
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 java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
/**
* Contains tests for {@link androidx.camera.core.CameraX} which require an actual implementation to
* run.
*/
@FlakyTest
@LargeTest
@RunWith(AndroidJUnit4.class)
public final class Camera2ImplCameraXTest {
private static final LensFacing DEFAULT_LENS_FACING = LensFacing.BACK;
private final MutableLiveData<Long> mAnalysisResult = new MutableLiveData<>();
private final MutableLiveData<Long> mAnalysisResult2 = new MutableLiveData<>();
private final ImageAnalysis.Analyzer mImageAnalyzer =
new ImageAnalysis.Analyzer() {
@Override
public void analyze(ImageProxy image, int rotationDegrees) {
mAnalysisResult.postValue(image.getTimestamp());
}
};
private final ImageAnalysis.Analyzer mImageAnalyzer2 =
new ImageAnalysis.Analyzer() {
@Override
public void analyze(ImageProxy image, int rotationDegrees) {
mAnalysisResult2.postValue(image.getTimestamp());
}
};
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private CameraDevice.StateCallback mDeviceStateCallback;
private FakeLifecycleOwner mLifecycle;
private static Observer<Long> createCountIncrementingObserver(final AtomicLong counter) {
return new Observer<Long>() {
@Override
public void onChanged(Long value) {
counter.incrementAndGet();
}
};
}
@Before
public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
Context context = ApplicationProvider.getApplicationContext();
CameraX.init(context, Camera2AppConfig.create(context));
mLifecycle = new FakeLifecycleOwner();
mDeviceStateCallback = mock(CameraDevice.StateCallback.class);
}
@After
public void tearDown() throws InterruptedException, ExecutionException {
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbindAll();
}
});
// Wait for CameraX to deinit
CameraX.deinit().get();
}
@Test
public void lifecycleResume_opensCameraAndStreamsFrames() {
Observer<Long> mockObserver = mock(Observer.class);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysis useCase = new ImageAnalysis(builder.build());
CameraX.bindToLifecycle(mLifecycle, useCase);
useCase.setAnalyzer(mImageAnalyzer);
mAnalysisResult.observe(mLifecycle, mockObserver);
mLifecycle.startAndResume();
}
});
verify(mockObserver, timeout(5000).times(10)).onChanged(any());
}
@Test
public void removedUseCase_doesNotStreamWhenLifecycleResumes() throws NullPointerException,
CameraAccessException, CameraInfoUnavailableException {
// Legacy device would not support two ImageAnalysis use cases combination.
int hardwareLevelValue;
CameraCharacteristics cameraCharacteristics =
CameraUtil.getCameraManager().getCameraCharacteristics(
CameraX.getCameraWithLensFacing(DEFAULT_LENS_FACING));
hardwareLevelValue = cameraCharacteristics.get(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
assumeTrue(
hardwareLevelValue != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
Observer<Long> mockObserver = mock(Observer.class);
Observer<Long> mockObserver2 = mock(Observer.class);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysis useCase = new ImageAnalysis(builder.build());
ImageAnalysisConfig config2 =
new ImageAnalysisConfig.Builder()
.setLensFacing(DEFAULT_LENS_FACING)
.build();
ImageAnalysis useCase2 = new ImageAnalysis(config2);
CameraX.bindToLifecycle(mLifecycle, useCase, useCase2);
useCase.setAnalyzer(mImageAnalyzer);
useCase2.setAnalyzer(mImageAnalyzer2);
mAnalysisResult.observe(mLifecycle, mockObserver);
mAnalysisResult2.observe(mLifecycle, mockObserver2);
CameraX.unbind(useCase);
mLifecycle.startAndResume();
}
});
// Let second ImageAnalysis get some images. This shows that the first ImageAnalysis has
// not observed any images, even though the camera has started to stream.
verify(mockObserver2, timeout(3000).times(3)).onChanged(any());
verify(mockObserver, never()).onChanged(any());
}
@Test
public void lifecyclePause_closesCameraAndStopsStreamingFrames() throws InterruptedException {
final AtomicLong observedCount = new AtomicLong(0);
final SessionCaptureCallback sessionCaptureCallback = new SessionCaptureCallback();
final DeviceStateCallback deviceStateCallback = new DeviceStateCallback();
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
ImageAnalysisConfig.Builder configBuilder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(configBuilder)
.setDeviceStateCallback(deviceStateCallback)
.setSessionCaptureCallback(sessionCaptureCallback);
ImageAnalysis useCase = new ImageAnalysis(configBuilder.build());
CameraX.bindToLifecycle(mLifecycle, useCase);
useCase.setAnalyzer(mImageAnalyzer);
mAnalysisResult.observe(mLifecycle, createCountIncrementingObserver(observedCount));
mLifecycle.startAndResume();
}
});
// Wait a little bit for the camera to open and stream frames.
sessionCaptureCallback.waitForOnCaptureCompleted(5);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mLifecycle.pauseAndStop();
}
});
// Wait a little bit for the camera to close.
deviceStateCallback.waitForOnClosed(1);
final Long firstObservedCount = observedCount.get();
assertThat(firstObservedCount).isGreaterThan(1L);
// Stay in idle state for a while.
Thread.sleep(5000);
// Additional frames should not be observed.
final Long secondObservedCount = observedCount.get();
assertThat(secondObservedCount).isEqualTo(firstObservedCount);
}
@Test
public void bind_opensCamera() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
useCase.setAnalyzer(mImageAnalyzer);
mLifecycle.startAndResume();
}
});
verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
}
@Test
public void bind_opensCamera_withOutAnalyzer() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
}
});
verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
}
@Test
public void bind_opensCamera_noActiveUseCase_sessionIsConfigured() {
CameraCaptureSession.StateCallback mockSessionStateCallback = mock(
CameraCaptureSession.StateCallback.class);
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback)
.setSessionStateCallback(mockSessionStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
}
});
// When no analyzer is set, there will be no active surface for repeating request
// CaptureSession#mSessionConfig will be null. Thus we wait until capture session
// onConfigured to see if it causes any issue.
verify(mockSessionStateCallback, timeout(3000)).onConfigured(
any(CameraCaptureSession.class));
}
@Test
public void bind_unbind_loopWithOutAnalyzer() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
mLifecycle.startAndResume();
for (int i = 0; i < 2; i++) {
CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class);
new Camera2Config.Extender(builder).setDeviceStateCallback(callback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
}
});
verify(callback, timeout(5000)).onOpened(any(CameraDevice.class));
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbind(useCase);
}
});
verify(callback, timeout(3000)).onClosed(any(CameraDevice.class));
}
}
@Test
public void bind_unbind_loopWithAnalyzer() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
mLifecycle.startAndResume();
for (int i = 0; i < 2; i++) {
CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class);
new Camera2Config.Extender(builder).setDeviceStateCallback(callback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
useCase.setAnalyzer(mImageAnalyzer);
}
});
verify(callback, timeout(5000)).onOpened(any(CameraDevice.class));
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbind(useCase);
}
});
verify(callback, timeout(3000)).onClosed(any(CameraDevice.class));
}
}
@Test
public void unbindAll_closesAllCameras() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
}
});
verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbindAll();
}
});
verify(mDeviceStateCallback, timeout(3000)).onClosed(any(CameraDevice.class));
}
@Test
public void unbindAllAssociatedUseCase_closesCamera() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config = builder.build();
ImageAnalysis useCase = new ImageAnalysis(config);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase);
mLifecycle.startAndResume();
}
});
verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbind(useCase);
}
});
verify(mDeviceStateCallback, timeout(3000)).onClosed(any(CameraDevice.class));
}
@Test
public void unbindPartialAssociatedUseCase_doesNotCloseCamera() throws InterruptedException {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config0 = builder.build();
ImageAnalysis useCase0 = new ImageAnalysis(config0);
ImageCaptureConfig configuration =
new ImageCaptureConfig.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
.build();
ImageCapture useCase1 = new ImageCapture(configuration);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase0, useCase1);
mLifecycle.startAndResume();
}
});
verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbind(useCase1);
}
});
Thread.sleep(3000);
verify(mDeviceStateCallback, never()).onClosed(any(CameraDevice.class));
}
@Test
public void unbindAllAssociatedUseCaseInParts_ClosesCamera() {
ImageAnalysisConfig.Builder builder =
new ImageAnalysisConfig.Builder().setLensFacing(DEFAULT_LENS_FACING);
new Camera2Config.Extender(builder).setDeviceStateCallback(mDeviceStateCallback);
ImageAnalysisConfig config0 = builder.build();
ImageAnalysis useCase0 = new ImageAnalysis(config0);
ImageCaptureConfig configuration =
new ImageCaptureConfig.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
.build();
ImageCapture useCase1 = new ImageCapture(configuration);
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.bindToLifecycle(mLifecycle, useCase0, useCase1);
mLifecycle.startAndResume();
}
});
verify(mDeviceStateCallback, timeout(3000)).onOpened(any(CameraDevice.class));
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
CameraX.unbind(useCase0);
CameraX.unbind(useCase1);
}
});
verify(mDeviceStateCallback, timeout(3000).times(1)).onClosed(any(CameraDevice.class));
}
}