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 junit.framework.Assert.*; 20 21 import static org.junit.Assume.assumeFalse; 22 import static org.junit.Assume.assumeTrue; 23 import static org.mockito.Mockito.*; 24 25 import android.app.Instrumentation; 26 import android.app.NotificationManager; 27 import android.app.UiAutomation; 28 import android.content.pm.PackageManager; 29 import android.hardware.camera2.CameraAccessException; 30 import android.hardware.camera2.CameraCharacteristics; 31 import android.hardware.camera2.CameraDevice; 32 import android.hardware.camera2.CameraManager; 33 import android.hardware.camera2.cts.Camera2ParameterizedTestCase; 34 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor; 35 import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback; 36 import android.hardware.camera2.cts.helpers.CameraErrorCollector; 37 import android.os.Build; 38 import android.os.Handler; 39 import android.os.HandlerThread; 40 import android.os.ParcelFileDescriptor; 41 import android.platform.test.annotations.AppModeFull; 42 import android.util.Log; 43 import android.util.Pair; 44 45 import androidx.test.InstrumentationRegistry; 46 47 import com.android.compatibility.common.util.PropertyUtil; 48 import com.android.ex.camera2.blocking.BlockingStateCallback; 49 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.junit.runners.Parameterized; 53 import org.mockito.ArgumentCaptor; 54 55 import java.io.FileInputStream; 56 import java.io.IOException; 57 import java.io.InputStream; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.HashMap; 61 import java.util.HashSet; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.Set; 65 import java.util.concurrent.Executor; 66 import java.util.concurrent.LinkedBlockingQueue; 67 68 /** 69 * <p>Basic test for CameraManager class.</p> 70 */ 71 72 @RunWith(Parameterized.class) 73 public class CameraManagerTest extends Camera2ParameterizedTestCase { 74 private static final String TAG = "CameraManagerTest"; 75 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 76 private static final int NUM_CAMERA_REOPENS = 10; 77 private static final int AVAILABILITY_TIMEOUT_MS = 10; 78 79 private PackageManager mPackageManager; 80 private NoopCameraListener mListener; 81 private HandlerThread mHandlerThread; 82 private Handler mHandler; 83 private BlockingStateCallback mCameraListener; 84 private CameraErrorCollector mCollector; 85 private Set<Set<String>> mConcurrentCameraIdCombinations; 86 87 /** Load validation jni on initialization. */ 88 static { 89 System.loadLibrary("ctscamera2_jni"); 90 } 91 92 @Override setUp()93 public void setUp() throws Exception { 94 super.setUp(); 95 mPackageManager = mContext.getPackageManager(); 96 assertNotNull("Can't get package manager", mPackageManager); 97 mListener = new NoopCameraListener(); 98 99 /** 100 * Workaround for mockito and JB-MR2 incompatibility 101 * 102 * Avoid java.lang.IllegalArgumentException: dexcache == null 103 * https://code.google.com/p/dexmaker/issues/detail?id=2 104 */ 105 System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString()); 106 107 mCameraListener = spy(new BlockingStateCallback()); 108 109 mHandlerThread = new HandlerThread(TAG); 110 mHandlerThread.start(); 111 mHandler = new Handler(mHandlerThread.getLooper()); 112 mCollector = new CameraErrorCollector(); 113 mConcurrentCameraIdCombinations = 114 CameraTestUtils.getConcurrentCameraIds(mCameraManager, mAdoptShellPerm); 115 } 116 117 @Override tearDown()118 public void tearDown() throws Exception { 119 mHandlerThread.quitSafely(); 120 mHandler = null; 121 122 try { 123 mCollector.verify(); 124 } catch (Throwable e) { 125 // When new Exception(e) is used, exception info will be printed twice. 126 throw new Exception(e.getMessage()); 127 } finally { 128 super.tearDown(); 129 } 130 } 131 132 /** 133 * Verifies that the reason is in the range of public-only codes. 134 */ checkCameraAccessExceptionReason(CameraAccessException e)135 private static int checkCameraAccessExceptionReason(CameraAccessException e) { 136 int reason = e.getReason(); 137 138 switch (reason) { 139 case CameraAccessException.CAMERA_DISABLED: 140 case CameraAccessException.CAMERA_DISCONNECTED: 141 case CameraAccessException.CAMERA_ERROR: 142 case CameraAccessException.CAMERA_IN_USE: 143 case CameraAccessException.MAX_CAMERAS_IN_USE: 144 return reason; 145 } 146 147 fail("Invalid CameraAccessException code: " + reason); 148 149 return -1; // unreachable 150 } 151 152 @Test testCameraManagerGetDeviceIdList()153 public void testCameraManagerGetDeviceIdList() throws Exception { 154 String[] ids = getCameraIdsUnderTest(); 155 if (VERBOSE) Log.v(TAG, "CameraManager ids: " + Arrays.toString(ids)); 156 157 assumeFalse("Camera related features may not be accurate for system cameras, skipping", 158 mAdoptShellPerm); 159 assumeTrue( 160 "Camera related features may not be accurent when test is run with single " 161 + "camera under test specified by cameraId override", 162 mOverrideCameraId == null); 163 164 /** 165 * Test: that if there is at least one reported id, then the system must have 166 * the FEATURE_CAMERA_ANY feature. 167 */ 168 assertTrue("System camera feature and camera id list don't match", 169 ids.length == 0 || 170 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)); 171 172 /** 173 * Test: that if the device has front or rear facing cameras, then there 174 * must be matched system features. 175 */ 176 boolean externalCameraConnected = false; 177 String mainBackId = null, mainFrontId = null; 178 Map<String, Integer> lensFacingMap = new HashMap<String, Integer>(); 179 for (int i = 0; i < ids.length; i++) { 180 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); 181 assertNotNull("Can't get camera characteristics for camera " + ids[i], props); 182 Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING); 183 lensFacingMap.put(ids[i], lensFacing); 184 assertNotNull("Can't get lens facing info", lensFacing); 185 if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) { 186 assertTrue("System doesn't have front camera feature", 187 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)); 188 if (mainFrontId == null) { 189 mainFrontId = ids[i]; 190 } 191 } else if (lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 192 assertTrue("System doesn't have back camera feature", 193 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)); 194 if (mainBackId == null) { 195 mainBackId = ids[i]; 196 } 197 } else if (lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) { 198 externalCameraConnected = true; 199 assertTrue("System doesn't have external camera feature", 200 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)); 201 } else { 202 fail("Unknown camera lens facing " + lensFacing.toString()); 203 } 204 } 205 206 // Test an external camera is connected if FEATURE_CAMERA_EXTERNAL is advertised 207 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)) { 208 assertTrue("External camera is not connected on device with FEATURE_CAMERA_EXTERNAL", 209 externalCameraConnected); 210 } 211 212 /** 213 * Test: that if there is one camera device, then the system must have some 214 * specific features. 215 */ 216 assertTrue("Missing system feature: FEATURE_CAMERA_ANY", 217 ids.length == 0 218 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)); 219 assertTrue("Missing system feature: FEATURE_CAMERA, FEATURE_CAMERA_FRONT or FEATURE_CAMERA_EXTERNAL", 220 ids.length == 0 221 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) 222 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) 223 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)); 224 225 testConcurrentCameraFeature(mainFrontId, mainBackId); 226 } 227 228 /** 229 * Returns true if mConcurrentCameraIdCombinations has at least one combination containing both 230 * mainFrontId and mainBackId. 231 * Returns false otherwise. 232 */ containsMainFrontBackConcurrentCombination(String mainFrontId, String mainBackId)233 private boolean containsMainFrontBackConcurrentCombination(String mainFrontId, 234 String mainBackId) { 235 if (mainFrontId == null || mainBackId == null) { 236 return false; 237 } 238 boolean combinationFound = false; 239 240 // Go through all combinations and see that at least one combination has a main 241 // front + main back camera. 242 for (Set<String> cameraIdCombination : mConcurrentCameraIdCombinations) { 243 boolean frontFacingFound = false, backFacingFound = false; 244 for (String cameraId : cameraIdCombination) { 245 if (cameraId.equals(mainFrontId)) { 246 frontFacingFound = true; 247 } else if (cameraId.equals(mainBackId)) { 248 backFacingFound = true; 249 } 250 if (frontFacingFound && backFacingFound) { 251 combinationFound = true; 252 break; 253 } 254 } 255 if (combinationFound) { 256 break; 257 } 258 } 259 return combinationFound; 260 } 261 262 /** 263 * Test the consistency of the statement: If FEATURE_CAMERA_CONCURRENT is advertised, 264 * CameraManager.getConcurrentCameraIds() 265 * returns a combination which contains the main front id and main back id, and vice versa. 266 */ testConcurrentCameraFeature(String mainFrontId, String mainBackId)267 private void testConcurrentCameraFeature(String mainFrontId, String mainBackId) 268 throws Exception { 269 boolean frontBackFeatureAdvertised = 270 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_CONCURRENT); 271 if (frontBackFeatureAdvertised) { 272 assertTrue("FEATURE_CAMERA_CONCURRENT advertised but main front id is null", 273 mainFrontId != null); 274 assertTrue("FEATURE_CAMERA_CONCURRENT advertised but main back id is null", 275 mainBackId != null); 276 } 277 278 boolean concurrentMainFrontBackCombinationFound = 279 containsMainFrontBackConcurrentCombination(mainFrontId, mainBackId); 280 281 String[] ids = getCameraIdsUnderTest(); 282 if (ids.length > 0) { 283 assertTrue("System camera feature FEATURE_CAMERA_CONCURRENT = " 284 + frontBackFeatureAdvertised 285 + " and device actually having a main front back combination which can operate " 286 + "concurrently = " + concurrentMainFrontBackCombinationFound 287 + " do not match", 288 frontBackFeatureAdvertised == concurrentMainFrontBackCombinationFound); 289 } 290 } 291 292 // Test: that properties can be queried from each device, without exceptions. 293 @Test testCameraManagerGetCameraCharacteristics()294 public void testCameraManagerGetCameraCharacteristics() throws Exception { 295 String[] ids = getCameraIdsUnderTest(); 296 for (int i = 0; i < ids.length; i++) { 297 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); 298 assertNotNull( 299 String.format("Can't get camera characteristics from: ID %s", ids[i]), props); 300 } 301 } 302 303 // Test: that properties queried between the Java SDK and the C++ NDK are equivalent. 304 @Test testCameraCharacteristicsNdkFromSdk()305 public void testCameraCharacteristicsNdkFromSdk() throws Exception { 306 String[] ids = getCameraIdsUnderTest(); 307 for (int i = 0; i < ids.length; i++) { 308 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); 309 Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING); 310 assertNotNull("Can't get lens facing info", lensFacing); 311 312 assertTrue(validateACameraMetadataFromCameraMetadataCriticalTagsNative( 313 props, lensFacing.intValue())); 314 } 315 } 316 317 // Returns true if `props` has lens facing `lensFacing` when queried from the NDK via 318 // ACameraMetadata_fromCameraMetadata(). validateACameraMetadataFromCameraMetadataCriticalTagsNative( CameraCharacteristics props, int lensFacing)319 private static native boolean validateACameraMetadataFromCameraMetadataCriticalTagsNative( 320 CameraCharacteristics props, int lensFacing); 321 322 // Test: that an exception is thrown if an invalid device id is passed down. 323 @Test testCameraManagerInvalidDevice()324 public void testCameraManagerInvalidDevice() throws Exception { 325 String[] ids = getCameraIdsUnderTest(); 326 // Create an invalid id by concatenating all the valid ids together. 327 StringBuilder invalidId = new StringBuilder(); 328 invalidId.append("INVALID"); 329 for (int i = 0; i < ids.length; i++) { 330 invalidId.append(ids[i]); 331 } 332 333 try { 334 mCameraManager.getCameraCharacteristics( 335 invalidId.toString()); 336 fail(String.format("Accepted invalid camera ID: %s", invalidId.toString())); 337 } catch (IllegalArgumentException e) { 338 // This is the exception that should be thrown in this case. 339 } 340 } 341 342 // Test: that each camera device can be opened one at a time, several times. 343 @Test testCameraManagerOpenCamerasSerially()344 public void testCameraManagerOpenCamerasSerially() throws Exception { 345 testCameraManagerOpenCamerasSerially(/*useExecutor*/ false); 346 testCameraManagerOpenCamerasSerially(/*useExecutor*/ true); 347 } 348 testCameraManagerOpenCamerasSerially(boolean useExecutor)349 private void testCameraManagerOpenCamerasSerially(boolean useExecutor) throws Exception { 350 final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null; 351 String[] ids = getCameraIdsUnderTest(); 352 for (int i = 0; i < ids.length; i++) { 353 for (int j = 0; j < NUM_CAMERA_REOPENS; j++) { 354 CameraDevice camera = null; 355 try { 356 MockStateCallback mockListener = MockStateCallback.mock(); 357 mCameraListener = new BlockingStateCallback(mockListener); 358 359 if (useExecutor) { 360 mCameraManager.openCamera(ids[i], executor, mCameraListener); 361 } else { 362 mCameraManager.openCamera(ids[i], mCameraListener, mHandler); 363 } 364 365 // Block until unConfigured 366 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 367 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 368 369 // Ensure state transitions are in right order: 370 // -- 1) Opened 371 // Ensure no other state transitions have occurred: 372 camera = CameraTestUtils.verifyCameraStateOpened(ids[i], mockListener); 373 } finally { 374 if (camera != null) { 375 camera.close(); 376 } 377 } 378 } 379 } 380 } 381 382 /** 383 * Test: one or more camera devices can be open at the same time, or the right error state 384 * is set if this can't be done. 385 */ 386 @Test testCameraManagerOpenAllCameras()387 public void testCameraManagerOpenAllCameras() throws Exception { 388 testCameraManagerOpenAllCameras(/*useExecutor*/ false); 389 testCameraManagerOpenAllCameras(/*useExecutor*/ true); 390 } 391 testCameraManagerOpenAllCameras(boolean useExecutor)392 private void testCameraManagerOpenAllCameras(boolean useExecutor) throws Exception { 393 String[] ids = getCameraIdsUnderTest(); 394 assertNotNull("Camera ids shouldn't be null", ids); 395 396 // Skip test if the device doesn't have multiple cameras. 397 if (ids.length <= 1) { 398 return; 399 } 400 401 final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null; 402 List<CameraDevice> cameraList = new ArrayList<CameraDevice>(); 403 List<MockStateCallback> listenerList = new ArrayList<MockStateCallback>(); 404 List<BlockingStateCallback> blockingListenerList = new ArrayList<BlockingStateCallback>(); 405 try { 406 for (int i = 0; i < ids.length; i++) { 407 // Ignore state changes from other cameras 408 MockStateCallback mockListener = MockStateCallback.mock(); 409 mCameraListener = new BlockingStateCallback(mockListener); 410 411 /** 412 * Track whether or not we got a synchronous error from openCamera. 413 * 414 * A synchronous error must also be accompanied by an asynchronous 415 * StateCallback#onError callback. 416 */ 417 boolean expectingError = false; 418 419 String cameraId = ids[i]; 420 try { 421 if (useExecutor) { 422 mCameraManager.openCamera(cameraId, executor, mCameraListener); 423 } else { 424 mCameraManager.openCamera(cameraId, mCameraListener, mHandler); 425 } 426 } catch (CameraAccessException e) { 427 int reason = checkCameraAccessExceptionReason(e); 428 if (reason == CameraAccessException.CAMERA_DISCONNECTED || 429 reason == CameraAccessException.CAMERA_DISABLED) { 430 // TODO: We should handle a Disabled camera by passing here and elsewhere 431 fail("Camera must not be disconnected or disabled for this test" + ids[i]); 432 } else { 433 expectingError = true; 434 } 435 } 436 437 List<Integer> expectedStates = new ArrayList<Integer>(); 438 expectedStates.add(BlockingStateCallback.STATE_OPENED); 439 expectedStates.add(BlockingStateCallback.STATE_ERROR); 440 int state = mCameraListener.waitForAnyOfStates( 441 expectedStates, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 442 443 // It's possible that we got an asynchronous error transition only. This is ok. 444 if (expectingError) { 445 assertEquals("Throwing a CAMERA_ERROR exception must be accompanied with a " + 446 "StateCallback#onError callback", 447 BlockingStateCallback.STATE_ERROR, state); 448 } 449 450 /** 451 * Two situations are considered passing: 452 * 1) The camera opened successfully. 453 * => No error must be set. 454 * 2) The camera did not open because there were too many other cameras opened. 455 * => Only MAX_CAMERAS_IN_USE error must be set. 456 * 457 * Any other situation is considered a failure. 458 * 459 * For simplicity we treat disconnecting asynchronously as a failure, so 460 * camera devices should not be physically unplugged during this test. 461 */ 462 463 CameraDevice camera; 464 if (state == BlockingStateCallback.STATE_ERROR) { 465 // Camera did not open because too many other cameras were opened 466 // => onError called exactly once with a non-null camera 467 assertTrue("At least one camera must be opened successfully", 468 cameraList.size() > 0); 469 470 ArgumentCaptor<CameraDevice> argument = 471 ArgumentCaptor.forClass(CameraDevice.class); 472 473 verify(mockListener) 474 .onError( 475 argument.capture(), 476 eq(CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE) 477 ); 478 verifyNoMoreInteractions(mockListener); 479 480 camera = argument.getValue(); 481 assertNotNull("Expected a non-null camera for the error transition for ID: " 482 + ids[i], camera); 483 } else if (state == BlockingStateCallback.STATE_OPENED) { 484 // Camera opened successfully. 485 // => onOpened called exactly once 486 camera = CameraTestUtils.verifyCameraStateOpened(cameraId, 487 mockListener); 488 } else { 489 fail("Unexpected state " + state); 490 camera = null; // unreachable. but need this for java compiler 491 } 492 493 // Keep track of cameras so we can close it later 494 cameraList.add(camera); 495 listenerList.add(mockListener); 496 blockingListenerList.add(mCameraListener); 497 } 498 } finally { 499 for (int i = 0; i < cameraList.size(); i++) { 500 // With conflicting devices, opening of one camera could result in the other camera 501 // being disconnected. To handle such case, reset the mock before close. 502 reset(listenerList.get(i)); 503 cameraList.get(i).close(); 504 } 505 for (BlockingStateCallback blockingListener : blockingListenerList) { 506 blockingListener.waitForState( 507 BlockingStateCallback.STATE_CLOSED, 508 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 509 } 510 } 511 512 /* 513 * Ensure that no state transitions have bled through from one camera to another 514 * after closing the cameras. 515 */ 516 int i = 0; 517 for (MockStateCallback listener : listenerList) { 518 CameraDevice camera = cameraList.get(i); 519 520 verify(listener).onClosed(eq(camera)); 521 verifyNoMoreInteractions(listener); 522 i++; 523 // Only a #close can happen on the camera since we were done with it. 524 // Also nothing else should've happened between the close and the open. 525 } 526 } 527 528 /** 529 * Test: that opening the same device multiple times and make sure the right 530 * error state is set. 531 */ 532 @Test testCameraManagerOpenCameraTwice()533 public void testCameraManagerOpenCameraTwice() throws Exception { 534 testCameraManagerOpenCameraTwice(/*useExecutor*/ false); 535 testCameraManagerOpenCameraTwice(/*useExecutor*/ true); 536 } 537 testCameraManagerOpenCameraTwice(boolean useExecutor)538 private void testCameraManagerOpenCameraTwice(boolean useExecutor) throws Exception { 539 String[] ids = getCameraIdsUnderTest(); 540 final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null; 541 542 // Test across every camera device. 543 for (int i = 0; i < ids.length; ++i) { 544 CameraDevice successCamera = null; 545 mCollector.setCameraId(ids[i]); 546 547 try { 548 MockStateCallback mockSuccessListener = MockStateCallback.mock(); 549 MockStateCallback mockFailListener = MockStateCallback.mock(); 550 551 BlockingStateCallback successListener = 552 new BlockingStateCallback(mockSuccessListener); 553 BlockingStateCallback failListener = 554 new BlockingStateCallback(mockFailListener); 555 556 if (useExecutor) { 557 mCameraManager.openCamera(ids[i], executor, successListener); 558 mCameraManager.openCamera(ids[i], executor, failListener); 559 } else { 560 mCameraManager.openCamera(ids[i], successListener, mHandler); 561 mCameraManager.openCamera(ids[i], failListener, mHandler); 562 } 563 564 successListener.waitForState(BlockingStateCallback.STATE_OPENED, 565 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 566 ArgumentCaptor<CameraDevice> argument = 567 ArgumentCaptor.forClass(CameraDevice.class); 568 verify(mockSuccessListener, atLeastOnce()).onOpened(argument.capture()); 569 verify(mockSuccessListener, atLeastOnce()).onDisconnected(argument.capture()); 570 571 failListener.waitForState(BlockingStateCallback.STATE_OPENED, 572 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 573 verify(mockFailListener, atLeastOnce()).onOpened(argument.capture()); 574 575 successCamera = CameraTestUtils.verifyCameraStateOpened( 576 ids[i], mockFailListener); 577 578 verifyNoMoreInteractions(mockFailListener); 579 } finally { 580 if (successCamera != null) { 581 successCamera.close(); 582 } 583 } 584 } 585 } 586 587 private class NoopCameraListener extends CameraManager.AvailabilityCallback { 588 @Override onCameraAvailable(String cameraId)589 public void onCameraAvailable(String cameraId) { 590 // No-op 591 } 592 593 @Override onCameraUnavailable(String cameraId)594 public void onCameraUnavailable(String cameraId) { 595 // No-op 596 } 597 } 598 599 /** 600 * Test: that the APIs to register and unregister a listener run successfully; 601 * doesn't test that the listener actually gets invoked at the right time. 602 * Registering a listener multiple times should have no effect, and unregistering 603 * a listener that isn't registered should have no effect. 604 */ 605 @Test testCameraManagerListener()606 public void testCameraManagerListener() throws Exception { 607 mCameraManager.unregisterAvailabilityCallback(mListener); 608 // Test Handler API 609 mCameraManager.registerAvailabilityCallback(mListener, mHandler); 610 mCameraManager.registerAvailabilityCallback(mListener, mHandler); 611 mCameraManager.unregisterAvailabilityCallback(mListener); 612 mCameraManager.unregisterAvailabilityCallback(mListener); 613 // Test Executor API 614 Executor executor = new HandlerExecutor(mHandler); 615 mCameraManager.registerAvailabilityCallback(executor, mListener); 616 mCameraManager.registerAvailabilityCallback(executor, mListener); 617 mCameraManager.unregisterAvailabilityCallback(mListener); 618 mCameraManager.unregisterAvailabilityCallback(mListener); 619 } 620 621 /** 622 * Test that the availability callbacks fire when expected 623 */ 624 @Test testCameraManagerListenerCallbacks()625 public void testCameraManagerListenerCallbacks() throws Exception { 626 if (mOverrideCameraId != null) { 627 // Testing is done for individual camera. Skip. 628 return; 629 } 630 testCameraManagerListenerCallbacks(/*useExecutor*/ false); 631 testCameraManagerListenerCallbacks(/*useExecutor*/ true); 632 } 633 634 testCameraManagerListenerCallbacks(boolean useExecutor)635 private void testCameraManagerListenerCallbacks(boolean useExecutor) throws Exception { 636 637 final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>(); 638 final LinkedBlockingQueue<String> unavailableEventQueue = new LinkedBlockingQueue<>(); 639 final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null; 640 641 final LinkedBlockingQueue<Pair<String, String>> availablePhysicalCamEventQueue = 642 new LinkedBlockingQueue<>(); 643 final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue = 644 new LinkedBlockingQueue<>(); 645 646 final LinkedBlockingQueue<String> onCameraOpenedEventQueue = new LinkedBlockingQueue<>(); 647 final LinkedBlockingQueue<String> onCameraClosedEventQueue = new LinkedBlockingQueue<>(); 648 649 CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() { 650 @Override 651 public void onCameraAvailable(String cameraId) { 652 // We allow this callback irrespective of mAdoptShellPerm since for this particular 653 // test, in the case when shell permissions are adopted we test all cameras, for 654 // simplicity. This is since when mAdoptShellPerm is false, we can't test for 655 // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions). 656 // So, to test all cameras, we test them when we adopt shell permission identity. 657 super.onCameraAvailable(cameraId); 658 availableEventQueue.offer(cameraId); 659 } 660 661 @Override 662 public void onCameraUnavailable(String cameraId) { 663 super.onCameraUnavailable(cameraId); 664 unavailableEventQueue.offer(cameraId); 665 } 666 667 @Override 668 public void onPhysicalCameraAvailable(String cameraId, String physicalCameraId) { 669 super.onPhysicalCameraAvailable(cameraId, physicalCameraId); 670 availablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId)); 671 } 672 673 @Override 674 public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) { 675 super.onPhysicalCameraUnavailable(cameraId, physicalCameraId); 676 unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId)); 677 } 678 679 @Override 680 public void onCameraOpened(String cameraId, String packageId) { 681 super.onCameraOpened(cameraId, packageId); 682 String curPackageId = mContext.getPackageName(); 683 assertTrue("Opening package should be " + curPackageId + ", was " + packageId, 684 curPackageId.equals(packageId)); 685 onCameraOpenedEventQueue.offer(cameraId); 686 } 687 688 @Override 689 public void onCameraClosed(String cameraId) { 690 super.onCameraClosed(cameraId); 691 onCameraClosedEventQueue.offer(cameraId); 692 } 693 694 }; 695 696 if (useExecutor) { 697 mCameraManager.registerAvailabilityCallback(executor, ac); 698 } else { 699 mCameraManager.registerAvailabilityCallback(ac, mHandler); 700 } 701 String[] cameras = getCameraIdsUnderTest(); 702 if (mAdoptShellPerm) { 703 //when mAdoptShellPerm is false, we can't test for 704 // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions). 705 // So, to test all cameras, we test them when we adopt shell permission identity. 706 cameras = mCameraManager.getCameraIdListNoLazy(); 707 } 708 709 if (cameras.length == 0) { 710 Log.i(TAG, "No cameras present, skipping test mAdoprPerm"); 711 return; 712 } 713 714 // Verify we received available for all cameras' initial state in a reasonable amount of time 715 HashSet<String> expectedAvailableCameras = new HashSet<String>(Arrays.asList(cameras)); 716 CameraTestUtils.verifyAvailabilityCbsReceived(expectedAvailableCameras, availableEventQueue, 717 unavailableEventQueue, true /*available*/); 718 719 // Clear physical camera callback queue in case the initial state of certain physical 720 // cameras are unavailable. 721 unavailablePhysicalCamEventQueue.clear(); 722 723 // Verify transitions for individual cameras 724 for (String id : cameras) { 725 MockStateCallback mockListener = MockStateCallback.mock(); 726 mCameraListener = new BlockingStateCallback(mockListener); 727 728 if (useExecutor) { 729 mCameraManager.openCamera(id, executor, mCameraListener); 730 } else { 731 mCameraManager.openCamera(id, mCameraListener, mHandler); 732 } 733 734 // Block until opened 735 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 736 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 737 // Then verify only open happened, and get the camera handle 738 CameraDevice camera = CameraTestUtils.verifyCameraStateOpened(id, mockListener); 739 740 CameraTestUtils.verifySingleAvailabilityCbsReceived(unavailableEventQueue, 741 availableEventQueue, id, "unavailability", "Availability"); 742 if (mAdoptShellPerm) { 743 // Verify that we see the expected 'onCameraOpened' event. 744 CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue, 745 onCameraClosedEventQueue, id, "onCameraOpened", "onCameraClosed"); 746 } 747 748 // Verify that we see the expected 'unavailable' events if this camera is a physical 749 // camera of another logical multi-camera 750 HashSet<Pair<String, String>> relatedLogicalCameras = new HashSet<>(); 751 for (String multiCamId : cameras) { 752 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(multiCamId); 753 Set<String> physicalIds = props.getPhysicalCameraIds(); 754 if (physicalIds.contains(id)) { 755 relatedLogicalCameras.add(new Pair<String, String>(multiCamId, id)); 756 } 757 } 758 759 HashSet<Pair<String, String>> expectedLogicalCameras = 760 new HashSet<>(relatedLogicalCameras); 761 CameraTestUtils.verifyAvailabilityCbsReceived(expectedLogicalCameras, 762 unavailablePhysicalCamEventQueue, availablePhysicalCamEventQueue, 763 false /*available*/); 764 765 // Verify that we see the expected 'available' event after closing the camera 766 767 camera.close(); 768 mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED, 769 CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS); 770 771 CameraTestUtils.verifySingleAvailabilityCbsReceived(availableEventQueue, 772 unavailableEventQueue, id, "availability", "Unavailability"); 773 774 if (mAdoptShellPerm) { 775 CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue, 776 onCameraOpenedEventQueue, id, "onCameraClosed", "onCameraOpened"); 777 } 778 779 expectedLogicalCameras = new HashSet<Pair<String, String>>(relatedLogicalCameras); 780 CameraTestUtils.verifyAvailabilityCbsReceived(expectedLogicalCameras, 781 availablePhysicalCamEventQueue, 782 null /*unExpectedEventQueue*/, 783 true /*available*/); 784 785 // Clear physical camera callback queue in case the initial state of certain physical 786 // cameras are unavailable. 787 unavailablePhysicalCamEventQueue.clear(); 788 } 789 790 // Verify that we can unregister the listener and see no more events 791 assertTrue("Availability events received unexpectedly", 792 availableEventQueue.size() == 0); 793 assertTrue("Unavailability events received unexpectedly", 794 unavailableEventQueue.size() == 0); 795 796 mCameraManager.unregisterAvailabilityCallback(ac); 797 798 { 799 // Open an arbitrary camera and make sure we don't hear about it 800 801 MockStateCallback mockListener = MockStateCallback.mock(); 802 mCameraListener = new BlockingStateCallback(mockListener); 803 804 if (useExecutor) { 805 mCameraManager.openCamera(cameras[0], executor, mCameraListener); 806 } else { 807 mCameraManager.openCamera(cameras[0], mCameraListener, mHandler); 808 } 809 810 // Block until opened 811 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 812 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 813 // Then verify only open happened, and close the camera 814 CameraDevice camera = CameraTestUtils.verifyCameraStateOpened(cameras[0], mockListener); 815 816 camera.close(); 817 818 mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED, 819 CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS); 820 821 // No unavailability or availability callback should have occured 822 String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 823 java.util.concurrent.TimeUnit.MILLISECONDS); 824 assertTrue(String.format("Received unavailability notice for ID %s unexpectedly ", 825 candidateId), 826 candidateId == null); 827 828 candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS, 829 java.util.concurrent.TimeUnit.MILLISECONDS); 830 assertTrue(String.format("Received availability notice for ID %s unexpectedly ", 831 candidateId), 832 candidateId == null); 833 834 Pair<String, String> candidatePhysicalIds = unavailablePhysicalCamEventQueue.poll( 835 AVAILABILITY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS); 836 assertTrue("Received unavailability physical camera notice unexpectedly ", 837 candidatePhysicalIds == null); 838 839 candidatePhysicalIds = availablePhysicalCamEventQueue.poll( 840 AVAILABILITY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS); 841 assertTrue("Received availability notice for physical camera unexpectedly ", 842 candidatePhysicalIds == null); 843 } 844 845 if (mAdoptShellPerm) { 846 // Open an arbitrary camera and make sure subsequently subscribed listener receives 847 // correct onCameraOpened/onCameraClosed callbacks 848 849 MockStateCallback mockListener = MockStateCallback.mock(); 850 mCameraListener = new BlockingStateCallback(mockListener); 851 852 if (useExecutor) { 853 mCameraManager.openCamera(cameras[0], executor, mCameraListener); 854 } else { 855 mCameraManager.openCamera(cameras[0], mCameraListener, mHandler); 856 } 857 858 // Block until opened 859 mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED, 860 CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS); 861 // Then verify only open happened, and close the camera 862 CameraDevice camera = CameraTestUtils.verifyCameraStateOpened(cameras[0], mockListener); 863 864 if (useExecutor) { 865 mCameraManager.registerAvailabilityCallback(executor, ac); 866 } else { 867 mCameraManager.registerAvailabilityCallback(ac, mHandler); 868 } 869 870 // Verify that we see the expected 'onCameraOpened' event. 871 CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue, 872 onCameraClosedEventQueue, cameras[0], "onCameraOpened", "onCameraClosed"); 873 874 camera.close(); 875 876 mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED, 877 CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS); 878 879 CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue, 880 onCameraOpenedEventQueue, cameras[0], "onCameraClosed", "onCameraOpened"); 881 882 mCameraManager.unregisterAvailabilityCallback(ac); 883 } 884 } // testCameraManagerListenerCallbacks 885 886 /** 887 * Test that the physical camera available/unavailable callback behavior is consistent 888 * between: 889 * 890 * - No camera is open, 891 * - After camera is opened, and 892 * - After camera is closed, 893 */ 894 @Test testPhysicalCameraAvailabilityConsistency()895 public void testPhysicalCameraAvailabilityConsistency() throws Throwable { 896 CameraTestUtils.testPhysicalCameraAvailabilityConsistencyHelper(getCameraIdsUnderTest(), 897 mCameraManager, mHandler, true /*expectInitialCallbackAfterOpen*/); 898 } 899 900 // Verify no LEGACY-level devices appear on devices first launched in the Q release or newer 901 @Test 902 @AppModeFull(reason = "Instant apps can't access Test API") testNoLegacyOnQ()903 public void testNoLegacyOnQ() throws Exception { 904 if(PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q){ 905 // LEGACY still allowed for devices upgrading to Q 906 return; 907 } 908 String[] ids = getCameraIdsUnderTest(); 909 for (int i = 0; i < ids.length; i++) { 910 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]); 911 assertNotNull( 912 String.format("Can't get camera characteristics from: ID %s", ids[i]), props); 913 Integer hardwareLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); 914 assertNotNull( 915 String.format("Can't get hardware level from: ID %s", ids[i]), hardwareLevel); 916 assertTrue(String.format( 917 "Camera device %s cannot be LEGACY level for devices launching on Q", 918 ids[i]), 919 hardwareLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY); 920 } 921 } 922 923 @Test testCameraManagerWithDnD()924 public void testCameraManagerWithDnD() throws Exception { 925 String[] cameras = getCameraIdsUnderTest(); 926 if (cameras.length == 0) { 927 Log.i(TAG, "No cameras present, skipping test"); 928 return; 929 } 930 // Allow the test package to adjust notification policy 931 toggleNotificationPolicyAccess(mContext.getPackageName(), 932 InstrumentationRegistry.getInstrumentation(), true); 933 934 // Enable DnD filtering 935 936 NotificationManager nm = mContext.getSystemService(NotificationManager.class); 937 try { 938 nm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE); 939 940 // Try to use the camera API 941 942 for (String cameraId : cameras) { 943 try { 944 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId); 945 assertTrue("Unable to get camera characteristics when DnD is enabled", 946 c != null); 947 } catch (RuntimeException e) { 948 fail("RuntimeException thrown when attempting to access camera " + 949 "characteristics with DnD enabled. " + 950 "https://android-review.googlesource.com/c/platform/frameworks/base/+" + 951 "/747089/ may be missing."); 952 } 953 } 954 } finally { 955 // Restore notifications to normal 956 957 nm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 958 } 959 } 960 961 @Test testCameraManagerAutomotiveCameras()962 public void testCameraManagerAutomotiveCameras() throws Exception { 963 if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 964 // Execute this test only on the automotive device implementations 965 Log.i(TAG, "Skips this test on non automotive device implementations"); 966 return; 967 } 968 969 String[] cameraIds = getCameraIdsUnderTest(); 970 if (cameraIds.length < 1) { 971 Log.i(TAG, "No cameras present, skipping test"); 972 return; 973 } 974 975 /** 976 * On automotive device implementations, all cameras must have android.automotive.location 977 * and android.automotive.lens.facing in their static metadata. Also, 978 * android.lens.poseTranslation and android.lens.poseRotation must present in a camera's 979 * static metadata, and android.lens.poseReference should be set as 980 * LENS_POSE_REFERENCE_AUTOMOTIVE in following conditions. 981 * 982 * - android.automotive.location has AUTOMOTIVE_LOCATION_EXTERIOR_OTHER or 983 * AUTOMOTIVE_LOCATION_EXTRA_OTHER 984 * - android.automotive.lens.facing has AUTOMOTIVE_LENS_FACING_EXTERIOR_OTHER or 985 * AUTOMOTIVE_LENS_FACING_INTERIOR_OTHER 986 * - One or more camera has the same android.automotive.location and 987 * android.automotive.lens.facing values 988 */ 989 Map<Pair<Integer, Integer>, ArrayList<String>> cameraGroup = new HashMap<>(); 990 for (String cameraId : cameraIds) { 991 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId); 992 assertNotNull( 993 String.format("Can't get camera characteristics from: ID %s", cameraId), props); 994 995 Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING); 996 if (lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) { 997 // Automotive device implementations may have external cameras but they are exempted 998 // from this test case. 999 continue; 1000 } 1001 1002 Integer cameraLocation = props.get(CameraCharacteristics.AUTOMOTIVE_LOCATION); 1003 assertNotNull( 1004 String.format("Can't get a camera location from: ID %s", cameraId), 1005 cameraLocation); 1006 1007 int[] automotiveLensFacing = props.get(CameraCharacteristics.AUTOMOTIVE_LENS_FACING); 1008 assertNotNull( 1009 String.format("Can't get a lens facing direction from: ID %s", cameraId), 1010 automotiveLensFacing); 1011 1012 if (cameraLocation == CameraCharacteristics.AUTOMOTIVE_LOCATION_EXTERIOR_OTHER || 1013 cameraLocation == CameraCharacteristics.AUTOMOTIVE_LOCATION_EXTRA_OTHER || 1014 automotiveLensFacing[0] == 1015 CameraCharacteristics.AUTOMOTIVE_LENS_FACING_EXTERIOR_OTHER || 1016 automotiveLensFacing[0] == 1017 CameraCharacteristics.AUTOMOTIVE_LENS_FACING_INTERIOR_OTHER) { 1018 checkAutomotiveLensPoseCharacteristics(cameraId, props); 1019 } else { 1020 Pair<Integer, Integer> key = new Pair<>(cameraLocation, automotiveLensFacing[0]); 1021 if (cameraGroup.containsKey(key)) { 1022 cameraGroup.get(key).add(cameraId); 1023 } else { 1024 cameraGroup.put(key, new ArrayList<>(Arrays.asList(cameraId))); 1025 } 1026 } 1027 } 1028 1029 for (Map.Entry<Pair<Integer, Integer>, ArrayList<String>> entry : cameraGroup.entrySet()) { 1030 ArrayList<String> cameraIdsToVerify = entry.getValue(); 1031 if (cameraIdsToVerify.size() > 1) { 1032 for (String id : cameraIdsToVerify) { 1033 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(id); 1034 checkAutomotiveLensPoseCharacteristics(id, props); 1035 } 1036 } 1037 } 1038 } 1039 checkAutomotiveLensPoseCharacteristics(String cameraId, CameraCharacteristics props)1040 private void checkAutomotiveLensPoseCharacteristics(String cameraId, 1041 CameraCharacteristics props) { 1042 Integer reference = props.get(CameraCharacteristics.LENS_POSE_REFERENCE); 1043 assertNotNull( 1044 String.format("Can't get a lens pose reference from: ID %s", cameraId), 1045 reference); 1046 assertTrue("Lens pose reference must be AUTOMOTIVE", 1047 reference == CameraCharacteristics.LENS_POSE_REFERENCE_AUTOMOTIVE); 1048 float[] translation = props.get(CameraCharacteristics.LENS_POSE_TRANSLATION); 1049 assertNotNull( 1050 String.format("Can't get a lens pose translation from: ID %s", cameraId), 1051 translation); 1052 float[] rotation = props.get(CameraCharacteristics.LENS_POSE_ROTATION); 1053 assertNotNull( 1054 String.format("Can't get a lens pose rotation from: ID %s", cameraId), 1055 rotation); 1056 } 1057 1058 toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)1059 private void toggleNotificationPolicyAccess(String packageName, 1060 Instrumentation instrumentation, boolean on) throws IOException { 1061 1062 String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName; 1063 1064 runCommand(command, instrumentation); 1065 1066 NotificationManager nm = mContext.getSystemService(NotificationManager.class); 1067 assertEquals("Notification Policy Access Grant is " + 1068 nm.isNotificationPolicyAccessGranted() + " not " + on, on, 1069 nm.isNotificationPolicyAccessGranted()); 1070 } 1071 runCommand(String command, Instrumentation instrumentation)1072 private void runCommand(String command, Instrumentation instrumentation) throws IOException { 1073 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 1074 // Execute command 1075 ParcelFileDescriptor fd = mUiAutomation.executeShellCommand(command); 1076 assertNotNull("Failed to execute shell command: " + command, fd); 1077 // Wait for the command to finish by reading until EOF 1078 try (InputStream in = new FileInputStream(fd.getFileDescriptor())) { 1079 byte[] buffer = new byte[4096]; 1080 while (in.read(buffer) > 0) {} 1081 } catch (IOException e) { 1082 throw new IOException("Could not read stdout of command: " + command, e); 1083 } 1084 } 1085 1086 } 1087