• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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