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