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 525 /** 526 * Test camera availability access callback. 527 */ testCamera2AccessCallback()528 public void testCamera2AccessCallback() throws Throwable { 529 int PERMISSION_CALLBACK_TIMEOUT_MS = 2000; 530 CameraManager manager = mContext.getSystemService(CameraManager.class); 531 assertNotNull("Unable to get CameraManager service!", manager); 532 String[] cameraIds = manager.getCameraIdListNoLazy(); 533 534 if (cameraIds.length == 0) { 535 Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras."); 536 return; 537 } 538 539 assertTrue("Context has no main looper!", mContext.getMainLooper() != null); 540 541 // Setup camera manager 542 Handler cameraHandler = new Handler(mContext.getMainLooper()); 543 544 final CameraManager.AvailabilityCallback mockAvailCb = 545 mock(CameraManager.AvailabilityCallback.class); 546 manager.registerAvailabilityCallback(mockAvailCb, cameraHandler); 547 548 // Remove current task from top of stack. This will impact the camera access 549 // priorities. 550 getActivity().moveTaskToBack(/*nonRoot*/true); 551 552 verify(mockAvailCb, timeout( 553 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged(); 554 555 forceCtsActivityToTop(); 556 557 verify(mockAvailCb, timeout( 558 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged(); 559 } 560 561 /** 562 * Test native camera availability access callback. 563 */ testCamera2NativeAccessCallback()564 public void testCamera2NativeAccessCallback() throws Throwable { 565 int PERMISSION_CALLBACK_TIMEOUT_MS = 2000; 566 CameraManager manager = mContext.getSystemService(CameraManager.class); 567 assertNotNull("Unable to get CameraManager service!", manager); 568 String[] cameraIds = manager.getCameraIdListNoLazy(); 569 570 if (cameraIds.length == 0) { 571 Log.i(TAG, "Skipping testBasicCamera2AccessCallback, device has no cameras."); 572 return; 573 } 574 575 // Setup camera manager 576 long context = 0; 577 try { 578 context = initializeAvailabilityCallbacksNative(); 579 assertTrue("Failed to initialize native availability callbacks", (context != 0)); 580 581 // Remove current task from top of stack. This will impact the camera access 582 // pririorties. 583 getActivity().moveTaskToBack(/*nonRoot*/true); 584 585 Thread.sleep(PERMISSION_CALLBACK_TIMEOUT_MS); 586 assertTrue("No camera permission access changed callback received", 587 (getAccessCallbacksCountAndResetNative(context) > 0)); 588 589 forceCtsActivityToTop(); 590 591 assertTrue("No camera permission access changed callback received", 592 (getAccessCallbacksCountAndResetNative(context) > 0)); 593 } finally { 594 if (context != 0) { 595 releaseAvailabilityCallbacksNative(context); 596 } 597 } 598 } 599 600 /** 601 * Test basic eviction scenarios for camera used in MediaRecoder 602 */ testMediaRecorderCameraActivityEviction()603 public void testMediaRecorderCameraActivityEviction() throws Throwable { 604 testAPI1ActivityEviction(MediaRecorderCameraActivity.class, 605 "mediaRecorderCameraActivityProcess"); 606 } 607 608 /** 609 * Test basic eviction scenarios for Camera1 API. 610 * 611 * This test will open camera, create a higher priority process to run the specified activity, 612 * open camera again, and verify the right clients are evicted. 613 * 614 * @param activityKlass An activity to run in a higher priority process. 615 * @param processName The process name. 616 */ testAPI1ActivityEviction(java.lang.Class<?> activityKlass, String processName)617 private void testAPI1ActivityEviction (java.lang.Class<?> activityKlass, String processName) 618 throws Throwable { 619 // Open a camera1 client in the main CTS process's activity 620 final Camera.ErrorCallback mockErrorCb1 = mock(Camera.ErrorCallback.class); 621 final boolean[] skip = {false}; 622 runTestOnUiThread(new Runnable() { 623 @Override 624 public void run() { 625 // Open camera 626 mCamera = Camera.open(); 627 if (mCamera == null) { 628 skip[0] = true; 629 } else { 630 mCamera.setErrorCallback(mockErrorCb1); 631 } 632 notifyFromUI(); 633 } 634 }); 635 waitForUI(); 636 637 if (skip[0]) { 638 Log.i(TAG, "Skipping testCamera1ActivityEviction, device has no cameras."); 639 return; 640 } 641 642 verifyZeroInteractions(mockErrorCb1); 643 644 startRemoteProcess(activityKlass, processName); 645 646 // Make sure camera was setup correctly in remote activity 647 List<ErrorLoggingService.LogEvent> events = null; 648 try { 649 events = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 650 TestConstants.EVENT_CAMERA_CONNECT); 651 } finally { 652 if (events != null) assertOnly(TestConstants.EVENT_CAMERA_CONNECT, events); 653 } 654 655 Thread.sleep(WAIT_TIME); 656 657 // Ensure UI thread has a chance to process callbacks. 658 runTestOnUiThread(new Runnable() { 659 @Override 660 public void run() { 661 Log.i("CTS", "Did something on UI thread."); 662 notifyFromUI(); 663 } 664 }); 665 waitForUI(); 666 667 // Make sure we received correct callback in error listener, and nothing else 668 verify(mockErrorCb1, only()).onError(eq(Camera.CAMERA_ERROR_EVICTED), isA(Camera.class)); 669 mCamera = null; 670 671 // Try to open the camera again (even though other TOP process holds the camera). 672 final boolean[] pass = {false}; 673 runTestOnUiThread(new Runnable() { 674 @Override 675 public void run() { 676 // Open camera 677 try { 678 mCamera = Camera.open(); 679 } catch (RuntimeException e) { 680 pass[0] = true; 681 } 682 notifyFromUI(); 683 } 684 }); 685 waitForUI(); 686 687 assertTrue("Did not receive exception when opening camera while camera is held by a" + 688 " higher priority client process.", pass[0]); 689 690 // Verify that attempting to open the camera didn't cause anything weird to happen in the 691 // other process. 692 List<ErrorLoggingService.LogEvent> eventList2 = null; 693 boolean timeoutExceptionHit = false; 694 try { 695 eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT); 696 } catch (TimeoutException e) { 697 timeoutExceptionHit = true; 698 } 699 700 assertNone("Remote camera service received invalid events: ", eventList2); 701 assertTrue("Remote camera service exited early", timeoutExceptionHit); 702 android.os.Process.killProcess(mProcessPid); 703 mProcessPid = -1; 704 forceCtsActivityToTop(); 705 } 706 707 /** 708 * Ensure the CTS activity becomes foreground again instead of launcher. 709 */ forceCtsActivityToTop()710 private void forceCtsActivityToTop() throws InterruptedException { 711 Thread.sleep(WAIT_TIME); 712 Activity a = getActivity(); 713 Intent activityIntent = new Intent(a, CameraCtsActivity.class); 714 activityIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 715 a.startActivity(activityIntent); 716 Thread.sleep(WAIT_TIME); 717 } 718 719 /** 720 * Block until UI thread calls {@link #notifyFromUI()}. 721 * @throws InterruptedException 722 */ waitForUI()723 private void waitForUI() throws InterruptedException { 724 synchronized(mLock) { 725 if (mCompleted) return; 726 while (!mCompleted) { 727 mLock.wait(); 728 } 729 mCompleted = false; 730 } 731 } 732 733 /** 734 * Wake up any threads waiting in calls to {@link #waitForUI()}. 735 */ notifyFromUI()736 private void notifyFromUI() { 737 synchronized (mLock) { 738 mCompleted = true; 739 mLock.notifyAll(); 740 } 741 } 742 743 /** 744 * Return the PID for the process with the given name in the given list of process info. 745 * 746 * @param processName the name of the process who's PID to return. 747 * @param list a list of {@link ActivityManager.RunningAppProcessInfo} to check. 748 * @return the PID of the given process, or -1 if it was not included in the list. 749 */ getPid(String processName, List<ActivityManager.RunningAppProcessInfo> list)750 private static int getPid(String processName, 751 List<ActivityManager.RunningAppProcessInfo> list) { 752 for (ActivityManager.RunningAppProcessInfo rai : list) { 753 if (processName.equals(rai.processName)) 754 return rai.pid; 755 } 756 return -1; 757 } 758 759 /** 760 * Start an activity of the given class running in a remote process with the given name. 761 * 762 * @param klass the class of the {@link android.app.Activity} to start. 763 * @param processName the remote activity name. 764 * @throws InterruptedException 765 */ startRemoteProcess(java.lang.Class<?> klass, String processName)766 public void startRemoteProcess(java.lang.Class<?> klass, String processName) 767 throws InterruptedException { 768 startRemoteProcess(klass, processName, false /*splitScreen*/); 769 } 770 771 /** 772 * Start an activity of the given class running in a remote process with the given name. 773 * 774 * @param klass the class of the {@link android.app.Activity} to start. 775 * @param processName the remote activity name. 776 * @param splitScreen Start new activity in split screen mode. 777 * @throws InterruptedException 778 */ startRemoteProcess(java.lang.Class<?> klass, String processName, boolean splitScreen)779 public void startRemoteProcess(java.lang.Class<?> klass, String processName, 780 boolean splitScreen) throws InterruptedException { 781 // Ensure no running activity process with same name 782 Activity a = getActivity(); 783 String cameraActivityName = a.getPackageName() + ":" + processName; 784 List<ActivityManager.RunningAppProcessInfo> list = 785 mActivityManager.getRunningAppProcesses(); 786 assertEquals("Activity " + cameraActivityName + " already running.", 787 -1, getPid(cameraActivityName, list)); 788 789 // Start activity in a new top foreground process 790 if (splitScreen) { 791 // startActivity(intent) doesn't work with TestTaskOrganizer's split screen, 792 // have to go through shell command. 793 // Also, android:exported must be true for this to work, see: 794 // https://developer.android.com/guide/topics/manifest/activity-element#exported 795 runShellCommand("am start %s/%s", a.getPackageName(), klass.getName()); 796 ComponentName secondActivityComponent = new ComponentName( 797 a.getPackageName(), klass.getName()); 798 mWmState.waitForValidState(secondActivityComponent); 799 int taskId = mWmState.getTaskByActivity(secondActivityComponent) 800 .getTaskId(); 801 802 // Requires @AppModeFull. 803 mTaskOrganizer.putTaskInSplitPrimary(a.getTaskId()); 804 ComponentName primaryActivityComponent = new ComponentName( 805 a.getPackageName(), a.getClass().getName()); 806 mWmState.waitForValidState(primaryActivityComponent); 807 808 // The taskAffinity of the secondary activity must be differ with the taskAffinity 809 // of the primary activity, otherwise it will replace the primary activity instead. 810 mTaskOrganizer.putTaskInSplitSecondary(taskId); 811 mWmState.waitForValidState(secondActivityComponent); 812 } else { 813 Intent activityIntent = new Intent(a, klass); 814 activityIntent.putExtra(TestConstants.EXTRA_IGNORE_CAMERA_ACCESS, true); 815 activityIntent.putExtra(TestConstants.EXTRA_IGNORE_TOP_ACTIVITY_RESUMED, true); 816 activityIntent.putExtra(TestConstants.EXTRA_IGNORE_ACTIVITY_PAUSED, true); 817 a.startActivity(activityIntent); 818 Thread.sleep(WAIT_TIME); 819 } 820 821 // Fail if activity isn't running 822 list = mActivityManager.getRunningAppProcesses(); 823 mProcessPid = getPid(cameraActivityName, list); 824 assertTrue("Activity " + cameraActivityName + " not found in list of running app " 825 + "processes.", -1 != mProcessPid); 826 } 827 828 /** 829 * Assert that there is only one event of the given type in the event list. 830 * 831 * @param event event type to check for. 832 * @param events {@link List} of events. 833 */ assertOnly(int event, List<ErrorLoggingService.LogEvent> events)834 public static void assertOnly(int event, List<ErrorLoggingService.LogEvent> events) { 835 assertTrue("Remote camera activity never received event: " + event, events != null); 836 for (ErrorLoggingService.LogEvent e : events) { 837 assertFalse("Remote camera activity received invalid event (" + e + 838 ") while waiting for event: " + event, 839 e.getEvent() < 0 || e.getEvent() != event); 840 } 841 assertTrue("Remote camera activity never received event: " + event, events.size() >= 1); 842 assertTrue("Remote camera activity received too many " + event + " events, received: " + 843 events.size(), events.size() == 1); 844 } 845 846 /** 847 * Assert there were no logEvents in the given list. 848 * 849 * @param msg message to show on assertion failure. 850 * @param events {@link List} of events. 851 */ assertNone(String msg, List<ErrorLoggingService.LogEvent> events)852 public static void assertNone(String msg, List<ErrorLoggingService.LogEvent> events) { 853 if (events == null) return; 854 StringBuilder builder = new StringBuilder(msg + "\n"); 855 for (ErrorLoggingService.LogEvent e : events) { 856 builder.append(e).append("\n"); 857 } 858 assertTrue(builder.toString(), events.isEmpty()); 859 } 860 861 /** 862 * Assert array is null or empty. 863 * 864 * @param array array to check. 865 */ assertNotEmpty(T[] array)866 public static <T> void assertNotEmpty(T[] array) { 867 assertNotNull("Array is null.", array); 868 assertFalse("Array is empty: " + Arrays.toString(array), array.length == 0); 869 } 870 871 /** 872 * Given an 'actual' array of objects, check that the objects given in the 'expected' 873 * array are also present in the 'actual' array in the same order. Objects in the 'actual' 874 * array that are not in the 'expected' array are skipped and ignored if they are given 875 * in the 'ignored' array, otherwise this assertion will fail. 876 * 877 * @param actual the ordered array of objects to check. 878 * @param expected the ordered array of expected objects. 879 * @param ignored the array of objects that will be ignored if present in actual, 880 * but not in expected (or are out of order). 881 * @param <T> 882 */ assertOrderedEvents(T[] actual, T[] expected, T[] ignored)883 public static <T> void assertOrderedEvents(T[] actual, T[] expected, T[] ignored) { 884 assertNotNull("List of actual events is null.", actual); 885 assertNotNull("List of expected events is null.", expected); 886 assertNotNull("List of ignored events is null.", ignored); 887 888 int expIndex = 0; 889 int index = 0; 890 for (T i : actual) { 891 // If explicitly expected, move to next 892 if (expIndex < expected.length && Objects.equals(i, expected[expIndex])) { 893 expIndex++; 894 continue; 895 } 896 897 // Fail if not ignored 898 boolean canIgnore = false; 899 for (T j : ignored) { 900 if (Objects.equals(i, j)) { 901 canIgnore = true; 902 break; 903 } 904 905 } 906 907 // Fail if not ignored. 908 assertTrue("Event at index " + index + " in actual array " + 909 Arrays.toString(actual) + " was unexpected: expected array was " + 910 Arrays.toString(expected) + ", ignored array was: " + 911 Arrays.toString(ignored), canIgnore); 912 index++; 913 } 914 assertTrue("Only had " + expIndex + " of " + expected.length + 915 " expected objects in array " + Arrays.toString(actual) + ", expected was " + 916 Arrays.toString(expected), expIndex == expected.length); 917 } 918 } 919