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