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