• 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 
525     /**
526      * Test camera availability access callback.
527      */
testCamera2AccessCallback()528     public void testCamera2AccessCallback() throws Throwable {
529         int PERMISSION_CALLBACK_TIMEOUT_MS = 2000;
530         CameraManager manager = mContext.getSystemService(CameraManager.class);
531         assertNotNull("Unable to get CameraManager service!", manager);
532         String[] cameraIds = manager.getCameraIdListNoLazy();
533 
534         if (cameraIds.length == 0) {
535             Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras.");
536             return;
537         }
538 
539         assertTrue("Context has no main looper!", mContext.getMainLooper() != null);
540 
541         // Setup camera manager
542         Handler cameraHandler = new Handler(mContext.getMainLooper());
543 
544         final CameraManager.AvailabilityCallback mockAvailCb =
545                 mock(CameraManager.AvailabilityCallback.class);
546         manager.registerAvailabilityCallback(mockAvailCb, cameraHandler);
547 
548         // Remove current task from top of stack. This will impact the camera access
549         // priorities.
550         getActivity().moveTaskToBack(/*nonRoot*/true);
551 
552         verify(mockAvailCb, timeout(
553                 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged();
554 
555         forceCtsActivityToTop();
556 
557         verify(mockAvailCb, timeout(
558                 PERMISSION_CALLBACK_TIMEOUT_MS).atLeastOnce()).onCameraAccessPrioritiesChanged();
559     }
560 
561     /**
562      * Test native camera availability access callback.
563      */
testCamera2NativeAccessCallback()564     public void testCamera2NativeAccessCallback() throws Throwable {
565         int PERMISSION_CALLBACK_TIMEOUT_MS = 2000;
566         CameraManager manager = mContext.getSystemService(CameraManager.class);
567         assertNotNull("Unable to get CameraManager service!", manager);
568         String[] cameraIds = manager.getCameraIdListNoLazy();
569 
570         if (cameraIds.length == 0) {
571             Log.i(TAG, "Skipping testBasicCamera2AccessCallback, device has no cameras.");
572             return;
573         }
574 
575         // Setup camera manager
576         long context = 0;
577         try {
578             context = initializeAvailabilityCallbacksNative();
579             assertTrue("Failed to initialize native availability callbacks", (context != 0));
580 
581             // Remove current task from top of stack. This will impact the camera access
582             // pririorties.
583             getActivity().moveTaskToBack(/*nonRoot*/true);
584 
585             Thread.sleep(PERMISSION_CALLBACK_TIMEOUT_MS);
586             assertTrue("No camera permission access changed callback received",
587                     (getAccessCallbacksCountAndResetNative(context) > 0));
588 
589             forceCtsActivityToTop();
590 
591             assertTrue("No camera permission access changed callback received",
592                     (getAccessCallbacksCountAndResetNative(context) > 0));
593         } finally {
594             if (context != 0) {
595                 releaseAvailabilityCallbacksNative(context);
596             }
597         }
598     }
599 
600     /**
601      * Test basic eviction scenarios for camera used in MediaRecoder
602      */
testMediaRecorderCameraActivityEviction()603     public void testMediaRecorderCameraActivityEviction() throws Throwable {
604         testAPI1ActivityEviction(MediaRecorderCameraActivity.class,
605                 "mediaRecorderCameraActivityProcess");
606     }
607 
608     /**
609      * Test basic eviction scenarios for Camera1 API.
610      *
611      * This test will open camera, create a higher priority process to run the specified activity,
612      * open camera again, and verify the right clients are evicted.
613      *
614      * @param activityKlass An activity to run in a higher priority process.
615      * @param processName The process name.
616      */
testAPI1ActivityEviction(java.lang.Class<?> activityKlass, String processName)617     private void testAPI1ActivityEviction (java.lang.Class<?> activityKlass, String processName)
618             throws Throwable {
619         // Open a camera1 client in the main CTS process's activity
620         final Camera.ErrorCallback mockErrorCb1 = mock(Camera.ErrorCallback.class);
621         final boolean[] skip = {false};
622         runTestOnUiThread(new Runnable() {
623             @Override
624             public void run() {
625                 // Open camera
626                 mCamera = Camera.open();
627                 if (mCamera == null) {
628                     skip[0] = true;
629                 } else {
630                     mCamera.setErrorCallback(mockErrorCb1);
631                 }
632                 notifyFromUI();
633             }
634         });
635         waitForUI();
636 
637         if (skip[0]) {
638             Log.i(TAG, "Skipping testCamera1ActivityEviction, device has no cameras.");
639             return;
640         }
641 
642         verifyZeroInteractions(mockErrorCb1);
643 
644         startRemoteProcess(activityKlass, processName);
645 
646         // Make sure camera was setup correctly in remote activity
647         List<ErrorLoggingService.LogEvent> events = null;
648         try {
649             events = mErrorServiceConnection.getLog(SETUP_TIMEOUT,
650                     TestConstants.EVENT_CAMERA_CONNECT);
651         } finally {
652             if (events != null) assertOnly(TestConstants.EVENT_CAMERA_CONNECT, events);
653         }
654 
655         Thread.sleep(WAIT_TIME);
656 
657         // Ensure UI thread has a chance to process callbacks.
658         runTestOnUiThread(new Runnable() {
659             @Override
660             public void run() {
661                 Log.i("CTS", "Did something on UI thread.");
662                 notifyFromUI();
663             }
664         });
665         waitForUI();
666 
667         // Make sure we received correct callback in error listener, and nothing else
668         verify(mockErrorCb1, only()).onError(eq(Camera.CAMERA_ERROR_EVICTED), isA(Camera.class));
669         mCamera = null;
670 
671         // Try to open the camera again (even though other TOP process holds the camera).
672         final boolean[] pass = {false};
673         runTestOnUiThread(new Runnable() {
674             @Override
675             public void run() {
676                 // Open camera
677                 try {
678                     mCamera = Camera.open();
679                 } catch (RuntimeException e) {
680                     pass[0] = true;
681                 }
682                 notifyFromUI();
683             }
684         });
685         waitForUI();
686 
687         assertTrue("Did not receive exception when opening camera while camera is held by a" +
688                 " higher priority client process.", pass[0]);
689 
690         // Verify that attempting to open the camera didn't cause anything weird to happen in the
691         // other process.
692         List<ErrorLoggingService.LogEvent> eventList2 = null;
693         boolean timeoutExceptionHit = false;
694         try {
695             eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT);
696         } catch (TimeoutException e) {
697             timeoutExceptionHit = true;
698         }
699 
700         assertNone("Remote camera service received invalid events: ", eventList2);
701         assertTrue("Remote camera service exited early", timeoutExceptionHit);
702         android.os.Process.killProcess(mProcessPid);
703         mProcessPid = -1;
704         forceCtsActivityToTop();
705     }
706 
707     /**
708      * Ensure the CTS activity becomes foreground again instead of launcher.
709      */
forceCtsActivityToTop()710     private void forceCtsActivityToTop() throws InterruptedException {
711         Thread.sleep(WAIT_TIME);
712         Activity a = getActivity();
713         Intent activityIntent = new Intent(a, CameraCtsActivity.class);
714         activityIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
715         a.startActivity(activityIntent);
716         Thread.sleep(WAIT_TIME);
717     }
718 
719     /**
720      * Block until UI thread calls {@link #notifyFromUI()}.
721      * @throws InterruptedException
722      */
waitForUI()723     private void waitForUI() throws InterruptedException {
724         synchronized(mLock) {
725             if (mCompleted) return;
726             while (!mCompleted) {
727                 mLock.wait();
728             }
729             mCompleted = false;
730         }
731     }
732 
733     /**
734      * Wake up any threads waiting in calls to {@link #waitForUI()}.
735      */
notifyFromUI()736     private void notifyFromUI() {
737         synchronized (mLock) {
738             mCompleted = true;
739             mLock.notifyAll();
740         }
741     }
742 
743     /**
744      * Return the PID for the process with the given name in the given list of process info.
745      *
746      * @param processName the name of the process who's PID to return.
747      * @param list a list of {@link ActivityManager.RunningAppProcessInfo} to check.
748      * @return the PID of the given process, or -1 if it was not included in the list.
749      */
getPid(String processName, List<ActivityManager.RunningAppProcessInfo> list)750     private static int getPid(String processName,
751                               List<ActivityManager.RunningAppProcessInfo> list) {
752         for (ActivityManager.RunningAppProcessInfo rai : list) {
753             if (processName.equals(rai.processName))
754                 return rai.pid;
755         }
756         return -1;
757     }
758 
759     /**
760      * Start an activity of the given class running in a remote process with the given name.
761      *
762      * @param klass the class of the {@link android.app.Activity} to start.
763      * @param processName the remote activity name.
764      * @throws InterruptedException
765      */
startRemoteProcess(java.lang.Class<?> klass, String processName)766     public void startRemoteProcess(java.lang.Class<?> klass, String processName)
767             throws InterruptedException {
768         startRemoteProcess(klass, processName, false /*splitScreen*/);
769     }
770 
771     /**
772      * Start an activity of the given class running in a remote process with the given name.
773      *
774      * @param klass the class of the {@link android.app.Activity} to start.
775      * @param processName the remote activity name.
776      * @param splitScreen Start new activity in split screen mode.
777      * @throws InterruptedException
778      */
startRemoteProcess(java.lang.Class<?> klass, String processName, boolean splitScreen)779     public void startRemoteProcess(java.lang.Class<?> klass, String processName,
780             boolean splitScreen) throws InterruptedException {
781         // Ensure no running activity process with same name
782         Activity a = getActivity();
783         String cameraActivityName = a.getPackageName() + ":" + processName;
784         List<ActivityManager.RunningAppProcessInfo> list =
785                 mActivityManager.getRunningAppProcesses();
786         assertEquals("Activity " + cameraActivityName + " already running.",
787                 -1, getPid(cameraActivityName, list));
788 
789         // Start activity in a new top foreground process
790         if (splitScreen) {
791             // startActivity(intent) doesn't work with TestTaskOrganizer's split screen,
792             // have to go through shell command.
793             // Also, android:exported must be true for this to work, see:
794             // https://developer.android.com/guide/topics/manifest/activity-element#exported
795             runShellCommand("am start %s/%s", a.getPackageName(), klass.getName());
796             ComponentName secondActivityComponent = new ComponentName(
797                     a.getPackageName(), klass.getName());
798             mWmState.waitForValidState(secondActivityComponent);
799             int taskId = mWmState.getTaskByActivity(secondActivityComponent)
800                     .getTaskId();
801 
802             // Requires @AppModeFull.
803             mTaskOrganizer.putTaskInSplitPrimary(a.getTaskId());
804             ComponentName primaryActivityComponent = new ComponentName(
805                     a.getPackageName(), a.getClass().getName());
806             mWmState.waitForValidState(primaryActivityComponent);
807 
808             // The taskAffinity of the secondary activity must be differ with the taskAffinity
809             // of the primary activity, otherwise it will replace the primary activity instead.
810             mTaskOrganizer.putTaskInSplitSecondary(taskId);
811             mWmState.waitForValidState(secondActivityComponent);
812         } else {
813             Intent activityIntent = new Intent(a, klass);
814             activityIntent.putExtra(TestConstants.EXTRA_IGNORE_CAMERA_ACCESS, true);
815             activityIntent.putExtra(TestConstants.EXTRA_IGNORE_TOP_ACTIVITY_RESUMED, true);
816             activityIntent.putExtra(TestConstants.EXTRA_IGNORE_ACTIVITY_PAUSED, true);
817             a.startActivity(activityIntent);
818             Thread.sleep(WAIT_TIME);
819         }
820 
821         // Fail if activity isn't running
822         list = mActivityManager.getRunningAppProcesses();
823         mProcessPid = getPid(cameraActivityName, list);
824         assertTrue("Activity " + cameraActivityName + " not found in list of running app "
825                 + "processes.", -1 != mProcessPid);
826     }
827 
828     /**
829      * Assert that there is only one event of the given type in the event list.
830      *
831      * @param event event type to check for.
832      * @param events {@link List} of events.
833      */
assertOnly(int event, List<ErrorLoggingService.LogEvent> events)834     public static void assertOnly(int event, List<ErrorLoggingService.LogEvent> events) {
835         assertTrue("Remote camera activity never received event: " + event, events != null);
836         for (ErrorLoggingService.LogEvent e : events) {
837             assertFalse("Remote camera activity received invalid event (" + e +
838                     ") while waiting for event: " + event,
839                     e.getEvent() < 0 || e.getEvent() != event);
840         }
841         assertTrue("Remote camera activity never received event: " + event, events.size() >= 1);
842         assertTrue("Remote camera activity received too many " + event + " events, received: " +
843                 events.size(), events.size() == 1);
844     }
845 
846     /**
847      * Assert there were no logEvents in the given list.
848      *
849      * @param msg message to show on assertion failure.
850      * @param events {@link List} of events.
851      */
assertNone(String msg, List<ErrorLoggingService.LogEvent> events)852     public static void assertNone(String msg, List<ErrorLoggingService.LogEvent> events) {
853         if (events == null) return;
854         StringBuilder builder = new StringBuilder(msg + "\n");
855         for (ErrorLoggingService.LogEvent e : events) {
856             builder.append(e).append("\n");
857         }
858         assertTrue(builder.toString(), events.isEmpty());
859     }
860 
861     /**
862      * Assert array is null or empty.
863      *
864      * @param array array to check.
865      */
assertNotEmpty(T[] array)866     public static <T> void assertNotEmpty(T[] array) {
867         assertNotNull("Array is null.", array);
868         assertFalse("Array is empty: " + Arrays.toString(array), array.length == 0);
869     }
870 
871     /**
872      * Given an 'actual' array of objects, check that the objects given in the 'expected'
873      * array are also present in the 'actual' array in the same order.  Objects in the 'actual'
874      * array that are not in the 'expected' array are skipped and ignored if they are given
875      * in the 'ignored' array, otherwise this assertion will fail.
876      *
877      * @param actual the ordered array of objects to check.
878      * @param expected the ordered array of expected objects.
879      * @param ignored the array of objects that will be ignored if present in actual,
880      *                but not in expected (or are out of order).
881      * @param <T>
882      */
assertOrderedEvents(T[] actual, T[] expected, T[] ignored)883     public static <T> void assertOrderedEvents(T[] actual, T[] expected, T[] ignored) {
884         assertNotNull("List of actual events is null.", actual);
885         assertNotNull("List of expected events is null.", expected);
886         assertNotNull("List of ignored events is null.", ignored);
887 
888         int expIndex = 0;
889         int index = 0;
890         for (T i : actual) {
891             // If explicitly expected, move to next
892             if (expIndex < expected.length && Objects.equals(i, expected[expIndex])) {
893                 expIndex++;
894                 continue;
895             }
896 
897             // Fail if not ignored
898             boolean canIgnore = false;
899             for (T j : ignored) {
900                 if (Objects.equals(i, j)) {
901                     canIgnore = true;
902                     break;
903                 }
904 
905             }
906 
907             // Fail if not ignored.
908             assertTrue("Event at index " + index + " in actual array " +
909                     Arrays.toString(actual) + " was unexpected: expected array was " +
910                     Arrays.toString(expected) + ", ignored array was: " +
911                     Arrays.toString(ignored), canIgnore);
912             index++;
913         }
914         assertTrue("Only had " + expIndex + " of " + expected.length +
915                 " expected objects in array " + Arrays.toString(actual) + ", expected was " +
916                 Arrays.toString(expected), expIndex == expected.length);
917     }
918 }
919