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