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