blob: 476f0166ca0da210bc8235b8054b5b68d7bcff73 [file] [log] [blame]
/*
* Copyright (C) 2013 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 com.android.mediaframeworktest.integration;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.SubmitInfo;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.view.Surface;
import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW;
import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
import org.mockito.ArgumentCaptor;
import org.mockito.compat.ArgumentMatcher;
import static org.mockito.Mockito.*;
public class CameraDeviceBinderTest extends AndroidTestCase {
private static String TAG = "CameraDeviceBinderTest";
// Number of streaming callbacks need to check.
private static int NUM_CALLBACKS_CHECKED = 10;
// Wait for capture result timeout value: 1500ms
private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500;
// Wait for flush timeout value: 1000ms
private final static int WAIT_FOR_FLUSH_TIMEOUT_MS = 1000;
// Wait for idle timeout value: 2000ms
private final static int WAIT_FOR_IDLE_TIMEOUT_MS = 2000;
// Wait while camera device starts working on requests
private final static int WAIT_FOR_WORK_MS = 300;
// Default size is VGA, which is mandatory camera supported image size by CDD.
private static final int DEFAULT_IMAGE_WIDTH = 640;
private static final int DEFAULT_IMAGE_HEIGHT = 480;
private static final int MAX_NUM_IMAGES = 5;
private String mCameraId;
private ICameraDeviceUser mCameraUser;
private CameraBinderTestUtils mUtils;
private ICameraDeviceCallbacks.Stub mMockCb;
private Surface mSurface;
private OutputConfiguration mOutputConfiguration;
private HandlerThread mHandlerThread;
private Handler mHandler;
ImageReader mImageReader;
public CameraDeviceBinderTest() {
}
private class ImageDropperListener implements ImageReader.OnImageAvailableListener {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
if (image != null) image.close();
}
}
public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
/*
* (non-Javadoc)
* @see
* android.hardware.camera2.ICameraDeviceCallbacks#onDeviceError(int,
* android.hardware.camera2.CaptureResultExtras)
*/
public void onDeviceError(int errorCode, CaptureResultExtras resultExtras)
throws RemoteException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onDeviceIdle()
*/
public void onDeviceIdle() throws RemoteException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see
* android.hardware.camera2.ICameraDeviceCallbacks#onCaptureStarted(
* android.hardware.camera2.CaptureResultExtras, long)
*/
public void onCaptureStarted(CaptureResultExtras resultExtras, long timestamp)
throws RemoteException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see
* android.hardware.camera2.ICameraDeviceCallbacks#onResultReceived(
* android.hardware.camera2.impl.CameraMetadataNative,
* android.hardware.camera2.CaptureResultExtras)
*/
public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
throws RemoteException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onPrepared()
*/
@Override
public void onPrepared(int streamId) throws RemoteException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onRequestQueueEmpty()
*/
@Override
public void onRequestQueueEmpty() throws RemoteException {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
*/
@Override
public void onRepeatingRequestError(long lastFrameNumber) {
// TODO Auto-generated method stub
}
}
class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> {
@Override
public boolean matchesObject(Object obj) {
return !((CameraMetadataNative) obj).isEmpty();
}
}
private void createDefaultSurface() {
mImageReader =
ImageReader.newInstance(DEFAULT_IMAGE_WIDTH,
DEFAULT_IMAGE_HEIGHT,
ImageFormat.YUV_420_888,
MAX_NUM_IMAGES);
mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
mSurface = mImageReader.getSurface();
mOutputConfiguration = new OutputConfiguration(mSurface);
}
private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception {
CameraMetadataNative metadata = null;
assertTrue(metadata.isEmpty());
metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
assertFalse(metadata.isEmpty());
CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
CameraCaptureSession.SESSION_ID_NONE);
assertFalse(request.isEmpty());
assertFalse(metadata.isEmpty());
if (needStream) {
int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
request.addTarget(mSurface);
}
return request;
}
private SubmitInfo submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception {
SubmitInfo requestInfo = mCameraUser.submitRequest(request, streaming);
assertTrue(
"Request IDs should be non-negative (expected: >= 0, actual: " +
requestInfo.getRequestId() + ")",
requestInfo.getRequestId() >= 0);
return requestInfo;
}
@Override
protected void setUp() throws Exception {
super.setUp();
/**
* Workaround for mockito and JB-MR2 incompatibility
*
* Avoid java.lang.IllegalArgumentException: dexcache == null
* https://code.google.com/p/dexmaker/issues/detail?id=2
*/
System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
mUtils = new CameraBinderTestUtils(getContext());
// This cannot be set in the constructor, since the onCreate isn't
// called yet
mCameraId = MediaFrameworkIntegrationTestRunner.mCameraId;
ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks();
String clientPackageName = getContext().getPackageName();
mMockCb = spy(dummyCallbacks);
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
clientPackageName, ICameraService.USE_CALLING_UID);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
createDefaultSurface();
Log.v(TAG, String.format("Camera %s connected", mCameraId));
}
@Override
protected void tearDown() throws Exception {
mCameraUser.disconnect();
mCameraUser = null;
mSurface.release();
mImageReader.close();
mHandlerThread.quitSafely();
}
@SmallTest
public void testCreateDefaultRequest() throws Exception {
CameraMetadataNative metadata = null;
assertTrue(metadata.isEmpty());
metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
assertFalse(metadata.isEmpty());
}
@SmallTest
public void testCreateStream() throws Exception {
int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
try {
mCameraUser.createStream(mOutputConfiguration);
fail("Creating same stream twice");
} catch (ServiceSpecificException e) {
assertEquals("Creating same stream twice",
e.errorCode, ICameraService.ERROR_ALREADY_EXISTS);
}
mCameraUser.deleteStream(streamId);
}
@SmallTest
public void testDeleteInvalidStream() throws Exception {
int[] badStreams = { -1, 0, 1, 0xC0FFEE };
for (int badStream : badStreams) {
try {
mCameraUser.deleteStream(badStream);
fail("Allowed bad stream delete");
} catch (ServiceSpecificException e) {
assertEquals(e.errorCode, ICameraService.ERROR_ILLEGAL_ARGUMENT);
}
}
}
@SmallTest
public void testCreateStreamTwo() throws Exception {
// Create first stream
int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
try {
mCameraUser.createStream(mOutputConfiguration);
fail("Created same stream twice");
} catch (ServiceSpecificException e) {
assertEquals("Created same stream twice",
ICameraService.ERROR_ALREADY_EXISTS, e.errorCode);
}
// Create second stream with a different surface.
SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
surfaceTexture.setDefaultBufferSize(640, 480);
Surface surface2 = new Surface(surfaceTexture);
OutputConfiguration output2 = new OutputConfiguration(surface2);
int streamId2 = mCameraUser.createStream(output2);
assertEquals(1, streamId2);
// Clean up streams
mCameraUser.deleteStream(streamId);
mCameraUser.deleteStream(streamId2);
}
@SmallTest
public void testSubmitBadRequest() throws Exception {
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
CaptureRequest request1 = builder.build();
try {
SubmitInfo requestInfo = mCameraUser.submitRequest(request1, /* streaming */false);
fail("Exception expected");
} catch(ServiceSpecificException e) {
assertEquals("Expected submitRequest to throw ServiceSpecificException with BAD_VALUE " +
"since we had 0 surface targets set.", ICameraService.ERROR_ILLEGAL_ARGUMENT,
e.errorCode);
}
builder.addTarget(mSurface);
CaptureRequest request2 = builder.build();
try {
SubmitInfo requestInfo = mCameraUser.submitRequest(request2, /* streaming */false);
fail("Exception expected");
} catch(ServiceSpecificException e) {
assertEquals("Expected submitRequest to throw ILLEGAL_ARGUMENT " +
"ServiceSpecificException since the target wasn't registered with createStream.",
ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
}
}
@SmallTest
public void testSubmitGoodRequest() throws Exception {
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
CaptureRequest request = builder.build();
// Submit valid request twice.
SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
assertNotSame("Request IDs should be unique for multiple requests",
requestInfo1.getRequestId(), requestInfo2.getRequestId());
}
@SmallTest
public void testSubmitStreamingRequest() throws Exception {
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
CaptureRequest request = builder.build();
// Submit valid request once (non-streaming), and another time
// (streaming)
SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfoStreaming = submitCameraRequest(request, /* streaming */true);
assertNotSame("Request IDs should be unique for multiple requests",
requestInfo1.getRequestId(),
requestInfoStreaming.getRequestId());
try {
long lastFrameNumber = mCameraUser.cancelRequest(-1);
fail("Expected exception");
} catch (ServiceSpecificException e) {
assertEquals("Invalid request IDs should not be cancellable",
ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
}
try {
long lastFrameNumber = mCameraUser.cancelRequest(requestInfo1.getRequestId());
fail("Expected exception");
} catch (ServiceSpecificException e) {
assertEquals("Non-streaming request IDs should not be cancellable",
ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
}
long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
}
@SmallTest
public void testCameraInfo() throws RemoteException {
CameraMetadataNative info = mCameraUser.getCameraInfo();
assertFalse(info.isEmpty());
assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
}
@SmallTest
public void testCameraCharacteristics() throws RemoteException {
CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId);
assertFalse(info.isEmpty());
assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
}
@SmallTest
public void testWaitUntilIdle() throws Exception {
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
SubmitInfo requestInfoStreaming = submitCameraRequest(builder.build(), /* streaming */true);
// Test Bad case first: waitUntilIdle when there is active repeating request
try {
mCameraUser.waitUntilIdle();
} catch (ServiceSpecificException e) {
assertEquals("waitUntilIdle is invalid operation when there is active repeating request",
ICameraService.ERROR_INVALID_OPERATION, e.errorCode);
}
// Test good case, waitUntilIdle when there is no active repeating request
long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
mCameraUser.waitUntilIdle();
}
@SmallTest
public void testCaptureResultCallbacks() throws Exception {
IsMetadataNotEmpty matcher = new IsMetadataNotEmpty();
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
// Test both single request and streaming request.
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived(
argThat(matcher),
any(CaptureResultExtras.class));
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
.onResultReceived(
argThat(matcher),
any(CaptureResultExtras.class));
}
@SmallTest
public void testCaptureStartedCallbacks() throws Exception {
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
// Test both single request and streaming request.
SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
any(CaptureResultExtras.class),
anyLong());
SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
.onCaptureStarted(
any(CaptureResultExtras.class),
timestamps.capture());
long timestamp = 0; // All timestamps should be larger than 0.
for (Long nextTimestamp : timestamps.getAllValues()) {
Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
assertTrue("Captures are out of order", timestamp < nextTimestamp);
timestamp = nextTimestamp;
}
}
@SmallTest
public void testIdleCallback() throws Exception {
int status;
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
// Try streaming
SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
// Wait a bit to fill up the queue
SystemClock.sleep(WAIT_FOR_WORK_MS);
// Cancel and make sure we eventually quiesce
long lastFrameNumber = mCameraUser.cancelRequest(streamingInfo.getRequestId());
verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onDeviceIdle();
// Submit a few capture requests
SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
// And wait for more idle
verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onDeviceIdle();
}
@SmallTest
public void testFlush() throws Exception {
int status;
// Initial flush should work
long lastFrameNumber = mCameraUser.flush();
// Then set up a stream
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
// Flush should still be a no-op, really
lastFrameNumber = mCameraUser.flush();
// Submit a few capture requests
SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
// Then flush and wait for idle
lastFrameNumber = mCameraUser.flush();
verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onDeviceIdle();
// Now a streaming request
SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
// Wait a bit to fill up the queue
SystemClock.sleep(WAIT_FOR_WORK_MS);
// Then flush and wait for the idle callback
lastFrameNumber = mCameraUser.flush();
verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onDeviceIdle();
// TODO: When errors are hooked up, count that errors + successful
// requests equal to 5.
}
}