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