Home | History | Annotate | Download | only in integration
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.mediaframeworktest.integration;
     18 
     19 import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW;
     20 
     21 import static org.mockito.Mockito.any;
     22 import static org.mockito.Mockito.anyLong;
     23 import static org.mockito.Mockito.argThat;
     24 import static org.mockito.Mockito.spy;
     25 import static org.mockito.Mockito.timeout;
     26 import static org.mockito.Mockito.verify;
     27 
     28 import android.graphics.ImageFormat;
     29 import android.graphics.SurfaceTexture;
     30 import android.hardware.ICameraService;
     31 import android.hardware.camera2.CameraCaptureSession;
     32 import android.hardware.camera2.CameraCharacteristics;
     33 import android.hardware.camera2.CaptureRequest;
     34 import android.hardware.camera2.ICameraDeviceCallbacks;
     35 import android.hardware.camera2.ICameraDeviceUser;
     36 import android.hardware.camera2.impl.CameraMetadataNative;
     37 import android.hardware.camera2.impl.CaptureResultExtras;
     38 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
     39 import android.hardware.camera2.params.OutputConfiguration;
     40 import android.hardware.camera2.utils.SubmitInfo;
     41 import android.media.Image;
     42 import android.media.ImageReader;
     43 import android.os.Handler;
     44 import android.os.HandlerThread;
     45 import android.os.RemoteException;
     46 import android.os.ServiceSpecificException;
     47 import android.os.SystemClock;
     48 import android.test.AndroidTestCase;
     49 import android.test.suitebuilder.annotation.SmallTest;
     50 import android.util.Log;
     51 import android.view.Surface;
     52 
     53 import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
     54 
     55 import org.mockito.ArgumentCaptor;
     56 import org.mockito.ArgumentMatcher;
     57 
     58 public class CameraDeviceBinderTest extends AndroidTestCase {
     59     private static String TAG = "CameraDeviceBinderTest";
     60     // Number of streaming callbacks need to check.
     61     private static int NUM_CALLBACKS_CHECKED = 10;
     62     // Wait for capture result timeout value: 1500ms
     63     private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500;
     64     // Wait for flush timeout value: 1000ms
     65     private final static int WAIT_FOR_FLUSH_TIMEOUT_MS = 1000;
     66     // Wait for idle timeout value: 2000ms
     67     private final static int WAIT_FOR_IDLE_TIMEOUT_MS = 2000;
     68     // Wait while camera device starts working on requests
     69     private final static int WAIT_FOR_WORK_MS = 300;
     70     // Default size is VGA, which is mandatory camera supported image size by CDD.
     71     private static final int DEFAULT_IMAGE_WIDTH = 640;
     72     private static final int DEFAULT_IMAGE_HEIGHT = 480;
     73     private static final int MAX_NUM_IMAGES = 5;
     74 
     75     private String mCameraId;
     76     private ICameraDeviceUser mCameraUser;
     77     private CameraBinderTestUtils mUtils;
     78     private ICameraDeviceCallbacks.Stub mMockCb;
     79     private Surface mSurface;
     80     private OutputConfiguration mOutputConfiguration;
     81     private HandlerThread mHandlerThread;
     82     private Handler mHandler;
     83     ImageReader mImageReader;
     84 
     85     public CameraDeviceBinderTest() {
     86     }
     87 
     88     private class ImageDropperListener implements ImageReader.OnImageAvailableListener {
     89 
     90         @Override
     91         public void onImageAvailable(ImageReader reader) {
     92             Image image = reader.acquireNextImage();
     93             if (image != null) image.close();
     94         }
     95     }
     96 
     97     public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
     98 
     99         /*
    100          * (non-Javadoc)
    101          * @see
    102          * android.hardware.camera2.ICameraDeviceCallbacks#onDeviceError(int,
    103          * android.hardware.camera2.CaptureResultExtras)
    104          */
    105         public void onDeviceError(int errorCode, CaptureResultExtras resultExtras)
    106                 throws RemoteException {
    107             // TODO Auto-generated method stub
    108 
    109         }
    110 
    111         /*
    112          * (non-Javadoc)
    113          * @see android.hardware.camera2.ICameraDeviceCallbacks#onDeviceIdle()
    114          */
    115         public void onDeviceIdle() throws RemoteException {
    116             // TODO Auto-generated method stub
    117 
    118         }
    119 
    120         /*
    121          * (non-Javadoc)
    122          * @see
    123          * android.hardware.camera2.ICameraDeviceCallbacks#onCaptureStarted(
    124          * android.hardware.camera2.CaptureResultExtras, long)
    125          */
    126         public void onCaptureStarted(CaptureResultExtras resultExtras, long timestamp)
    127                 throws RemoteException {
    128             // TODO Auto-generated method stub
    129 
    130         }
    131 
    132         /*
    133          * (non-Javadoc)
    134          * @see
    135          * android.hardware.camera2.ICameraDeviceCallbacks#onResultReceived(
    136          * android.hardware.camera2.impl.CameraMetadataNative,
    137          * android.hardware.camera2.CaptureResultExtras)
    138          */
    139         public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
    140                 PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
    141             // TODO Auto-generated method stub
    142 
    143         }
    144 
    145         /*
    146          * (non-Javadoc)
    147          * @see android.hardware.camera2.ICameraDeviceCallbacks#onPrepared()
    148          */
    149         @Override
    150         public void onPrepared(int streamId) throws RemoteException {
    151             // TODO Auto-generated method stub
    152 
    153         }
    154 
    155         /*
    156          * (non-Javadoc)
    157          * @see android.hardware.camera2.ICameraDeviceCallbacks#onRequestQueueEmpty()
    158          */
    159         @Override
    160         public void onRequestQueueEmpty() throws RemoteException {
    161             // TODO Auto-generated method stub
    162 
    163         }
    164 
    165         /*
    166          * (non-Javadoc)
    167          * @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
    168          */
    169         @Override
    170         public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
    171             // TODO Auto-generated method stub
    172         }
    173     }
    174 
    175     class IsMetadataNotEmpty implements ArgumentMatcher<CameraMetadataNative> {
    176         @Override
    177         public boolean matches(CameraMetadataNative obj) {
    178             return !obj.isEmpty();
    179         }
    180     }
    181 
    182     private void createDefaultSurface() {
    183         mImageReader =
    184                 ImageReader.newInstance(DEFAULT_IMAGE_WIDTH,
    185                         DEFAULT_IMAGE_HEIGHT,
    186                         ImageFormat.YUV_420_888,
    187                         MAX_NUM_IMAGES);
    188         mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
    189         mSurface = mImageReader.getSurface();
    190         mOutputConfiguration = new OutputConfiguration(mSurface);
    191     }
    192 
    193     private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception {
    194         CameraMetadataNative metadata = null;
    195         assertTrue(metadata.isEmpty());
    196 
    197         metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
    198         assertFalse(metadata.isEmpty());
    199 
    200         CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
    201                 CameraCaptureSession.SESSION_ID_NONE, mCameraId, /*physicalCameraIdSet*/null);
    202         assertFalse(request.isEmpty());
    203         assertFalse(metadata.isEmpty());
    204         if (needStream) {
    205             int streamId = mCameraUser.createStream(mOutputConfiguration);
    206             assertEquals(0, streamId);
    207             request.addTarget(mSurface);
    208         }
    209         return request;
    210     }
    211 
    212     private SubmitInfo submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception {
    213         SubmitInfo requestInfo = mCameraUser.submitRequest(request, streaming);
    214         assertTrue(
    215                 "Request IDs should be non-negative (expected: >= 0, actual: " +
    216                 requestInfo.getRequestId() + ")",
    217                 requestInfo.getRequestId() >= 0);
    218         return requestInfo;
    219     }
    220 
    221     @Override
    222     protected void setUp() throws Exception {
    223         super.setUp();
    224 
    225         /**
    226          * Workaround for mockito and JB-MR2 incompatibility
    227          *
    228          * Avoid java.lang.IllegalArgumentException: dexcache == null
    229          * https://code.google.com/p/dexmaker/issues/detail?id=2
    230          */
    231         System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
    232         mUtils = new CameraBinderTestUtils(getContext());
    233 
    234         // This cannot be set in the constructor, since the onCreate isn't
    235         // called yet
    236         mCameraId = MediaFrameworkIntegrationTestRunner.mCameraId;
    237 
    238         ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks();
    239 
    240         String clientPackageName = getContext().getPackageName();
    241 
    242         mMockCb = spy(dummyCallbacks);
    243 
    244         mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
    245                 clientPackageName, ICameraService.USE_CALLING_UID);
    246         assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
    247         mHandlerThread = new HandlerThread(TAG);
    248         mHandlerThread.start();
    249         mHandler = new Handler(mHandlerThread.getLooper());
    250         createDefaultSurface();
    251 
    252         Log.v(TAG, String.format("Camera %s connected", mCameraId));
    253     }
    254 
    255     @Override
    256     protected void tearDown() throws Exception {
    257         mCameraUser.disconnect();
    258         mCameraUser = null;
    259         mSurface.release();
    260         mImageReader.close();
    261         mHandlerThread.quitSafely();
    262     }
    263 
    264     @SmallTest
    265     public void testCreateDefaultRequest() throws Exception {
    266         CameraMetadataNative metadata = null;
    267         assertTrue(metadata.isEmpty());
    268 
    269         metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
    270         assertFalse(metadata.isEmpty());
    271 
    272     }
    273 
    274     @SmallTest
    275     public void testCreateStream() throws Exception {
    276         int streamId = mCameraUser.createStream(mOutputConfiguration);
    277         assertEquals(0, streamId);
    278 
    279         try {
    280             mCameraUser.createStream(mOutputConfiguration);
    281             fail("Creating same stream twice");
    282         } catch (ServiceSpecificException e) {
    283             assertEquals("Creating same stream twice",
    284                     e.errorCode, ICameraService.ERROR_ALREADY_EXISTS);
    285         }
    286 
    287         mCameraUser.deleteStream(streamId);
    288     }
    289 
    290     @SmallTest
    291     public void testDeleteInvalidStream() throws Exception {
    292         int[] badStreams = { -1, 0, 1, 0xC0FFEE };
    293         for (int badStream : badStreams) {
    294             try {
    295                 mCameraUser.deleteStream(badStream);
    296                 fail("Allowed bad stream delete");
    297             } catch (ServiceSpecificException e) {
    298                 assertEquals(e.errorCode, ICameraService.ERROR_ILLEGAL_ARGUMENT);
    299             }
    300         }
    301     }
    302 
    303     @SmallTest
    304     public void testCreateStreamTwo() throws Exception {
    305 
    306         // Create first stream
    307         int streamId = mCameraUser.createStream(mOutputConfiguration);
    308         assertEquals(0, streamId);
    309 
    310         try {
    311             mCameraUser.createStream(mOutputConfiguration);
    312             fail("Created same stream twice");
    313         } catch (ServiceSpecificException e) {
    314             assertEquals("Created same stream twice",
    315                     ICameraService.ERROR_ALREADY_EXISTS, e.errorCode);
    316         }
    317 
    318         // Create second stream with a different surface.
    319         SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
    320         surfaceTexture.setDefaultBufferSize(640, 480);
    321         Surface surface2 = new Surface(surfaceTexture);
    322         OutputConfiguration output2 = new OutputConfiguration(surface2);
    323 
    324         int streamId2 = mCameraUser.createStream(output2);
    325         assertEquals(1, streamId2);
    326 
    327         // Clean up streams
    328         mCameraUser.deleteStream(streamId);
    329         mCameraUser.deleteStream(streamId2);
    330     }
    331 
    332     @SmallTest
    333     public void testSubmitBadRequest() throws Exception {
    334 
    335         CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
    336         CaptureRequest request1 = builder.build();
    337         try {
    338             SubmitInfo requestInfo = mCameraUser.submitRequest(request1, /* streaming */false);
    339             fail("Exception expected");
    340         } catch(ServiceSpecificException e) {
    341             assertEquals("Expected submitRequest to throw ServiceSpecificException with BAD_VALUE " +
    342                     "since we had 0 surface targets set.", ICameraService.ERROR_ILLEGAL_ARGUMENT,
    343                     e.errorCode);
    344         }
    345 
    346         builder.addTarget(mSurface);
    347         CaptureRequest request2 = builder.build();
    348         try {
    349             SubmitInfo requestInfo = mCameraUser.submitRequest(request2, /* streaming */false);
    350             fail("Exception expected");
    351         } catch(ServiceSpecificException e) {
    352             assertEquals("Expected submitRequest to throw ILLEGAL_ARGUMENT " +
    353                     "ServiceSpecificException since the target wasn't registered with createStream.",
    354                     ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
    355         }
    356     }
    357 
    358     @SmallTest
    359     public void testSubmitGoodRequest() throws Exception {
    360 
    361         CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
    362         CaptureRequest request = builder.build();
    363 
    364         // Submit valid request twice.
    365         SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
    366         SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
    367         assertNotSame("Request IDs should be unique for multiple requests",
    368                 requestInfo1.getRequestId(), requestInfo2.getRequestId());
    369 
    370     }
    371 
    372     @SmallTest
    373     public void testSubmitStreamingRequest() throws Exception {
    374 
    375         CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
    376 
    377         CaptureRequest request = builder.build();
    378 
    379         // Submit valid request once (non-streaming), and another time
    380         // (streaming)
    381         SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
    382 
    383         SubmitInfo requestInfoStreaming = submitCameraRequest(request, /* streaming */true);
    384         assertNotSame("Request IDs should be unique for multiple requests",
    385                 requestInfo1.getRequestId(),
    386                 requestInfoStreaming.getRequestId());
    387 
    388         try {
    389             long lastFrameNumber = mCameraUser.cancelRequest(-1);
    390             fail("Expected exception");
    391         } catch (ServiceSpecificException e) {
    392             assertEquals("Invalid request IDs should not be cancellable",
    393                     ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
    394         }
    395 
    396         try {
    397             long lastFrameNumber = mCameraUser.cancelRequest(requestInfo1.getRequestId());
    398             fail("Expected exception");
    399         } catch (ServiceSpecificException e) {
    400             assertEquals("Non-streaming request IDs should not be cancellable",
    401                     ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
    402         }
    403 
    404         long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
    405     }
    406 
    407     @SmallTest
    408     public void testCameraInfo() throws RemoteException {
    409         CameraMetadataNative info = mCameraUser.getCameraInfo();
    410 
    411         assertFalse(info.isEmpty());
    412         assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
    413     }
    414 
    415     @SmallTest
    416     public void testCameraCharacteristics() throws RemoteException {
    417         CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId);
    418 
    419         assertFalse(info.isEmpty());
    420         assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
    421     }
    422 
    423     @SmallTest
    424     public void testWaitUntilIdle() throws Exception {
    425         CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
    426         SubmitInfo requestInfoStreaming = submitCameraRequest(builder.build(), /* streaming */true);
    427 
    428         // Test Bad case first: waitUntilIdle when there is active repeating request
    429         try {
    430             mCameraUser.waitUntilIdle();
    431         } catch (ServiceSpecificException e) {
    432             assertEquals("waitUntilIdle is invalid operation when there is active repeating request",
    433                     ICameraService.ERROR_INVALID_OPERATION, e.errorCode);
    434         }
    435 
    436         // Test good case, waitUntilIdle when there is no active repeating request
    437         long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
    438         mCameraUser.waitUntilIdle();
    439     }
    440 
    441     @SmallTest
    442     public void testCaptureResultCallbacks() throws Exception {
    443         IsMetadataNotEmpty matcher = new IsMetadataNotEmpty();
    444         CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
    445 
    446         // Test both single request and streaming request.
    447         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived(
    448                 argThat(matcher),
    449                 any(CaptureResultExtras.class),
    450                 any(PhysicalCaptureResultInfo[].class));
    451 
    452         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
    453                 .onResultReceived(
    454                         argThat(matcher),
    455                         any(CaptureResultExtras.class),
    456                         any(PhysicalCaptureResultInfo[].class));
    457     }
    458 
    459     @SmallTest
    460     public void testCaptureStartedCallbacks() throws Exception {
    461         CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
    462 
    463         ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
    464 
    465         // Test both single request and streaming request.
    466         SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
    467         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
    468                 any(CaptureResultExtras.class),
    469                 anyLong());
    470 
    471         SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
    472         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
    473                 .onCaptureStarted(
    474                         any(CaptureResultExtras.class),
    475                         timestamps.capture());
    476 
    477         long timestamp = 0; // All timestamps should be larger than 0.
    478         for (Long nextTimestamp : timestamps.getAllValues()) {
    479             Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
    480             assertTrue("Captures are out of order", timestamp < nextTimestamp);
    481             timestamp = nextTimestamp;
    482         }
    483     }
    484 
    485     @SmallTest
    486     public void testIdleCallback() throws Exception {
    487         int status;
    488         CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
    489 
    490         // Try streaming
    491         SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
    492 
    493         // Wait a bit to fill up the queue
    494         SystemClock.sleep(WAIT_FOR_WORK_MS);
    495 
    496         // Cancel and make sure we eventually quiesce
    497         long lastFrameNumber = mCameraUser.cancelRequest(streamingInfo.getRequestId());
    498 
    499         verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onDeviceIdle();
    500 
    501         // Submit a few capture requests
    502         SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
    503         SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
    504         SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
    505         SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
    506         SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
    507 
    508         // And wait for more idle
    509         verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onDeviceIdle();
    510 
    511     }
    512 
    513     @SmallTest
    514     public void testFlush() throws Exception {
    515         int status;
    516 
    517         // Initial flush should work
    518         long lastFrameNumber = mCameraUser.flush();
    519 
    520         // Then set up a stream
    521         CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
    522 
    523         // Flush should still be a no-op, really
    524         lastFrameNumber = mCameraUser.flush();
    525 
    526         // Submit a few capture requests
    527         SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
    528         SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
    529         SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
    530         SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
    531         SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
    532 
    533         // Then flush and wait for idle
    534         lastFrameNumber = mCameraUser.flush();
    535 
    536         verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onDeviceIdle();
    537 
    538         // Now a streaming request
    539         SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
    540 
    541         // Wait a bit to fill up the queue
    542         SystemClock.sleep(WAIT_FOR_WORK_MS);
    543 
    544         // Then flush and wait for the idle callback
    545         lastFrameNumber = mCameraUser.flush();
    546 
    547         verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onDeviceIdle();
    548 
    549         // TODO: When errors are hooked up, count that errors + successful
    550         // requests equal to 5.
    551     }
    552 }
    553