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