1 /* 2 * Copyright (C) 2015 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.multiprocess.camera.cts; 18 19 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 20 21 import static org.mockito.Mockito.*; 22 23 import android.app.Activity; 24 import android.app.ActivityManager; 25 import android.app.ActivityTaskManager; 26 import android.app.UiAutomation; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.graphics.Rect; 31 import android.hardware.Camera; 32 import android.hardware.camera2.CameraAccessException; 33 import android.hardware.camera2.CameraDevice; 34 import android.hardware.camera2.CameraManager; 35 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor; 36 import android.hardware.cts.CameraCtsActivity; 37 import android.os.Handler; 38 import android.os.SystemClock; 39 import android.platform.test.annotations.AppModeFull; 40 import android.server.wm.NestedShellPermission; 41 import android.server.wm.TestTaskOrganizer; 42 import android.server.wm.WindowManagerStateHelper; 43 import android.test.ActivityInstrumentationTestCase2; 44 import android.util.ArrayMap; 45 import android.util.ArraySet; 46 import android.util.Log; 47 import android.view.InputDevice; 48 import android.view.MotionEvent; 49 50 import androidx.test.InstrumentationRegistry; 51 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.Set; 58 import java.util.concurrent.Executor; 59 import java.util.concurrent.TimeoutException; 60 61 /** 62 * Tests for multi-process camera usage behavior. 63 */ 64 public class CameraEvictionTest extends ActivityInstrumentationTestCase2<CameraCtsActivity> { 65 66 public static final String TAG = "CameraEvictionTest"; 67 68 private static final int OPEN_TIMEOUT = 2000; // Timeout for camera to open (ms). 69 private static final int SETUP_TIMEOUT = 5000; // Remote camera setup timeout (ms). 70 private static final int EVICTION_TIMEOUT = 1000; // Remote camera eviction timeout (ms). 71 private static final int WAIT_TIME = 2000; // Time to wait for process to launch (ms). 72 private static final int UI_TIMEOUT = 10000; // Time to wait for UI event before timeout (ms). 73 // Time to wait for onCameraAccessPrioritiesChanged (ms). 74 private static final int CAMERA_ACCESS_TIMEOUT = 2000; 75 76 // CACHED_APP_MAX_ADJ - FG oom score 77 private static final int CACHED_APP_VS_FG_OOM_DELTA = 999; 78 ErrorLoggingService.ErrorServiceConnection mErrorServiceConnection; 79 80 private ActivityManager mActivityManager; 81 private Context mContext; 82 private Camera mCamera; 83 private CameraDevice mCameraDevice; 84 private UiAutomation mUiAutomation; 85 private final Object mLock = new Object(); 86 private boolean mCompleted = false; 87 private int mProcessPid = -1; 88 private WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); 89 private TestTaskOrganizer mTaskOrganizer; 90 91 /** Load jni on initialization */ 92 static { 93 System.loadLibrary("ctscamera2_jni"); 94 } 95 initializeAvailabilityCallbacksNative()96 private static native long initializeAvailabilityCallbacksNative(); getAccessCallbacksCountAndResetNative(long context)97 private static native int getAccessCallbacksCountAndResetNative(long context); releaseAvailabilityCallbacksNative(long context)98 private static native long releaseAvailabilityCallbacksNative(long context); 99 CameraEvictionTest()100 public CameraEvictionTest() { 101 super(CameraCtsActivity.class); 102 } 103 104 public static class StateCallbackImpl extends CameraDevice.StateCallback { 105 CameraDevice mCameraDevice; 106 StateCallbackImpl()107 public StateCallbackImpl() { 108 super(); 109 } 110 111 @Override onOpened(CameraDevice cameraDevice)112 public void onOpened(CameraDevice cameraDevice) { 113 synchronized(this) { 114 mCameraDevice = cameraDevice; 115 } 116 Log.i(TAG, "CameraDevice onOpened called for main CTS test process."); 117 } 118 119 @Override onClosed(CameraDevice camera)120 public void onClosed(CameraDevice camera) { 121 super.onClosed(camera); 122 synchronized(this) { 123 mCameraDevice = null; 124 } 125 Log.i(TAG, "CameraDevice onClosed called for main CTS test process."); 126 } 127 128 @Override onDisconnected(CameraDevice cameraDevice)129 public void onDisconnected(CameraDevice cameraDevice) { 130 synchronized(this) { 131 mCameraDevice = null; 132 } 133 Log.i(TAG, "CameraDevice onDisconnected called for main CTS test process."); 134 135 } 136 137 @Override onError(CameraDevice cameraDevice, int i)138 public void onError(CameraDevice cameraDevice, int i) { 139 Log.i(TAG, "CameraDevice onError called for main CTS test process with error " + 140 "code: " + i); 141 } 142 getCameraDevice()143 public synchronized CameraDevice getCameraDevice() { 144 return mCameraDevice; 145 } 146 } 147 148 @Override setUp()149 protected void setUp() throws Exception { 150 super.setUp(); 151 152 mCompleted = false; 153 getActivity(); 154 mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 155 mContext = InstrumentationRegistry.getTargetContext(); 156 System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString()); 157 mActivityManager = mContext.getSystemService(ActivityManager.class); 158 mErrorServiceConnection = new ErrorLoggingService.ErrorServiceConnection(mContext); 159 mErrorServiceConnection.start(); 160 NestedShellPermission.run(() -> { 161 mTaskOrganizer = new TestTaskOrganizer(); 162 }); 163 } 164 165 @Override tearDown()166 protected void tearDown() throws Exception { 167 if (mProcessPid != -1) { 168 android.os.Process.killProcess(mProcessPid); 169 mProcessPid = -1; 170 } 171 if (mErrorServiceConnection != null) { 172 mErrorServiceConnection.stop(); 173 mErrorServiceConnection = null; 174 } 175 if (mCamera != null) { 176 mCamera.release(); 177 mCamera = null; 178 } 179 if (mCameraDevice != null) { 180 mCameraDevice.close(); 181 mCameraDevice = null; 182 } 183 mContext = null; 184 mActivityManager = null; 185 mTaskOrganizer.unregisterOrganizerIfNeeded(); 186 super.tearDown(); 187 } 188 189 /** 190 * Test basic eviction scenarios for the Camera1 API. 191 */ testCamera1ActivityEviction()192 public void testCamera1ActivityEviction() throws Throwable { 193 testAPI1ActivityEviction(Camera1Activity.class, "camera1ActivityProcess"); 194 } 195 testBasicCamera2ActivityEviction()196 public void testBasicCamera2ActivityEviction() throws Throwable { 197 testBasicCamera2ActivityEvictionInternal(/*lowerPriority*/ false); 198 } 199 testBasicCamera2ActivityEvictionOomScoreOffset()200 public void testBasicCamera2ActivityEvictionOomScoreOffset() throws Throwable { 201 testBasicCamera2ActivityEvictionInternal(/*lowerPriority*/ true); 202 } 203 /** 204 * Test basic eviction scenarios for the Camera2 API. 205 */ testBasicCamera2ActivityEvictionInternal(boolean lowerPriority)206 private void testBasicCamera2ActivityEvictionInternal(boolean lowerPriority) throws Throwable { 207 UiAutomation uiAutomation = null; 208 if (lowerPriority && mUiAutomation != null) { 209 mUiAutomation.adoptShellPermissionIdentity(); 210 } 211 CameraManager manager = mContext.getSystemService(CameraManager.class); 212 assertNotNull("Unable to get CameraManager service!", manager); 213 String[] cameraIds = manager.getCameraIdListNoLazy(); 214 215 if (cameraIds.length == 0) { 216 Log.i(TAG, "Skipping testBasicCamera2ActivityEviction, device has no cameras."); 217 return; 218 } 219 220 assertTrue("Context has no main looper!", mContext.getMainLooper() != null); 221 222 // Setup camera manager 223 String chosenCamera = cameraIds[0]; 224 Handler cameraHandler = new Handler(mContext.getMainLooper()); 225 final CameraManager.AvailabilityCallback mockAvailCb = 226 mock(CameraManager.AvailabilityCallback.class); 227 228 manager.registerAvailabilityCallback(mockAvailCb, cameraHandler); 229 230 Thread.sleep(WAIT_TIME); 231 232 verify(mockAvailCb, times(1)).onCameraAvailable(chosenCamera); 233 verify(mockAvailCb, never()).onCameraUnavailable(chosenCamera); 234 235 // Setup camera device 236 final CameraDevice.StateCallback spyStateCb = spy(new StateCallbackImpl()); 237 manager.openCamera(chosenCamera, spyStateCb, cameraHandler); 238 239 verify(spyStateCb, timeout(OPEN_TIMEOUT).times(1)).onOpened(any(CameraDevice.class)); 240 verify(spyStateCb, never()).onClosed(any(CameraDevice.class)); 241 verify(spyStateCb, never()).onDisconnected(any(CameraDevice.class)); 242 verify(spyStateCb, never()).onError(any(CameraDevice.class), anyInt()); 243 244 // Open camera from remote process 245 startRemoteProcess(Camera2Activity.class, "camera2ActivityProcess"); 246 247 // Verify that the remote camera was opened correctly 248 List<ErrorLoggingService.LogEvent> allEvents = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 249 TestConstants.EVENT_CAMERA_CONNECT); 250 assertNotNull("Camera device not setup in remote process!", allEvents); 251 252 // Filter out relevant events for other camera devices 253 ArrayList<ErrorLoggingService.LogEvent> events = new ArrayList<>(); 254 for (ErrorLoggingService.LogEvent e : allEvents) { 255 int eventTag = e.getEvent(); 256 if (eventTag == TestConstants.EVENT_CAMERA_UNAVAILABLE || 257 eventTag == TestConstants.EVENT_CAMERA_CONNECT || 258 eventTag == TestConstants.EVENT_CAMERA_AVAILABLE) { 259 if (!Objects.equals(e.getLogText(), chosenCamera)) { 260 continue; 261 } 262 } 263 events.add(e); 264 } 265 int[] eventList = new int[events.size()]; 266 int eventIdx = 0; 267 for (ErrorLoggingService.LogEvent e : events) { 268 eventList[eventIdx++] = e.getEvent(); 269 } 270 String[] actualEvents = TestConstants.convertToStringArray(eventList); 271 String[] expectedEvents = new String[] { TestConstants.EVENT_ACTIVITY_RESUMED_STR, 272 TestConstants.EVENT_CAMERA_UNAVAILABLE_STR, 273 TestConstants.EVENT_CAMERA_CONNECT_STR }; 274 String[] ignoredEvents = new String[] { TestConstants.EVENT_CAMERA_AVAILABLE_STR, 275 TestConstants.EVENT_CAMERA_UNAVAILABLE_STR }; 276 assertOrderedEvents(actualEvents, expectedEvents, ignoredEvents); 277 278 // Verify that the local camera was evicted properly 279 verify(spyStateCb, times(1)).onDisconnected(any(CameraDevice.class)); 280 verify(spyStateCb, never()).onClosed(any(CameraDevice.class)); 281 verify(spyStateCb, never()).onError(any(CameraDevice.class), anyInt()); 282 verify(spyStateCb, times(1)).onOpened(any(CameraDevice.class)); 283 284 // Verify that we can no longer open the camera, as it is held by a higher priority process 285 try { 286 if (!lowerPriority) { 287 manager.openCamera(chosenCamera, spyStateCb, cameraHandler); 288 } else { 289 // Go to top again, try getting hold of camera with priority lowered, we should get 290 // an exception 291 Executor cameraExecutor = new HandlerExecutor(cameraHandler); 292 forceCtsActivityToTop(); 293 manager.openCamera(chosenCamera, CACHED_APP_VS_FG_OOM_DELTA, cameraExecutor, 294 spyStateCb); 295 } 296 fail("Didn't receive exception when trying to open camera held by higher priority " + 297 "process."); 298 } catch(CameraAccessException e) { 299 assertTrue("Received incorrect camera exception when opening camera: " + e, 300 e.getReason() == CameraAccessException.CAMERA_IN_USE); 301 } 302 303 // Verify that attempting to open the camera didn't cause anything weird to happen in the 304 // other process. 305 List<ErrorLoggingService.LogEvent> eventList2 = null; 306 boolean timeoutExceptionHit = false; 307 try { 308 eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT); 309 } catch (TimeoutException e) { 310 timeoutExceptionHit = true; 311 } 312 313 assertNone("Remote camera service received invalid events: ", eventList2); 314 assertTrue("Remote camera service exited early", timeoutExceptionHit); 315 android.os.Process.killProcess(mProcessPid); 316 mProcessPid = -1; 317 forceCtsActivityToTop(); 318 if (lowerPriority && mUiAutomation != null) { 319 mUiAutomation.dropShellPermissionIdentity(); 320 } 321 } 322 323 /** 324 * Tests that a client without SYSTEM_CAMERA permissions receives a security exception when 325 * trying to modify the oom score for camera framework. 326 */ testCamera2OomScoreOffsetPermissions()327 public void testCamera2OomScoreOffsetPermissions() throws Throwable { 328 CameraManager manager = mContext.getSystemService(CameraManager.class); 329 assertNotNull("Unable to get CameraManager service!", manager); 330 String[] cameraIds = manager.getCameraIdListNoLazy(); 331 332 if (cameraIds.length == 0) { 333 Log.i(TAG, "Skipping testBasicCamera2OomScoreOffsetPermissions, no cameras present."); 334 return; 335 } 336 337 assertTrue("Context has no main looper!", mContext.getMainLooper() != null); 338 for (String cameraId : cameraIds) { 339 // Setup camera manager 340 Handler cameraHandler = new Handler(mContext.getMainLooper()); 341 final CameraManager.AvailabilityCallback mockAvailCb = 342 mock(CameraManager.AvailabilityCallback.class); 343 344 final CameraDevice.StateCallback spyStateCb = spy(new StateCallbackImpl()); 345 manager.registerAvailabilityCallback(mockAvailCb, cameraHandler); 346 347 Thread.sleep(WAIT_TIME); 348 349 verify(mockAvailCb, times(1)).onCameraAvailable(cameraId); 350 verify(mockAvailCb, never()).onCameraUnavailable(cameraId); 351 352 try { 353 // Go to top again, try getting hold of camera with priority lowered, we should get 354 // an exception 355 Executor cameraExecutor = new HandlerExecutor(cameraHandler); 356 manager.openCamera(cameraId, CACHED_APP_VS_FG_OOM_DELTA, cameraExecutor, 357 spyStateCb); 358 fail("Didn't receive security exception when trying to open camera with modifed" + 359 "oom score without SYSTEM_CAMERA permissions"); 360 } catch(SecurityException e) { 361 // fine 362 } 363 } 364 } 365 injectTapEvent(int x, int y)366 private void injectTapEvent(int x, int y) { 367 long systemClock = SystemClock.uptimeMillis(); 368 369 final int motionEventTimeDeltaMs = 100; 370 MotionEvent downEvent = MotionEvent.obtain(systemClock, systemClock, 371 (int) MotionEvent.ACTION_DOWN, x, y, 0); 372 downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); 373 assertTrue("Failed to inject downEvent.", mUiAutomation.injectInputEvent(downEvent, true)); 374 375 MotionEvent upEvent = MotionEvent.obtain(systemClock, 376 systemClock + motionEventTimeDeltaMs, (int) MotionEvent.ACTION_UP, 377 x, y, 0); 378 upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); 379 assertTrue("Failed to inject upEvent.", mUiAutomation.injectInputEvent(upEvent, true)); 380 } 381 382 /** 383 * Return a Map of eventTag -> number of times encountered 384 */ getEventTagCountMap(List<ErrorLoggingService.LogEvent> events)385 private Map<Integer, Integer> getEventTagCountMap(List<ErrorLoggingService.LogEvent> events) { 386 ArrayMap<Integer, Integer> eventTagCountMap = new ArrayMap<>(); 387 for (ErrorLoggingService.LogEvent e : events) { 388 int eventTag = e.getEvent(); 389 if (!eventTagCountMap.containsKey(eventTag)) { 390 eventTagCountMap.put(eventTag, 1); 391 } else { 392 eventTagCountMap.put(eventTag, eventTagCountMap.get(eventTag) + 1); 393 } 394 } 395 return eventTagCountMap; 396 } 397 398 /** 399 * Test camera availability access callback in split window mode. 400 */ 401 @AppModeFull(reason = "TestTaskOrganizer.putTaskInSplitPrimary, .putTaskInSplitSecondary") testCamera2AccessCallbackInSplitMode()402 public void testCamera2AccessCallbackInSplitMode() throws Throwable { 403 if (!ActivityTaskManager.supportsSplitScreenMultiWindow(getActivity())) { 404 return; 405 } 406 407 final int permissionCallbackTimeoutMs = 3000; 408 CameraManager manager = mContext.getSystemService(CameraManager.class); 409 assertNotNull("Unable to get CameraManager service!", manager); 410 String[] cameraIds = manager.getCameraIdListNoLazy(); 411 412 if (cameraIds.length == 0) { 413 Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras."); 414 return; 415 } 416 417 startRemoteProcess(Camera2Activity.class, "camera2ActivityProcess", 418 true /*splitScreen*/); 419 420 // Verify that the remote camera did open as expected 421 List<ErrorLoggingService.LogEvent> allEvents = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 422 TestConstants.EVENT_CAMERA_CONNECT); 423 assertNotNull("Camera device not setup in remote process!", allEvents); 424 425 int activityResumed = 0; 426 boolean cameraConnected = false; 427 boolean activityPaused = false; 428 429 Map<Integer, Integer> eventTagCountMap = getEventTagCountMap(allEvents); 430 for (int eventTag : eventTagCountMap.keySet()) { 431 if (eventTag == TestConstants.EVENT_ACTIVITY_RESUMED) { 432 activityResumed += eventTagCountMap.get(eventTag); 433 } 434 } 435 activityPaused = eventTagCountMap.containsKey(TestConstants.EVENT_ACTIVITY_PAUSED); 436 cameraConnected = eventTagCountMap.containsKey(TestConstants.EVENT_CAMERA_CONNECT); 437 assertTrue("Remote activity never resumed!", activityResumed > 0); 438 assertTrue("Camera device not setup in remote process!", cameraConnected); 439 440 Rect firstBounds = mTaskOrganizer.getPrimaryTaskBounds(); 441 Rect secondBounds = mTaskOrganizer.getSecondaryTaskBounds(); 442 443 Log.v(TAG, "Split bounds: (" + firstBounds.left + ", " + firstBounds.top + ", " 444 + firstBounds.right + ", " + firstBounds.bottom + "), (" 445 + secondBounds.left + ", " + secondBounds.top + ", " 446 + secondBounds.right + ", " + secondBounds.bottom + ")"); 447 448 // Both the remote activity and the in-process activity will go through a pause-resume cycle 449 // which we're not interested in testing. Wait until the end of it before expecting 450 // onCameraAccessPrioritiesChanged events. 451 if (!activityPaused) { 452 allEvents = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 453 TestConstants.EVENT_ACTIVITY_PAUSED); 454 assertNotNull("Remote activity not paused!", allEvents); 455 eventTagCountMap = getEventTagCountMap(allEvents); 456 for (int eventTag : eventTagCountMap.keySet()) { 457 if (eventTag == TestConstants.EVENT_ACTIVITY_RESUMED) { 458 activityResumed += eventTagCountMap.get(eventTag); 459 } 460 } 461 activityPaused = eventTagCountMap.containsKey(TestConstants.EVENT_ACTIVITY_PAUSED); 462 } 463 464 assertTrue(activityPaused); 465 466 if (activityResumed < 2) { 467 allEvents = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 468 TestConstants.EVENT_ACTIVITY_RESUMED); 469 assertNotNull("Remote activity not resumed after pause!", allEvents); 470 eventTagCountMap = getEventTagCountMap(allEvents); 471 for (int eventTag : eventTagCountMap.keySet()) { 472 if (eventTag == TestConstants.EVENT_ACTIVITY_RESUMED) { 473 activityResumed += eventTagCountMap.get(eventTag); 474 } 475 } 476 } 477 478 assertEquals(2, activityResumed); 479 480 Set<Integer> expectedEventsPrimary = new ArraySet<>(); 481 expectedEventsPrimary.add(TestConstants.EVENT_CAMERA_ACCESS_PRIORITIES_CHANGED); 482 expectedEventsPrimary.add(TestConstants.EVENT_ACTIVITY_TOP_RESUMED_FALSE); 483 484 Set<Integer> expectedEventsSecondary = new ArraySet<>(); 485 expectedEventsSecondary.add(TestConstants.EVENT_CAMERA_ACCESS_PRIORITIES_CHANGED); 486 expectedEventsSecondary.add(TestConstants.EVENT_ACTIVITY_TOP_RESUMED_TRUE); 487 488 // Priorities are also expected to change when a second activity only gains or loses focus 489 // while running in split screen mode. 490 injectTapEvent(firstBounds.centerX(), firstBounds.centerY()); 491 allEvents = mErrorServiceConnection.getLog(CAMERA_ACCESS_TIMEOUT, expectedEventsPrimary); 492 493 // Run many iterations to make sure there are no negatives. Limit this to 15 seconds. 494 long begin = System.currentTimeMillis(); 495 final int maxIterations = 100; 496 final long timeLimitMs = 15000; 497 for (int i = 0; i < maxIterations && System.currentTimeMillis() - begin < timeLimitMs; 498 i++) { 499 injectTapEvent(secondBounds.centerX(), secondBounds.centerY()); 500 allEvents = mErrorServiceConnection.getLog(CAMERA_ACCESS_TIMEOUT, 501 expectedEventsSecondary); 502 assertNotNull(allEvents); 503 eventTagCountMap = getEventTagCountMap(allEvents); 504 assertTrue(eventTagCountMap.containsKey( 505 TestConstants.EVENT_CAMERA_ACCESS_PRIORITIES_CHANGED)); 506 assertTrue(eventTagCountMap.containsKey( 507 TestConstants.EVENT_ACTIVITY_TOP_RESUMED_TRUE)); 508 assertFalse(eventTagCountMap.containsKey( 509 TestConstants.EVENT_ACTIVITY_TOP_RESUMED_FALSE)); 510 511 injectTapEvent(firstBounds.centerX(), firstBounds.centerY()); 512 allEvents = mErrorServiceConnection.getLog(CAMERA_ACCESS_TIMEOUT, 513 expectedEventsPrimary); 514 assertNotNull(allEvents); 515 eventTagCountMap = getEventTagCountMap(allEvents); 516 assertTrue(eventTagCountMap.containsKey( 517 TestConstants.EVENT_CAMERA_ACCESS_PRIORITIES_CHANGED)); 518 assertTrue(eventTagCountMap.containsKey( 519 TestConstants.EVENT_ACTIVITY_TOP_RESUMED_FALSE)); 520 assertFalse(eventTagCountMap.containsKey( 521 TestConstants.EVENT_ACTIVITY_TOP_RESUMED_TRUE)); 522 } 523 524 mTaskOrganizer.unregisterOrganizerIfNeeded(); 525 Thread.sleep(WAIT_TIME); 526 } 527 528 /** 529 * Test camera availability access callback. 530 */ testCamera2AccessCallback()531 public void testCamera2AccessCallback() throws Throwable { 532 int PERMISSION_CALLBACK_TIMEOUT_MS = 2000; 533 CameraManager manager = mContext.getSystemService(CameraManager.class); 534 assertNotNull("Unable to get CameraManager service!", manager); 535 String[] cameraIds = manager.getCameraIdListNoLazy(); 536 537 if (cameraIds.length == 0) { 538 Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras."); 539 return; 540 } 541 542 assertTrue("Context has no main looper!", mContext.getMainLooper() != null); 543 544 // Setup camera manager 545 Handler cameraHandler = new Handler(mContext.getMainLooper()); 546 547 final CameraManager.AvailabilityCallback mockAvailCb = 548 mock(CameraManager.AvailabilityCallback.class); 549 manager.registerAvailabilityCallback(mockAvailCb, cameraHandler); 550 551 // Remove current task from top of stack. This will impact the camera access 552 // priorities. 553 getActivity().moveTaskToBack(/*nonRoot*/true); 554 555 verify(mockAvailCb, timeout( 556 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged(); 557 558 forceCtsActivityToTop(); 559 560 verify(mockAvailCb, timeout( 561 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged(); 562 } 563 564 /** 565 * Test native camera availability access callback. 566 */ testCamera2NativeAccessCallback()567 public void testCamera2NativeAccessCallback() throws Throwable { 568 int PERMISSION_CALLBACK_TIMEOUT_MS = 2000; 569 CameraManager manager = mContext.getSystemService(CameraManager.class); 570 assertNotNull("Unable to get CameraManager service!", manager); 571 String[] cameraIds = manager.getCameraIdListNoLazy(); 572 573 if (cameraIds.length == 0) { 574 Log.i(TAG, "Skipping testBasicCamera2AccessCallback, device has no cameras."); 575 return; 576 } 577 578 // Setup camera manager 579 long context = 0; 580 try { 581 context = initializeAvailabilityCallbacksNative(); 582 assertTrue("Failed to initialize native availability callbacks", (context != 0)); 583 584 // Remove current task from top of stack. This will impact the camera access 585 // pririorties. 586 getActivity().moveTaskToBack(/*nonRoot*/true); 587 588 Thread.sleep(PERMISSION_CALLBACK_TIMEOUT_MS); 589 assertTrue("No camera permission access changed callback received", 590 (getAccessCallbacksCountAndResetNative(context) > 0)); 591 592 forceCtsActivityToTop(); 593 594 assertTrue("No camera permission access changed callback received", 595 (getAccessCallbacksCountAndResetNative(context) > 0)); 596 } finally { 597 if (context != 0) { 598 releaseAvailabilityCallbacksNative(context); 599 } 600 } 601 } 602 603 /** 604 * Test basic eviction scenarios for camera used in MediaRecoder 605 */ testMediaRecorderCameraActivityEviction()606 public void testMediaRecorderCameraActivityEviction() throws Throwable { 607 testAPI1ActivityEviction(MediaRecorderCameraActivity.class, 608 "mediaRecorderCameraActivityProcess"); 609 } 610 611 /** 612 * Test basic eviction scenarios for Camera1 API. 613 * 614 * This test will open camera, create a higher priority process to run the specified activity, 615 * open camera again, and verify the right clients are evicted. 616 * 617 * @param activityKlass An activity to run in a higher priority process. 618 * @param processName The process name. 619 */ testAPI1ActivityEviction(java.lang.Class<?> activityKlass, String processName)620 private void testAPI1ActivityEviction (java.lang.Class<?> activityKlass, String processName) 621 throws Throwable { 622 // Open a camera1 client in the main CTS process's activity 623 final Camera.ErrorCallback mockErrorCb1 = mock(Camera.ErrorCallback.class); 624 final boolean[] skip = {false}; 625 runTestOnUiThread(new Runnable() { 626 @Override 627 public void run() { 628 // Open camera 629 mCamera = Camera.open(); 630 if (mCamera == null) { 631 skip[0] = true; 632 } else { 633 mCamera.setErrorCallback(mockErrorCb1); 634 } 635 notifyFromUI(); 636 } 637 }); 638 waitForUI(); 639 640 if (skip[0]) { 641 Log.i(TAG, "Skipping testCamera1ActivityEviction, device has no cameras."); 642 return; 643 } 644 645 verifyZeroInteractions(mockErrorCb1); 646 647 startRemoteProcess(activityKlass, processName); 648 649 // Make sure camera was setup correctly in remote activity 650 List<ErrorLoggingService.LogEvent> events = null; 651 try { 652 events = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 653 TestConstants.EVENT_CAMERA_CONNECT); 654 } finally { 655 if (events != null) assertOnly(TestConstants.EVENT_CAMERA_CONNECT, events); 656 } 657 658 Thread.sleep(WAIT_TIME); 659 660 // Ensure UI thread has a chance to process callbacks. 661 runTestOnUiThread(new Runnable() { 662 @Override 663 public void run() { 664 Log.i("CTS", "Did something on UI thread."); 665 notifyFromUI(); 666 } 667 }); 668 waitForUI(); 669 670 // Make sure we received correct callback in error listener, and nothing else 671 verify(mockErrorCb1, only()).onError(eq(Camera.CAMERA_ERROR_EVICTED), isA(Camera.class)); 672 mCamera = null; 673 674 // Try to open the camera again (even though other TOP process holds the camera). 675 final boolean[] pass = {false}; 676 runTestOnUiThread(new Runnable() { 677 @Override 678 public void run() { 679 // Open camera 680 try { 681 mCamera = Camera.open(); 682 } catch (RuntimeException e) { 683 pass[0] = true; 684 } 685 notifyFromUI(); 686 } 687 }); 688 waitForUI(); 689 690 assertTrue("Did not receive exception when opening camera while camera is held by a" + 691 " higher priority client process.", pass[0]); 692 693 // Verify that attempting to open the camera didn't cause anything weird to happen in the 694 // other process. 695 List<ErrorLoggingService.LogEvent> eventList2 = null; 696 boolean timeoutExceptionHit = false; 697 try { 698 eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT); 699 } catch (TimeoutException e) { 700 timeoutExceptionHit = true; 701 } 702 703 assertNone("Remote camera service received invalid events: ", eventList2); 704 assertTrue("Remote camera service exited early", timeoutExceptionHit); 705 android.os.Process.killProcess(mProcessPid); 706 mProcessPid = -1; 707 forceCtsActivityToTop(); 708 } 709 710 /** 711 * Ensure the CTS activity becomes foreground again instead of launcher. 712 */ forceCtsActivityToTop()713 private void forceCtsActivityToTop() throws InterruptedException { 714 Thread.sleep(WAIT_TIME); 715 Activity a = getActivity(); 716 Intent activityIntent = new Intent(a, CameraCtsActivity.class); 717 activityIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 718 a.startActivity(activityIntent); 719 Thread.sleep(WAIT_TIME); 720 } 721 722 /** 723 * Block until UI thread calls {@link #notifyFromUI()}. 724 * @throws InterruptedException 725 */ waitForUI()726 private void waitForUI() throws InterruptedException { 727 synchronized(mLock) { 728 if (mCompleted) return; 729 while (!mCompleted) { 730 mLock.wait(); 731 } 732 mCompleted = false; 733 } 734 } 735 736 /** 737 * Wake up any threads waiting in calls to {@link #waitForUI()}. 738 */ notifyFromUI()739 private void notifyFromUI() { 740 synchronized (mLock) { 741 mCompleted = true; 742 mLock.notifyAll(); 743 } 744 } 745 746 /** 747 * Return the PID for the process with the given name in the given list of process info. 748 * 749 * @param processName the name of the process who's PID to return. 750 * @param list a list of {@link ActivityManager.RunningAppProcessInfo} to check. 751 * @return the PID of the given process, or -1 if it was not included in the list. 752 */ getPid(String processName, List<ActivityManager.RunningAppProcessInfo> list)753 private static int getPid(String processName, 754 List<ActivityManager.RunningAppProcessInfo> list) { 755 for (ActivityManager.RunningAppProcessInfo rai : list) { 756 if (processName.equals(rai.processName)) 757 return rai.pid; 758 } 759 return -1; 760 } 761 762 /** 763 * Start an activity of the given class running in a remote process with the given name. 764 * 765 * @param klass the class of the {@link android.app.Activity} to start. 766 * @param processName the remote activity name. 767 * @throws InterruptedException 768 */ startRemoteProcess(java.lang.Class<?> klass, String processName)769 public void startRemoteProcess(java.lang.Class<?> klass, String processName) 770 throws InterruptedException { 771 startRemoteProcess(klass, processName, false /*splitScreen*/); 772 } 773 774 /** 775 * Start an activity of the given class running in a remote process with the given name. 776 * 777 * @param klass the class of the {@link android.app.Activity} to start. 778 * @param processName the remote activity name. 779 * @param splitScreen Start new activity in split screen mode. 780 * @throws InterruptedException 781 */ startRemoteProcess(java.lang.Class<?> klass, String processName, boolean splitScreen)782 public void startRemoteProcess(java.lang.Class<?> klass, String processName, 783 boolean splitScreen) throws InterruptedException { 784 // Ensure no running activity process with same name 785 Activity a = getActivity(); 786 String cameraActivityName = a.getPackageName() + ":" + processName; 787 List<ActivityManager.RunningAppProcessInfo> list = 788 mActivityManager.getRunningAppProcesses(); 789 assertEquals("Activity " + cameraActivityName + " already running.", 790 -1, getPid(cameraActivityName, list)); 791 792 // Start activity in a new top foreground process 793 if (splitScreen) { 794 // startActivity(intent) doesn't work with TestTaskOrganizer's split screen, 795 // have to go through shell command. 796 // Also, android:exported must be true for this to work, see: 797 // https://developer.android.com/guide/topics/manifest/activity-element#exported 798 runShellCommand("am start %s/%s", a.getPackageName(), klass.getName()); 799 ComponentName secondActivityComponent = new ComponentName( 800 a.getPackageName(), klass.getName()); 801 mWmState.waitForValidState(secondActivityComponent); 802 int taskId = mWmState.getTaskByActivity(secondActivityComponent) 803 .getTaskId(); 804 805 // Requires @AppModeFull. 806 mTaskOrganizer.putTaskInSplitPrimary(a.getTaskId()); 807 ComponentName primaryActivityComponent = new ComponentName( 808 a.getPackageName(), a.getClass().getName()); 809 mWmState.waitForValidState(primaryActivityComponent); 810 811 // The taskAffinity of the secondary activity must be differ with the taskAffinity 812 // of the primary activity, otherwise it will replace the primary activity instead. 813 mTaskOrganizer.putTaskInSplitSecondary(taskId); 814 mWmState.waitForValidState(secondActivityComponent); 815 } else { 816 Intent activityIntent = new Intent(a, klass); 817 activityIntent.putExtra(TestConstants.EXTRA_IGNORE_CAMERA_ACCESS, true); 818 activityIntent.putExtra(TestConstants.EXTRA_IGNORE_TOP_ACTIVITY_RESUMED, true); 819 activityIntent.putExtra(TestConstants.EXTRA_IGNORE_ACTIVITY_PAUSED, true); 820 a.startActivity(activityIntent); 821 Thread.sleep(WAIT_TIME); 822 } 823 824 // Fail if activity isn't running 825 list = mActivityManager.getRunningAppProcesses(); 826 mProcessPid = getPid(cameraActivityName, list); 827 assertTrue("Activity " + cameraActivityName + " not found in list of running app " 828 + "processes.", -1 != mProcessPid); 829 } 830 831 /** 832 * Assert that there is only one event of the given type in the event list. 833 * 834 * @param event event type to check for. 835 * @param events {@link List} of events. 836 */ assertOnly(int event, List<ErrorLoggingService.LogEvent> events)837 public static void assertOnly(int event, List<ErrorLoggingService.LogEvent> events) { 838 assertTrue("Remote camera activity never received event: " + event, events != null); 839 for (ErrorLoggingService.LogEvent e : events) { 840 assertFalse("Remote camera activity received invalid event (" + e + 841 ") while waiting for event: " + event, 842 e.getEvent() < 0 || e.getEvent() != event); 843 } 844 assertTrue("Remote camera activity never received event: " + event, events.size() >= 1); 845 assertTrue("Remote camera activity received too many " + event + " events, received: " + 846 events.size(), events.size() == 1); 847 } 848 849 /** 850 * Assert there were no logEvents in the given list. 851 * 852 * @param msg message to show on assertion failure. 853 * @param events {@link List} of events. 854 */ assertNone(String msg, List<ErrorLoggingService.LogEvent> events)855 public static void assertNone(String msg, List<ErrorLoggingService.LogEvent> events) { 856 if (events == null) return; 857 StringBuilder builder = new StringBuilder(msg + "\n"); 858 for (ErrorLoggingService.LogEvent e : events) { 859 builder.append(e).append("\n"); 860 } 861 assertTrue(builder.toString(), events.isEmpty()); 862 } 863 864 /** 865 * Assert array is null or empty. 866 * 867 * @param array array to check. 868 */ assertNotEmpty(T[] array)869 public static <T> void assertNotEmpty(T[] array) { 870 assertNotNull("Array is null.", array); 871 assertFalse("Array is empty: " + Arrays.toString(array), array.length == 0); 872 } 873 874 /** 875 * Given an 'actual' array of objects, check that the objects given in the 'expected' 876 * array are also present in the 'actual' array in the same order. Objects in the 'actual' 877 * array that are not in the 'expected' array are skipped and ignored if they are given 878 * in the 'ignored' array, otherwise this assertion will fail. 879 * 880 * @param actual the ordered array of objects to check. 881 * @param expected the ordered array of expected objects. 882 * @param ignored the array of objects that will be ignored if present in actual, 883 * but not in expected (or are out of order). 884 * @param <T> 885 */ assertOrderedEvents(T[] actual, T[] expected, T[] ignored)886 public static <T> void assertOrderedEvents(T[] actual, T[] expected, T[] ignored) { 887 assertNotNull("List of actual events is null.", actual); 888 assertNotNull("List of expected events is null.", expected); 889 assertNotNull("List of ignored events is null.", ignored); 890 891 int expIndex = 0; 892 int index = 0; 893 for (T i : actual) { 894 // If explicitly expected, move to next 895 if (expIndex < expected.length && Objects.equals(i, expected[expIndex])) { 896 expIndex++; 897 continue; 898 } 899 900 // Fail if not ignored 901 boolean canIgnore = false; 902 for (T j : ignored) { 903 if (Objects.equals(i, j)) { 904 canIgnore = true; 905 break; 906 } 907 908 } 909 910 // Fail if not ignored. 911 assertTrue("Event at index " + index + " in actual array " + 912 Arrays.toString(actual) + " was unexpected: expected array was " + 913 Arrays.toString(expected) + ", ignored array was: " + 914 Arrays.toString(ignored), canIgnore); 915 index++; 916 } 917 assertTrue("Only had " + expIndex + " of " + expected.length + 918 " expected objects in array " + Arrays.toString(actual) + ", expected was " + 919 Arrays.toString(expected), expIndex == expected.length); 920 } 921 } 922