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