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