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