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