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