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