1 /* 2 * Copyright 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 android.hardware.camera2.cts; 18 19 import static org.mockito.Mockito.*; 20 import static org.mockito.AdditionalMatchers.not; 21 import static org.mockito.AdditionalMatchers.and; 22 23 import android.content.Context; 24 import android.content.pm.PackageManager; 25 import android.hardware.camera2.CameraAccessException; 26 import android.hardware.camera2.CameraCharacteristics; 27 import android.hardware.camera2.CameraDevice; 28 import android.hardware.camera2.CameraDevice.StateCallback; 29 import android.hardware.camera2.CameraManager; 30 import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback; 31 import android.hardware.camera2.cts.helpers.CameraErrorCollector; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.test.AndroidTestCase; 35 import android.util.Log; 36 37 import com.android.ex.camera2.blocking.BlockingStateCallback; 38 39 import org.mockito.ArgumentCaptor; 40 import org.mockito.InOrder; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.concurrent.LinkedBlockingQueue; 47 48 /** 49 * <p>Basic test for CameraManager class.</p> 50 */ 51 public class CameraManagerTest extends AndroidTestCase { 52 private static final String TAG = "CameraManagerTest"; 53 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 54 private static final int NUM_CAMERA_REOPENS = 10; 55 56 private PackageManager mPackageManager; 57 private CameraManager mCameraManager; 58 private NoopCameraListener mListener; 59 private HandlerThread mHandlerThread; 60 private Handler mHandler; 61 private BlockingStateCallback mCameraListener; 62 private CameraErrorCollector mCollector; 63 64 @Override 65 public void setContext(Context context) { 66 super.setContext(context); 67 mCameraManager = (CameraManager)context.getSystemService(Context.CAMERA_SERVICE); 68 assertNotNull("Can't connect to camera manager", mCameraManager); 69 mPackageManager = context.getPackageManager(); 70 assertNotNull("Can't get package manager", mPackageManager); 71 mListener = new NoopCameraListener(); 72 } 73 74 @Override 75 protected void setUp() throws Exception { 76 super.setUp(); 77 78 /** 79 * Workaround for mockito and JB-MR2 incompatibility 80 * 81 * Avoid java.lang.IllegalArgumentException: dexcache == null 82 * https://code.google.com/p/dexmaker/issues/detail?id=2 83 */ 84 System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); 85 86 mCameraListener = spy(new BlockingStateCallback()); 87 88 mHandlerThread = new HandlerThread(TAG); 89 mHandlerThread.start(); 90 mHandler = new Handler(mHandlerThread.getLooper()); 91 mCollector = new CameraErrorCollector(); 92 } 93 94 @Override 95 protected void tearDown() throws Exception { 96 mHandlerThread.quitSafely(); 97 mHandler = null; 98 99 try { 100 mCollector.verify(); 101 } catch (Throwable e) { 102 // When new Exception(e) is used, exception info will be printed twice. 103 throw new Exception(e.getMessage()); 104 } finally { 105 super.tearDown(); 106 } 107 } 108 109 /** 110 * Verifies that the reason is in the range of public-only codes. 111 */ 112 private static int checkCameraAccessExceptionReason(CameraAccessException e) { 113 int reason = e.getReason(); 114 115 switch (reason) { 116 case CameraAccessException.CAMERA_DISABLED: 117 case CameraAccessException.CAMERA_DISCONNECTED: 118 case CameraAccessException.CAMERA_ERROR: 119 return reason; 120 } 121 122 fail("Invalid CameraAccessException code: " + reason); 123 124 return -1; // unreachable 125 } 126 127 public void testCameraManagerGetDeviceIdList() throws Exception { 128 129 // Test: that the getCameraIdList method runs without exceptions. 130 String[] ids = mCameraManager.getCameraIdList(); 131 if (VERBOSE) Log.v(TAG, "CameraManager ids: " + Arrays.toString(ids)); 132 133 /** 134 * Test: that if there is at least one reported id, then the system must have 135 * the FEATURE_CAMERA_ANY feature. 136 */ 137 assertTrue("System camera feature and camera id list don't match", 138 ids.length == 0 || 139 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)); 140 141 /** 142 * Test: that if the device has front or rear facing cameras, then there 143 * must be matched system features. 144 */ 145 for (int i = 0; i < ids.length; i++) { 146 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); 147 assertNotNull("Can't get camera characteristics for camera " + ids[i], props); 148 Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING); 149 assertNotNull("Can't get lens facing info", lensFacing); 150 if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) { 151 assertTrue("System doesn't have front camera feature", 152 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) || 153 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)); 154 } else if (lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 155 assertTrue("System doesn't have back camera feature", 156 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)); 157 } else { 158 fail("Unknown camera lens facing " + lensFacing.toString()); 159 } 160 } 161 162 /** 163 * Test: that if there is one camera device, then the system must have some 164 * specific features. 165 */ 166 assertTrue("Missing system feature: FEATURE_CAMERA_ANY", 167 ids.length == 0 168 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)); 169 assertTrue("Missing system feature: FEATURE_CAMERA or FEATURE_CAMERA_FRONT", 170 ids.length == 0 171 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) 172 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)); 173 } 174 175 // Test: that properties can be queried from each device, without exceptions. 176 public void testCameraManagerGetCameraCharacteristics() throws Exception { 177 String[] ids = mCameraManager.getCameraIdList(); 178 for (int i = 0; i < ids.length; i++) { 179 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); 180 assertNotNull( 181 String.format("Can't get camera characteristics from: ID %s", ids[i]), props); 182 } 183 } 184 185 // Test: that an exception is thrown if an invalid device id is passed down. 186 public void testCameraManagerInvalidDevice() throws Exception { 187 String[] ids = mCameraManager.getCameraIdList(); 188 // Create an invalid id by concatenating all the valid ids together. 189 StringBuilder invalidId = new StringBuilder(); 190 invalidId.append("INVALID"); 191 for (int i = 0; i < ids.length; i++) { 192 invalidId.append(ids[i]); 193 } 194 195 try { 196 mCameraManager.getCameraCharacteristics( 197 invalidId.toString()); 198 fail(String.format("Accepted invalid camera ID: %s", invalidId.toString())); 199 } catch (IllegalArgumentException e) { 200 // This is the exception that should be thrown in this case. 201 } 202 } 203 204 // Test: that each camera device can be opened one at a time, several times. 205 public void testCameraManagerOpenCamerasSerially() throws Exception { 206 String[] ids = mCameraManager.getCameraIdList(); 207 for (int i = 0; i < ids.length; i++) { 208 for (int j = 0; j < NUM_CAMERA_REOPENS; j++) { 209 CameraDevice camera = null; 210 try { 211 MockStateCallback mockListener = MockStateCallback.mock(); 212 mCameraListener = new BlockingStateCallback(mockListener); 213 214 mCameraManager.openCamera(ids[i], mCameraListener, mHandler); 215 216 // Block until unConfigured 217 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 218 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 219 220 // Ensure state transitions are in right order: 221 // -- 1) Opened 222 // Ensure no other state transitions have occurred: 223 camera = verifyCameraStateOpened(ids[i], mockListener); 224 } finally { 225 if (camera != null) { 226 camera.close(); 227 } 228 } 229 } 230 } 231 } 232 233 /** 234 * Test: one or more camera devices can be open at the same time, or the right error state 235 * is set if this can't be done. 236 */ 237 public void testCameraManagerOpenAllCameras() throws Exception { 238 String[] ids = mCameraManager.getCameraIdList(); 239 assertNotNull("Camera ids shouldn't be null", ids); 240 241 // Skip test if the device doesn't have multiple cameras. 242 if (ids.length <= 1) { 243 return; 244 } 245 246 List<CameraDevice> cameraList = new ArrayList<CameraDevice>(); 247 List<MockStateCallback> listenerList = new ArrayList<MockStateCallback>(); 248 List<BlockingStateCallback> blockingListenerList = new ArrayList<BlockingStateCallback>(); 249 try { 250 for (int i = 0; i < ids.length; i++) { 251 // Ignore state changes from other cameras 252 MockStateCallback mockListener = MockStateCallback.mock(); 253 mCameraListener = new BlockingStateCallback(mockListener); 254 255 /** 256 * Track whether or not we got a synchronous error from openCamera. 257 * 258 * A synchronous error must also be accompanied by an asynchronous 259 * StateCallback#onError callback. 260 */ 261 boolean expectingError = false; 262 263 String cameraId = ids[i]; 264 try { 265 mCameraManager.openCamera(cameraId, mCameraListener, 266 mHandler); 267 } catch (CameraAccessException e) { 268 if (checkCameraAccessExceptionReason(e) == CameraAccessException.CAMERA_ERROR) { 269 expectingError = true; 270 } else { 271 // TODO: We should handle a Disabled camera by passing here and elsewhere 272 fail("Camera must not be disconnected or disabled for this test" + ids[i]); 273 } 274 } 275 276 List<Integer> expectedStates = new ArrayList<Integer>(); 277 expectedStates.add(BlockingStateCallback.STATE_OPENED); 278 expectedStates.add(BlockingStateCallback.STATE_ERROR); 279 int state = mCameraListener.waitForAnyOfStates( 280 expectedStates, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 281 282 // It's possible that we got an asynchronous error transition only. This is ok. 283 if (expectingError) { 284 assertEquals("Throwing a CAMERA_ERROR exception must be accompanied with a " + 285 "StateCallback#onError callback", 286 BlockingStateCallback.STATE_ERROR, state); 287 } 288 289 /** 290 * Two situations are considered passing: 291 * 1) The camera opened successfully. 292 * => No error must be set. 293 * 2) The camera did not open because there were too many other cameras opened. 294 * => Only MAX_CAMERAS_IN_USE error must be set. 295 * 296 * Any other situation is considered a failure. 297 * 298 * For simplicity we treat disconnecting asynchronously as a failure, so 299 * camera devices should not be physically unplugged during this test. 300 */ 301 302 CameraDevice camera; 303 if (state == BlockingStateCallback.STATE_ERROR) { 304 // Camera did not open because too many other cameras were opened 305 // => onError called exactly once with a non-null camera 306 assertTrue("At least one camera must be opened successfully", 307 cameraList.size() > 0); 308 309 ArgumentCaptor<CameraDevice> argument = 310 ArgumentCaptor.forClass(CameraDevice.class); 311 312 verify(mockListener) 313 .onError( 314 argument.capture(), 315 eq(CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE)); 316 verifyNoMoreInteractions(mockListener); 317 318 camera = argument.getValue(); 319 assertNotNull("Expected a non-null camera for the error transition for ID: " 320 + ids[i], camera); 321 } else if (state == BlockingStateCallback.STATE_OPENED) { 322 // Camera opened successfully. 323 // => onOpened called exactly once 324 camera = verifyCameraStateOpened(cameraId, 325 mockListener); 326 } else { 327 fail("Unexpected state " + state); 328 camera = null; // unreachable. but need this for java compiler 329 } 330 331 // Keep track of cameras so we can close it later 332 cameraList.add(camera); 333 listenerList.add(mockListener); 334 blockingListenerList.add(mCameraListener); 335 } 336 } finally { 337 for (CameraDevice camera : cameraList) { 338 camera.close(); 339 } 340 for (BlockingStateCallback blockingListener : blockingListenerList) { 341 blockingListener.waitForState( 342 BlockingStateCallback.STATE_CLOSED, 343 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 344 } 345 } 346 347 /* 348 * Ensure that no state transitions have bled through from one camera to another 349 * after closing the cameras. 350 */ 351 int i = 0; 352 for (MockStateCallback listener : listenerList) { 353 CameraDevice camera = cameraList.get(i); 354 355 verify(listener).onClosed(eq(camera)); 356 verifyNoMoreInteractions(listener); 357 i++; 358 // Only a #close can happen on the camera since we were done with it. 359 // Also nothing else should've happened between the close and the open. 360 } 361 } 362 363 /** 364 * Verifies the camera in this listener was opened and then unconfigured exactly once. 365 * 366 * <p>This assumes that no other action to the camera has been done (e.g. 367 * it hasn't been configured, or closed, or disconnected). Verification is 368 * performed immediately without any timeouts.</p> 369 * 370 * <p>This checks that the state has previously changed first for opened and then unconfigured. 371 * Any other state transitions will fail. A test failure is thrown if verification fails.</p> 372 * 373 * @param cameraId Camera identifier 374 * @param listener Listener which was passed to {@link CameraManager#openCamera} 375 * 376 * @return The camera device (non-{@code null}). 377 */ 378 private static CameraDevice verifyCameraStateOpened(String cameraId, 379 MockStateCallback listener) { 380 ArgumentCaptor<CameraDevice> argument = 381 ArgumentCaptor.forClass(CameraDevice.class); 382 InOrder inOrder = inOrder(listener); 383 384 /** 385 * State transitions (in that order): 386 * 1) onOpened 387 * 388 * No other transitions must occur for successful #openCamera 389 */ 390 inOrder.verify(listener) 391 .onOpened(argument.capture()); 392 393 CameraDevice camera = argument.getValue(); 394 assertNotNull( 395 String.format("Failed to open camera device ID: %s", cameraId), 396 camera); 397 398 // Do not use inOrder here since that would skip anything called before onOpened 399 verifyNoMoreInteractions(listener); 400 401 return camera; 402 } 403 404 /** 405 * Test: that opening the same device multiple times and make sure the right 406 * error state is set. 407 */ 408 public void testCameraManagerOpenCameraTwice() throws Exception { 409 String[] ids = mCameraManager.getCameraIdList(); 410 411 // Test across every camera device. 412 for (int i = 0; i < ids.length; ++i) { 413 CameraDevice successCamera = null; 414 mCollector.setCameraId(ids[i]); 415 416 try { 417 MockStateCallback mockSuccessListener = MockStateCallback.mock(); 418 MockStateCallback mockFailListener = MockStateCallback.mock(); 419 420 BlockingStateCallback successListener = 421 new BlockingStateCallback(mockSuccessListener); 422 BlockingStateCallback failListener = 423 new BlockingStateCallback(mockFailListener); 424 425 mCameraManager.openCamera(ids[i], successListener, mHandler); 426 mCameraManager.openCamera(ids[i], failListener, 427 mHandler); 428 429 successListener.waitForState(BlockingStateCallback.STATE_OPENED, 430 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 431 ArgumentCaptor<CameraDevice> argument = 432 ArgumentCaptor.forClass(CameraDevice.class); 433 verify(mockSuccessListener, atLeastOnce()).onOpened(argument.capture()); 434 verify(mockSuccessListener, atLeastOnce()).onDisconnected(argument.capture()); 435 436 failListener.waitForState(BlockingStateCallback.STATE_OPENED, 437 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 438 verify(mockFailListener, atLeastOnce()).onOpened(argument.capture()); 439 440 successCamera = verifyCameraStateOpened( 441 ids[i], mockFailListener); 442 443 verifyNoMoreInteractions(mockFailListener); 444 } finally { 445 if (successCamera != null) { 446 successCamera.close(); 447 } 448 } 449 } 450 } 451 452 private class NoopCameraListener extends CameraManager.AvailabilityCallback { 453 @Override 454 public void onCameraAvailable(String cameraId) { 455 // No-op 456 } 457 458 @Override 459 public void onCameraUnavailable(String cameraId) { 460 // No-op 461 } 462 } 463 464 /** 465 * Test: that the APIs to register and unregister a listener run successfully; 466 * doesn't test that the listener actually gets invoked at the right time. 467 * Registering a listener multiple times should have no effect, and unregistering 468 * a listener that isn't registered should have no effect. 469 */ 470 public void testCameraManagerListener() throws Exception { 471 mCameraManager.unregisterAvailabilityCallback(mListener); 472 mCameraManager.registerAvailabilityCallback(mListener, mHandler); 473 mCameraManager.registerAvailabilityCallback(mListener, mHandler); 474 mCameraManager.unregisterAvailabilityCallback(mListener); 475 mCameraManager.unregisterAvailabilityCallback(mListener); 476 } 477 478 /** 479 * Test that the availability callbacks fire when expected 480 */ 481 public void testCameraManagerListenerCallbacks() throws Exception { 482 final int AVAILABILITY_TIMEOUT_MS = 10; 483 484 final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>(); 485 final LinkedBlockingQueue<String> unavailableEventQueue = new LinkedBlockingQueue<>(); 486 487 CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() { 488 @Override 489 public void onCameraAvailable(String cameraId) { 490 availableEventQueue.offer(cameraId); 491 } 492 493 @Override 494 public void onCameraUnavailable(String cameraId) { 495 unavailableEventQueue.offer(cameraId); 496 } 497 }; 498 499 mCameraManager.registerAvailabilityCallback(ac, mHandler); 500 String[] cameras = mCameraManager.getCameraIdList(); 501 502 if (cameras.length == 0) { 503 Log.i(TAG, "No cameras present, skipping test"); 504 return; 505 } 506 507 // Verify we received available for all cameras' initial state in a reasonable amount of time 508 HashSet<String> expectedAvailableCameras = new HashSet<String>(Arrays.asList(cameras)); 509 while (expectedAvailableCameras.size() > 0) { 510 String id = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 511 java.util.concurrent.TimeUnit.MILLISECONDS); 512 assertTrue("Did not receive initial availability notices for some cameras", 513 id != null); 514 expectedAvailableCameras.remove(id); 515 } 516 // Verify no unavailable cameras were reported 517 assertTrue("Some camera devices are initially unavailable", 518 unavailableEventQueue.size() == 0); 519 520 // Verify transitions for individual cameras 521 for (String id : cameras) { 522 MockStateCallback mockListener = MockStateCallback.mock(); 523 mCameraListener = new BlockingStateCallback(mockListener); 524 525 mCameraManager.openCamera(id, mCameraListener, mHandler); 526 527 // Block until opened 528 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 529 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 530 // Then verify only open happened, and get the camera handle 531 CameraDevice camera = verifyCameraStateOpened(id, mockListener); 532 533 // Verify that we see the expected 'unavailable' event. 534 String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 535 java.util.concurrent.TimeUnit.MILLISECONDS); 536 assertTrue(String.format("Received unavailability notice for wrong ID " + 537 "(expected %s, got %s)", id, candidateId), 538 id.equals(candidateId)); 539 assertTrue("Availability events received unexpectedly", 540 availableEventQueue.size() == 0); 541 542 // Verify that we see the expected 'available' event after closing the camera 543 544 camera.close(); 545 546 mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED, 547 CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS); 548 549 candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 550 java.util.concurrent.TimeUnit.MILLISECONDS); 551 assertTrue(String.format("Received availability notice for wrong ID " + 552 "(expected %s, got %s)", id, candidateId), 553 id.equals(candidateId)); 554 assertTrue("Unavailability events received unexpectedly", 555 unavailableEventQueue.size() == 0); 556 557 } 558 559 // Verify that we can unregister the listener and see no more events 560 assertTrue("Availability events received unexpectedly", 561 availableEventQueue.size() == 0); 562 assertTrue("Unavailability events received unexpectedly", 563 unavailableEventQueue.size() == 0); 564 565 mCameraManager.unregisterAvailabilityCallback(ac); 566 567 { 568 // Open an arbitrary camera and make sure we don't hear about it 569 570 MockStateCallback mockListener = MockStateCallback.mock(); 571 mCameraListener = new BlockingStateCallback(mockListener); 572 573 mCameraManager.openCamera(cameras[0], mCameraListener, mHandler); 574 575 // Block until opened 576 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 577 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 578 // Then verify only open happened, and close the camera 579 CameraDevice camera = verifyCameraStateOpened(cameras[0], mockListener); 580 581 camera.close(); 582 583 mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED, 584 CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS); 585 586 // No unavailability or availability callback should have occured 587 String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 588 java.util.concurrent.TimeUnit.MILLISECONDS); 589 assertTrue(String.format("Received unavailability notice for ID %s unexpectedly ", 590 candidateId), 591 candidateId == null); 592 593 candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 594 java.util.concurrent.TimeUnit.MILLISECONDS); 595 assertTrue(String.format("Received availability notice for ID %s unexpectedly ", 596 candidateId), 597 candidateId == null); 598 599 600 } 601 602 } // testCameraManagerListenerCallbacks 603 604 } 605