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.ArgumentCaptor;
49import org.mockito.compat.ArgumentMatcher;
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 String 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#onRequestQueueEmpty()
152         */
153        @Override
154        public void onRequestQueueEmpty() throws RemoteException {
155            // TODO Auto-generated method stub
156
157        }
158
159        /*
160         * (non-Javadoc)
161         * @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
162         */
163        @Override
164        public void onRepeatingRequestError(long lastFrameNumber) {
165            // TODO Auto-generated method stub
166        }
167    }
168
169    class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> {
170        @Override
171        public boolean matchesObject(Object obj) {
172            return !((CameraMetadataNative) obj).isEmpty();
173        }
174    }
175
176    private void createDefaultSurface() {
177        mImageReader =
178                ImageReader.newInstance(DEFAULT_IMAGE_WIDTH,
179                        DEFAULT_IMAGE_HEIGHT,
180                        ImageFormat.YUV_420_888,
181                        MAX_NUM_IMAGES);
182        mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
183        mSurface = mImageReader.getSurface();
184        mOutputConfiguration = new OutputConfiguration(mSurface);
185    }
186
187    private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception {
188        CameraMetadataNative metadata = null;
189        assertTrue(metadata.isEmpty());
190
191        metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
192        assertFalse(metadata.isEmpty());
193
194        CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
195                CameraCaptureSession.SESSION_ID_NONE);
196        assertFalse(request.isEmpty());
197        assertFalse(metadata.isEmpty());
198        if (needStream) {
199            int streamId = mCameraUser.createStream(mOutputConfiguration);
200            assertEquals(0, streamId);
201            request.addTarget(mSurface);
202        }
203        return request;
204    }
205
206    private SubmitInfo submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception {
207        SubmitInfo requestInfo = mCameraUser.submitRequest(request, streaming);
208        assertTrue(
209                "Request IDs should be non-negative (expected: >= 0, actual: " +
210                requestInfo.getRequestId() + ")",
211                requestInfo.getRequestId() >= 0);
212        return requestInfo;
213    }
214
215    @Override
216    protected void setUp() throws Exception {
217        super.setUp();
218
219        /**
220         * Workaround for mockito and JB-MR2 incompatibility
221         *
222         * Avoid java.lang.IllegalArgumentException: dexcache == null
223         * https://code.google.com/p/dexmaker/issues/detail?id=2
224         */
225        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
226        mUtils = new CameraBinderTestUtils(getContext());
227
228        // This cannot be set in the constructor, since the onCreate isn't
229        // called yet
230        mCameraId = MediaFrameworkIntegrationTestRunner.mCameraId;
231
232        ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks();
233
234        String clientPackageName = getContext().getPackageName();
235
236        mMockCb = spy(dummyCallbacks);
237
238        mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
239                clientPackageName, ICameraService.USE_CALLING_UID);
240        assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
241        mHandlerThread = new HandlerThread(TAG);
242        mHandlerThread.start();
243        mHandler = new Handler(mHandlerThread.getLooper());
244        createDefaultSurface();
245
246        Log.v(TAG, String.format("Camera %s connected", mCameraId));
247    }
248
249    @Override
250    protected void tearDown() throws Exception {
251        mCameraUser.disconnect();
252        mCameraUser = null;
253        mSurface.release();
254        mImageReader.close();
255        mHandlerThread.quitSafely();
256    }
257
258    @SmallTest
259    public void testCreateDefaultRequest() throws Exception {
260        CameraMetadataNative metadata = null;
261        assertTrue(metadata.isEmpty());
262
263        metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
264        assertFalse(metadata.isEmpty());
265
266    }
267
268    @SmallTest
269    public void testCreateStream() throws Exception {
270        int streamId = mCameraUser.createStream(mOutputConfiguration);
271        assertEquals(0, streamId);
272
273        try {
274            mCameraUser.createStream(mOutputConfiguration);
275            fail("Creating same stream twice");
276        } catch (ServiceSpecificException e) {
277            assertEquals("Creating same stream twice",
278                    e.errorCode, ICameraService.ERROR_ALREADY_EXISTS);
279        }
280
281        mCameraUser.deleteStream(streamId);
282    }
283
284    @SmallTest
285    public void testDeleteInvalidStream() throws Exception {
286        int[] badStreams = { -1, 0, 1, 0xC0FFEE };
287        for (int badStream : badStreams) {
288            try {
289                mCameraUser.deleteStream(badStream);
290                fail("Allowed bad stream delete");
291            } catch (ServiceSpecificException e) {
292                assertEquals(e.errorCode, ICameraService.ERROR_ILLEGAL_ARGUMENT);
293            }
294        }
295    }
296
297    @SmallTest
298    public void testCreateStreamTwo() throws Exception {
299
300        // Create first stream
301        int streamId = mCameraUser.createStream(mOutputConfiguration);
302        assertEquals(0, streamId);
303
304        try {
305            mCameraUser.createStream(mOutputConfiguration);
306            fail("Created same stream twice");
307        } catch (ServiceSpecificException e) {
308            assertEquals("Created same stream twice",
309                    ICameraService.ERROR_ALREADY_EXISTS, e.errorCode);
310        }
311
312        // Create second stream with a different surface.
313        SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
314        surfaceTexture.setDefaultBufferSize(640, 480);
315        Surface surface2 = new Surface(surfaceTexture);
316        OutputConfiguration output2 = new OutputConfiguration(surface2);
317
318        int streamId2 = mCameraUser.createStream(output2);
319        assertEquals(1, streamId2);
320
321        // Clean up streams
322        mCameraUser.deleteStream(streamId);
323        mCameraUser.deleteStream(streamId2);
324    }
325
326    @SmallTest
327    public void testSubmitBadRequest() throws Exception {
328
329        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
330        CaptureRequest request1 = builder.build();
331        try {
332            SubmitInfo requestInfo = mCameraUser.submitRequest(request1, /* streaming */false);
333            fail("Exception expected");
334        } catch(ServiceSpecificException e) {
335            assertEquals("Expected submitRequest to throw ServiceSpecificException with BAD_VALUE " +
336                    "since we had 0 surface targets set.", ICameraService.ERROR_ILLEGAL_ARGUMENT,
337                    e.errorCode);
338        }
339
340        builder.addTarget(mSurface);
341        CaptureRequest request2 = builder.build();
342        try {
343            SubmitInfo requestInfo = mCameraUser.submitRequest(request2, /* streaming */false);
344            fail("Exception expected");
345        } catch(ServiceSpecificException e) {
346            assertEquals("Expected submitRequest to throw ILLEGAL_ARGUMENT " +
347                    "ServiceSpecificException since the target wasn't registered with createStream.",
348                    ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
349        }
350    }
351
352    @SmallTest
353    public void testSubmitGoodRequest() throws Exception {
354
355        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
356        CaptureRequest request = builder.build();
357
358        // Submit valid request twice.
359        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
360        SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
361        assertNotSame("Request IDs should be unique for multiple requests",
362                requestInfo1.getRequestId(), requestInfo2.getRequestId());
363
364    }
365
366    @SmallTest
367    public void testSubmitStreamingRequest() throws Exception {
368
369        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
370
371        CaptureRequest request = builder.build();
372
373        // Submit valid request once (non-streaming), and another time
374        // (streaming)
375        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
376
377        SubmitInfo requestInfoStreaming = submitCameraRequest(request, /* streaming */true);
378        assertNotSame("Request IDs should be unique for multiple requests",
379                requestInfo1.getRequestId(),
380                requestInfoStreaming.getRequestId());
381
382        try {
383            long lastFrameNumber = mCameraUser.cancelRequest(-1);
384            fail("Expected exception");
385        } catch (ServiceSpecificException e) {
386            assertEquals("Invalid request IDs should not be cancellable",
387                    ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
388        }
389
390        try {
391            long lastFrameNumber = mCameraUser.cancelRequest(requestInfo1.getRequestId());
392            fail("Expected exception");
393        } catch (ServiceSpecificException e) {
394            assertEquals("Non-streaming request IDs should not be cancellable",
395                    ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
396        }
397
398        long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
399    }
400
401    @SmallTest
402    public void testCameraInfo() throws RemoteException {
403        CameraMetadataNative info = mCameraUser.getCameraInfo();
404
405        assertFalse(info.isEmpty());
406        assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
407    }
408
409    @SmallTest
410    public void testCameraCharacteristics() throws RemoteException {
411        CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId);
412
413        assertFalse(info.isEmpty());
414        assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
415    }
416
417    @SmallTest
418    public void testWaitUntilIdle() throws Exception {
419        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
420        SubmitInfo requestInfoStreaming = submitCameraRequest(builder.build(), /* streaming */true);
421
422        // Test Bad case first: waitUntilIdle when there is active repeating request
423        try {
424            mCameraUser.waitUntilIdle();
425        } catch (ServiceSpecificException e) {
426            assertEquals("waitUntilIdle is invalid operation when there is active repeating request",
427                    ICameraService.ERROR_INVALID_OPERATION, e.errorCode);
428        }
429
430        // Test good case, waitUntilIdle when there is no active repeating request
431        long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
432        mCameraUser.waitUntilIdle();
433    }
434
435    @SmallTest
436    public void testCaptureResultCallbacks() throws Exception {
437        IsMetadataNotEmpty matcher = new IsMetadataNotEmpty();
438        CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
439
440        // Test both single request and streaming request.
441        verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived(
442                argThat(matcher),
443                any(CaptureResultExtras.class));
444
445        verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
446                .onResultReceived(
447                        argThat(matcher),
448                        any(CaptureResultExtras.class));
449    }
450
451    @SmallTest
452    public void testCaptureStartedCallbacks() throws Exception {
453        CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
454
455        ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
456
457        // Test both single request and streaming request.
458        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
459        verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
460                any(CaptureResultExtras.class),
461                anyLong());
462
463        SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
464        verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
465                .onCaptureStarted(
466                        any(CaptureResultExtras.class),
467                        timestamps.capture());
468
469        long timestamp = 0; // All timestamps should be larger than 0.
470        for (Long nextTimestamp : timestamps.getAllValues()) {
471            Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
472            assertTrue("Captures are out of order", timestamp < nextTimestamp);
473            timestamp = nextTimestamp;
474        }
475    }
476
477    @SmallTest
478    public void testIdleCallback() throws Exception {
479        int status;
480        CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
481
482        // Try streaming
483        SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
484
485        // Wait a bit to fill up the queue
486        SystemClock.sleep(WAIT_FOR_WORK_MS);
487
488        // Cancel and make sure we eventually quiesce
489        long lastFrameNumber = mCameraUser.cancelRequest(streamingInfo.getRequestId());
490
491        verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onDeviceIdle();
492
493        // Submit a few capture requests
494        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
495        SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
496        SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
497        SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
498        SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
499
500        // And wait for more idle
501        verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onDeviceIdle();
502
503    }
504
505    @SmallTest
506    public void testFlush() throws Exception {
507        int status;
508
509        // Initial flush should work
510        long lastFrameNumber = mCameraUser.flush();
511
512        // Then set up a stream
513        CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
514
515        // Flush should still be a no-op, really
516        lastFrameNumber = mCameraUser.flush();
517
518        // Submit a few capture requests
519        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
520        SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
521        SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
522        SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
523        SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
524
525        // Then flush and wait for idle
526        lastFrameNumber = mCameraUser.flush();
527
528        verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onDeviceIdle();
529
530        // Now a streaming request
531        SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
532
533        // Wait a bit to fill up the queue
534        SystemClock.sleep(WAIT_FOR_WORK_MS);
535
536        // Then flush and wait for the idle callback
537        lastFrameNumber = mCameraUser.flush();
538
539        verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onDeviceIdle();
540
541        // TODO: When errors are hooked up, count that errors + successful
542        // requests equal to 5.
543    }
544}
545