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 android.app.Activity; 20 import android.app.ActivityManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.hardware.Camera; 24 import android.hardware.camera2.CameraAccessException; 25 import android.hardware.camera2.CameraDevice; 26 import android.hardware.camera2.CameraManager; 27 import android.hardware.cts.CameraCtsActivity; 28 import android.os.Handler; 29 import android.test.ActivityInstrumentationTestCase2; 30 import android.util.Log; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.concurrent.TimeoutException; 37 38 import static org.mockito.Mockito.*; 39 40 /** 41 * Tests for multi-process camera usage behavior. 42 */ 43 public class CameraEvictionTest extends ActivityInstrumentationTestCase2<CameraCtsActivity> { 44 45 public static final String TAG = "CameraEvictionTest"; 46 47 private static final int OPEN_TIMEOUT = 2000; // Timeout for camera to open (ms). 48 private static final int SETUP_TIMEOUT = 5000; // Remote camera setup timeout (ms). 49 private static final int EVICTION_TIMEOUT = 1000; // Remote camera eviction timeout (ms). 50 private static final int WAIT_TIME = 2000; // Time to wait for process to launch (ms). 51 private static final int UI_TIMEOUT = 10000; // Time to wait for UI event before timeout (ms). 52 ErrorLoggingService.ErrorServiceConnection mErrorServiceConnection; 53 54 private ActivityManager mActivityManager; 55 private Context mContext; 56 private Camera mCamera; 57 private CameraDevice mCameraDevice; 58 private final Object mLock = new Object(); 59 private boolean mCompleted = false; 60 private int mProcessPid = -1; 61 62 /** Load jni on initialization */ 63 static { 64 System.loadLibrary("ctscamera2_jni"); 65 } 66 initializeAvailabilityCallbacksNative()67 private static native long initializeAvailabilityCallbacksNative(); getAccessCallbacksCountAndResetNative(long context)68 private static native int getAccessCallbacksCountAndResetNative(long context); releaseAvailabilityCallbacksNative(long context)69 private static native long releaseAvailabilityCallbacksNative(long context); 70 CameraEvictionTest()71 public CameraEvictionTest() { 72 super(CameraCtsActivity.class); 73 } 74 75 public static class StateCallbackImpl extends CameraDevice.StateCallback { 76 CameraDevice mCameraDevice; 77 StateCallbackImpl()78 public StateCallbackImpl() { 79 super(); 80 } 81 82 @Override onOpened(CameraDevice cameraDevice)83 public void onOpened(CameraDevice cameraDevice) { 84 synchronized(this) { 85 mCameraDevice = cameraDevice; 86 } 87 Log.i(TAG, "CameraDevice onOpened called for main CTS test process."); 88 } 89 90 @Override onClosed(CameraDevice camera)91 public void onClosed(CameraDevice camera) { 92 super.onClosed(camera); 93 synchronized(this) { 94 mCameraDevice = null; 95 } 96 Log.i(TAG, "CameraDevice onClosed called for main CTS test process."); 97 } 98 99 @Override onDisconnected(CameraDevice cameraDevice)100 public void onDisconnected(CameraDevice cameraDevice) { 101 synchronized(this) { 102 mCameraDevice = null; 103 } 104 Log.i(TAG, "CameraDevice onDisconnected called for main CTS test process."); 105 106 } 107 108 @Override onError(CameraDevice cameraDevice, int i)109 public void onError(CameraDevice cameraDevice, int i) { 110 Log.i(TAG, "CameraDevice onError called for main CTS test process with error " + 111 "code: " + i); 112 } 113 getCameraDevice()114 public synchronized CameraDevice getCameraDevice() { 115 return mCameraDevice; 116 } 117 } 118 119 @Override setUp()120 protected void setUp() throws Exception { 121 super.setUp(); 122 123 mCompleted = false; 124 getActivity(); 125 mContext = getInstrumentation().getTargetContext(); 126 System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString()); 127 mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 128 mErrorServiceConnection = new ErrorLoggingService.ErrorServiceConnection(mContext); 129 mErrorServiceConnection.start(); 130 } 131 132 @Override tearDown()133 protected void tearDown() throws Exception { 134 if (mProcessPid != -1) { 135 android.os.Process.killProcess(mProcessPid); 136 mProcessPid = -1; 137 } 138 if (mErrorServiceConnection != null) { 139 mErrorServiceConnection.stop(); 140 mErrorServiceConnection = null; 141 } 142 if (mCamera != null) { 143 mCamera.release(); 144 mCamera = null; 145 } 146 if (mCameraDevice != null) { 147 mCameraDevice.close(); 148 mCameraDevice = null; 149 } 150 mContext = null; 151 mActivityManager = null; 152 super.tearDown(); 153 } 154 155 /** 156 * Test basic eviction scenarios for the Camera1 API. 157 */ testCamera1ActivityEviction()158 public void testCamera1ActivityEviction() throws Throwable { 159 testAPI1ActivityEviction(Camera1Activity.class, "camera1ActivityProcess"); 160 } 161 162 /** 163 * Test basic eviction scenarios for the Camera2 API. 164 */ testBasicCamera2ActivityEviction()165 public void testBasicCamera2ActivityEviction() throws Throwable { 166 CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 167 assertNotNull(manager); 168 String[] cameraIds = manager.getCameraIdList(); 169 170 if (cameraIds.length == 0) { 171 Log.i(TAG, "Skipping testBasicCamera2ActivityEviction, device has no cameras."); 172 return; 173 } 174 175 assertTrue(mContext.getMainLooper() != null); 176 177 // Setup camera manager 178 String chosenCamera = cameraIds[0]; 179 Handler cameraHandler = new Handler(mContext.getMainLooper()); 180 final CameraManager.AvailabilityCallback mockAvailCb = 181 mock(CameraManager.AvailabilityCallback.class); 182 183 manager.registerAvailabilityCallback(mockAvailCb, cameraHandler); 184 185 Thread.sleep(WAIT_TIME); 186 187 verify(mockAvailCb, times(1)).onCameraAvailable(chosenCamera); 188 verify(mockAvailCb, never()).onCameraUnavailable(chosenCamera); 189 190 // Setup camera device 191 final CameraDevice.StateCallback spyStateCb = spy(new StateCallbackImpl()); 192 manager.openCamera(chosenCamera, spyStateCb, cameraHandler); 193 194 verify(spyStateCb, timeout(OPEN_TIMEOUT).times(1)).onOpened(any(CameraDevice.class)); 195 verify(spyStateCb, never()).onClosed(any(CameraDevice.class)); 196 verify(spyStateCb, never()).onDisconnected(any(CameraDevice.class)); 197 verify(spyStateCb, never()).onError(any(CameraDevice.class), anyInt()); 198 199 // Open camera from remote process 200 startRemoteProcess(Camera2Activity.class, "camera2ActivityProcess"); 201 202 // Verify that the remote camera was opened correctly 203 List<ErrorLoggingService.LogEvent> allEvents = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 204 TestConstants.EVENT_CAMERA_CONNECT); 205 assertNotNull("Camera device not setup in remote process!", allEvents); 206 207 // Filter out relevant events for other camera devices 208 ArrayList<ErrorLoggingService.LogEvent> events = new ArrayList<>(); 209 for (ErrorLoggingService.LogEvent e : allEvents) { 210 int eventTag = e.getEvent(); 211 if (eventTag == TestConstants.EVENT_CAMERA_UNAVAILABLE || 212 eventTag == TestConstants.EVENT_CAMERA_CONNECT || 213 eventTag == TestConstants.EVENT_CAMERA_AVAILABLE) { 214 if (!Objects.equals(e.getLogText(), chosenCamera)) { 215 continue; 216 } 217 } 218 events.add(e); 219 } 220 int[] eventList = new int[events.size()]; 221 int eventIdx = 0; 222 for (ErrorLoggingService.LogEvent e : events) { 223 eventList[eventIdx++] = e.getEvent(); 224 } 225 String[] actualEvents = TestConstants.convertToStringArray(eventList); 226 String[] expectedEvents = new String[] {TestConstants.EVENT_CAMERA_UNAVAILABLE_STR, 227 TestConstants.EVENT_CAMERA_CONNECT_STR}; 228 String[] ignoredEvents = new String[] { TestConstants.EVENT_CAMERA_AVAILABLE_STR, 229 TestConstants.EVENT_CAMERA_UNAVAILABLE_STR }; 230 assertOrderedEvents(actualEvents, expectedEvents, ignoredEvents); 231 232 // Verify that the local camera was evicted properly 233 verify(spyStateCb, times(1)).onDisconnected(any(CameraDevice.class)); 234 verify(spyStateCb, never()).onClosed(any(CameraDevice.class)); 235 verify(spyStateCb, never()).onError(any(CameraDevice.class), anyInt()); 236 verify(spyStateCb, times(1)).onOpened(any(CameraDevice.class)); 237 238 // Verify that we can no longer open the camera, as it is held by a higher priority process 239 try { 240 manager.openCamera(chosenCamera, spyStateCb, cameraHandler); 241 fail("Didn't receive exception when trying to open camera held by higher priority " + 242 "process."); 243 } catch(CameraAccessException e) { 244 assertTrue("Received incorrect camera exception when opening camera: " + e, 245 e.getReason() == CameraAccessException.CAMERA_IN_USE); 246 } 247 248 // Verify that attempting to open the camera didn't cause anything weird to happen in the 249 // other process. 250 List<ErrorLoggingService.LogEvent> eventList2 = null; 251 boolean timeoutExceptionHit = false; 252 try { 253 eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT); 254 } catch (TimeoutException e) { 255 timeoutExceptionHit = true; 256 } 257 258 assertNone("Remote camera service received invalid events: ", eventList2); 259 assertTrue("Remote camera service exited early", timeoutExceptionHit); 260 android.os.Process.killProcess(mProcessPid); 261 mProcessPid = -1; 262 forceCtsActivityToTop(); 263 } 264 265 /** 266 * Test camera availability access callback. 267 */ testCamera2AccessCallback()268 public void testCamera2AccessCallback() throws Throwable { 269 int PERMISSION_CALLBACK_TIMEOUT_MS = 2000; 270 CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 271 assertNotNull(manager); 272 String[] cameraIds = manager.getCameraIdList(); 273 274 if (cameraIds.length == 0) { 275 Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras."); 276 return; 277 } 278 279 assertTrue(mContext.getMainLooper() != null); 280 281 // Setup camera manager 282 Handler cameraHandler = new Handler(mContext.getMainLooper()); 283 284 final CameraManager.AvailabilityCallback mockAvailCb = 285 mock(CameraManager.AvailabilityCallback.class); 286 manager.registerAvailabilityCallback(mockAvailCb, cameraHandler); 287 288 // Remove current task from top of stack. This will impact the camera access 289 // pririorties. 290 getActivity().moveTaskToBack(/*nonRoot*/true); 291 292 verify(mockAvailCb, timeout( 293 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged(); 294 295 forceCtsActivityToTop(); 296 297 verify(mockAvailCb, timeout( 298 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged(); 299 } 300 301 /** 302 * Test native camera availability access callback. 303 */ testCamera2NativeAccessCallback()304 public void testCamera2NativeAccessCallback() throws Throwable { 305 int PERMISSION_CALLBACK_TIMEOUT_MS = 2000; 306 CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 307 assertNotNull(manager); 308 String[] cameraIds = manager.getCameraIdList(); 309 310 if (cameraIds.length == 0) { 311 Log.i(TAG, "Skipping testBasicCamera2AccessCallback, device has no cameras."); 312 return; 313 } 314 315 // Setup camera manager 316 long context = 0; 317 try { 318 context = initializeAvailabilityCallbacksNative(); 319 assertTrue("Failed to initialize native availability callbacks", (context != 0)); 320 321 // Remove current task from top of stack. This will impact the camera access 322 // pririorties. 323 getActivity().moveTaskToBack(/*nonRoot*/true); 324 325 Thread.sleep(PERMISSION_CALLBACK_TIMEOUT_MS); 326 assertTrue("No camera permission access changed callback received", 327 (getAccessCallbacksCountAndResetNative(context) > 0)); 328 329 forceCtsActivityToTop(); 330 331 assertTrue("No camera permission access changed callback received", 332 (getAccessCallbacksCountAndResetNative(context) > 0)); 333 } finally { 334 if (context != 0) { 335 releaseAvailabilityCallbacksNative(context); 336 } 337 } 338 } 339 340 /** 341 * Test basic eviction scenarios for camera used in MediaRecoder 342 */ testMediaRecorderCameraActivityEviction()343 public void testMediaRecorderCameraActivityEviction() throws Throwable { 344 testAPI1ActivityEviction(MediaRecorderCameraActivity.class, 345 "mediaRecorderCameraActivityProcess"); 346 } 347 348 /** 349 * Test basic eviction scenarios for Camera1 API. 350 * 351 * This test will open camera, create a higher priority process to run the specified activity, 352 * open camera again, and verify the right clients are evicted. 353 * 354 * @param activityKlass An activity to run in a higher priority process. 355 * @param processName The process name. 356 */ testAPI1ActivityEviction(java.lang.Class<?> activityKlass, String processName)357 private void testAPI1ActivityEviction (java.lang.Class<?> activityKlass, String processName) 358 throws Throwable { 359 // Open a camera1 client in the main CTS process's activity 360 final Camera.ErrorCallback mockErrorCb1 = mock(Camera.ErrorCallback.class); 361 final boolean[] skip = {false}; 362 runTestOnUiThread(new Runnable() { 363 @Override 364 public void run() { 365 // Open camera 366 mCamera = Camera.open(); 367 if (mCamera == null) { 368 skip[0] = true; 369 } else { 370 mCamera.setErrorCallback(mockErrorCb1); 371 } 372 notifyFromUI(); 373 } 374 }); 375 waitForUI(); 376 377 if (skip[0]) { 378 Log.i(TAG, "Skipping testCamera1ActivityEviction, device has no cameras."); 379 return; 380 } 381 382 verifyZeroInteractions(mockErrorCb1); 383 384 startRemoteProcess(activityKlass, processName); 385 386 // Make sure camera was setup correctly in remote activity 387 List<ErrorLoggingService.LogEvent> events = null; 388 try { 389 events = mErrorServiceConnection.getLog(SETUP_TIMEOUT, 390 TestConstants.EVENT_CAMERA_CONNECT); 391 } finally { 392 if (events != null) assertOnly(TestConstants.EVENT_CAMERA_CONNECT, events); 393 } 394 395 Thread.sleep(WAIT_TIME); 396 397 // Ensure UI thread has a chance to process callbacks. 398 runTestOnUiThread(new Runnable() { 399 @Override 400 public void run() { 401 Log.i("CTS", "Did something on UI thread."); 402 notifyFromUI(); 403 } 404 }); 405 waitForUI(); 406 407 // Make sure we received correct callback in error listener, and nothing else 408 verify(mockErrorCb1, only()).onError(eq(Camera.CAMERA_ERROR_EVICTED), isA(Camera.class)); 409 mCamera = null; 410 411 // Try to open the camera again (even though other TOP process holds the camera). 412 final boolean[] pass = {false}; 413 runTestOnUiThread(new Runnable() { 414 @Override 415 public void run() { 416 // Open camera 417 try { 418 mCamera = Camera.open(); 419 } catch (RuntimeException e) { 420 pass[0] = true; 421 } 422 notifyFromUI(); 423 } 424 }); 425 waitForUI(); 426 427 assertTrue("Did not receive exception when opening camera while camera is held by a" + 428 " higher priority client process.", pass[0]); 429 430 // Verify that attempting to open the camera didn't cause anything weird to happen in the 431 // other process. 432 List<ErrorLoggingService.LogEvent> eventList2 = null; 433 boolean timeoutExceptionHit = false; 434 try { 435 eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT); 436 } catch (TimeoutException e) { 437 timeoutExceptionHit = true; 438 } 439 440 assertNone("Remote camera service received invalid events: ", eventList2); 441 assertTrue("Remote camera service exited early", timeoutExceptionHit); 442 android.os.Process.killProcess(mProcessPid); 443 mProcessPid = -1; 444 forceCtsActivityToTop(); 445 } 446 447 /** 448 * Ensure the CTS activity becomes foreground again instead of launcher. 449 */ forceCtsActivityToTop()450 private void forceCtsActivityToTop() throws InterruptedException { 451 Thread.sleep(WAIT_TIME); 452 Activity a = getActivity(); 453 Intent activityIntent = new Intent(a, CameraCtsActivity.class); 454 activityIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 455 a.startActivity(activityIntent); 456 Thread.sleep(WAIT_TIME); 457 } 458 459 /** 460 * Block until UI thread calls {@link #notifyFromUI()}. 461 * @throws InterruptedException 462 */ waitForUI()463 private void waitForUI() throws InterruptedException { 464 synchronized(mLock) { 465 if (mCompleted) return; 466 while (!mCompleted) { 467 mLock.wait(); 468 } 469 mCompleted = false; 470 } 471 } 472 473 /** 474 * Wake up any threads waiting in calls to {@link #waitForUI()}. 475 */ notifyFromUI()476 private void notifyFromUI() { 477 synchronized (mLock) { 478 mCompleted = true; 479 mLock.notifyAll(); 480 } 481 } 482 483 /** 484 * Return the PID for the process with the given name in the given list of process info. 485 * 486 * @param processName the name of the process who's PID to return. 487 * @param list a list of {@link ActivityManager.RunningAppProcessInfo} to check. 488 * @return the PID of the given process, or -1 if it was not included in the list. 489 */ getPid(String processName, List<ActivityManager.RunningAppProcessInfo> list)490 private static int getPid(String processName, 491 List<ActivityManager.RunningAppProcessInfo> list) { 492 for (ActivityManager.RunningAppProcessInfo rai : list) { 493 if (processName.equals(rai.processName)) 494 return rai.pid; 495 } 496 return -1; 497 } 498 499 /** 500 * Start an activity of the given class running in a remote process with the given name. 501 * 502 * @param klass the class of the {@link android.app.Activity} to start. 503 * @param processName the remote activity name. 504 * @throws InterruptedException 505 */ startRemoteProcess(java.lang.Class<?> klass, String processName)506 public void startRemoteProcess(java.lang.Class<?> klass, String processName) 507 throws InterruptedException { 508 // Ensure no running activity process with same name 509 Activity a = getActivity(); 510 String cameraActivityName = a.getPackageName() + ":" + processName; 511 List<ActivityManager.RunningAppProcessInfo> list = 512 mActivityManager.getRunningAppProcesses(); 513 assertEquals(-1, getPid(cameraActivityName, list)); 514 515 // Start activity in a new top foreground process 516 Intent activityIntent = new Intent(a, klass); 517 a.startActivity(activityIntent); 518 Thread.sleep(WAIT_TIME); 519 520 // Fail if activity isn't running 521 list = mActivityManager.getRunningAppProcesses(); 522 mProcessPid = getPid(cameraActivityName, list); 523 assertTrue(-1 != mProcessPid); 524 } 525 526 /** 527 * Assert that there is only one event of the given type in the event list. 528 * 529 * @param event event type to check for. 530 * @param events {@link List} of events. 531 */ assertOnly(int event, List<ErrorLoggingService.LogEvent> events)532 public static void assertOnly(int event, List<ErrorLoggingService.LogEvent> events) { 533 assertTrue("Remote camera activity never received event: " + event, events != null); 534 for (ErrorLoggingService.LogEvent e : events) { 535 assertFalse("Remote camera activity received invalid event (" + e + 536 ") while waiting for event: " + event, 537 e.getEvent() < 0 || e.getEvent() != event); 538 } 539 assertTrue("Remote camera activity never received event: " + event, events.size() >= 1); 540 assertTrue("Remote camera activity received too many " + event + " events, received: " + 541 events.size(), events.size() == 1); 542 } 543 544 /** 545 * Assert there were no logEvents in the given list. 546 * 547 * @param msg message to show on assertion failure. 548 * @param events {@link List} of events. 549 */ assertNone(String msg, List<ErrorLoggingService.LogEvent> events)550 public static void assertNone(String msg, List<ErrorLoggingService.LogEvent> events) { 551 if (events == null) return; 552 StringBuilder builder = new StringBuilder(msg + "\n"); 553 for (ErrorLoggingService.LogEvent e : events) { 554 builder.append(e).append("\n"); 555 } 556 assertTrue(builder.toString(), events.isEmpty()); 557 } 558 559 /** 560 * Assert array is null or empty. 561 * 562 * @param array array to check. 563 */ assertNotEmpty(T[] array)564 public static <T> void assertNotEmpty(T[] array) { 565 assertNotNull(array); 566 assertFalse("Array is empty: " + Arrays.toString(array), array.length == 0); 567 } 568 569 /** 570 * Given an 'actual' array of objects, check that the objects given in the 'expected' 571 * array are also present in the 'actual' array in the same order. Objects in the 'actual' 572 * array that are not in the 'expected' array are skipped and ignored if they are given 573 * in the 'ignored' array, otherwise this assertion will fail. 574 * 575 * @param actual the ordered array of objects to check. 576 * @param expected the ordered array of expected objects. 577 * @param ignored the array of objects that will be ignored if present in actual, 578 * but not in expected (or are out of order). 579 * @param <T> 580 */ assertOrderedEvents(T[] actual, T[] expected, T[] ignored)581 public static <T> void assertOrderedEvents(T[] actual, T[] expected, T[] ignored) { 582 assertNotNull(actual); 583 assertNotNull(expected); 584 assertNotNull(ignored); 585 586 int expIndex = 0; 587 int index = 0; 588 for (T i : actual) { 589 // If explicitly expected, move to next 590 if (expIndex < expected.length && Objects.equals(i, expected[expIndex])) { 591 expIndex++; 592 continue; 593 } 594 595 // Fail if not ignored 596 boolean canIgnore = false; 597 for (T j : ignored) { 598 if (Objects.equals(i, j)) { 599 canIgnore = true; 600 break; 601 } 602 603 } 604 605 // Fail if not ignored. 606 assertTrue("Event at index " + index + " in actual array " + 607 Arrays.toString(actual) + " was unexpected: expected array was " + 608 Arrays.toString(expected) + ", ignored array was: " + 609 Arrays.toString(ignored), canIgnore); 610 index++; 611 } 612 assertTrue("Only had " + expIndex + " of " + expected.length + 613 " expected objects in array " + Arrays.toString(actual) + ", expected was " + 614 Arrays.toString(expected), expIndex == expected.length); 615 } 616 } 617