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