Home | History | Annotate | Download | only in cts
      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 android.app.Activity;
     20 import android.app.ActivityManager;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.hardware.Camera;
     24 import android.hardware.camera2.CameraAccessException;
     25 import android.hardware.camera2.CameraDevice;
     26 import android.hardware.camera2.CameraManager;
     27 import android.hardware.cts.CameraCtsActivity;
     28 import android.os.Handler;
     29 import android.platform.test.annotations.AppModeFull;
     30 import android.test.ActivityInstrumentationTestCase2;
     31 import android.util.Log;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Arrays;
     35 import java.util.List;
     36 import java.util.Objects;
     37 import java.util.concurrent.TimeoutException;
     38 
     39 import static org.mockito.Mockito.*;
     40 
     41 /**
     42  * Tests for multi-process camera usage behavior.
     43  */
     44 @AppModeFull
     45 public class CameraEvictionTest extends ActivityInstrumentationTestCase2<CameraCtsActivity> {
     46 
     47     public static final String TAG = "CameraEvictionTest";
     48 
     49     private static final int OPEN_TIMEOUT = 2000; // Timeout for camera to open (ms).
     50     private static final int SETUP_TIMEOUT = 5000; // Remote camera setup timeout (ms).
     51     private static final int EVICTION_TIMEOUT = 1000; // Remote camera eviction timeout (ms).
     52     private static final int WAIT_TIME = 2000; // Time to wait for process to launch (ms).
     53     private static final int UI_TIMEOUT = 10000; // Time to wait for UI event before timeout (ms).
     54     ErrorLoggingService.ErrorServiceConnection mErrorServiceConnection;
     55 
     56     private ActivityManager mActivityManager;
     57     private Context mContext;
     58     private Camera mCamera;
     59     private CameraDevice mCameraDevice;
     60     private final Object mLock = new Object();
     61     private boolean mCompleted = false;
     62     private int mProcessPid = -1;
     63 
     64     public CameraEvictionTest() {
     65         super(CameraCtsActivity.class);
     66     }
     67 
     68     public static class StateCallbackImpl extends CameraDevice.StateCallback {
     69         CameraDevice mCameraDevice;
     70 
     71         public StateCallbackImpl() {
     72             super();
     73         }
     74 
     75         @Override
     76         public void onOpened(CameraDevice cameraDevice) {
     77             synchronized(this) {
     78                 mCameraDevice = cameraDevice;
     79             }
     80             Log.i(TAG, "CameraDevice onOpened called for main CTS test process.");
     81         }
     82 
     83         @Override
     84         public void onClosed(CameraDevice camera) {
     85             super.onClosed(camera);
     86             synchronized(this) {
     87                 mCameraDevice = null;
     88             }
     89             Log.i(TAG, "CameraDevice onClosed called for main CTS test process.");
     90         }
     91 
     92         @Override
     93         public void onDisconnected(CameraDevice cameraDevice) {
     94             synchronized(this) {
     95                 mCameraDevice = null;
     96             }
     97             Log.i(TAG, "CameraDevice onDisconnected called for main CTS test process.");
     98 
     99         }
    100 
    101         @Override
    102         public void onError(CameraDevice cameraDevice, int i) {
    103             Log.i(TAG, "CameraDevice onError called for main CTS test process with error " +
    104                     "code: " + i);
    105         }
    106 
    107         public synchronized CameraDevice getCameraDevice() {
    108             return mCameraDevice;
    109         }
    110     }
    111 
    112     @Override
    113     protected void setUp() throws Exception {
    114         super.setUp();
    115 
    116         mCompleted = false;
    117         getActivity();
    118         mContext = getInstrumentation().getTargetContext();
    119         System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
    120         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
    121         mErrorServiceConnection = new ErrorLoggingService.ErrorServiceConnection(mContext);
    122         mErrorServiceConnection.start();
    123     }
    124 
    125     @Override
    126     protected void tearDown() throws Exception {
    127         if (mProcessPid != -1) {
    128             android.os.Process.killProcess(mProcessPid);
    129             mProcessPid = -1;
    130         }
    131         if (mErrorServiceConnection != null) {
    132             mErrorServiceConnection.stop();
    133             mErrorServiceConnection = null;
    134         }
    135         if (mCamera != null) {
    136             mCamera.release();
    137             mCamera = null;
    138         }
    139         if (mCameraDevice != null) {
    140             mCameraDevice.close();
    141             mCameraDevice = null;
    142         }
    143         mContext = null;
    144         mActivityManager = null;
    145         super.tearDown();
    146     }
    147 
    148     /**
    149      * Test basic eviction scenarios for the Camera1 API.
    150      */
    151     public void testCamera1ActivityEviction() throws Throwable {
    152         testAPI1ActivityEviction(Camera1Activity.class, "camera1ActivityProcess");
    153     }
    154 
    155     /**
    156      * Test basic eviction scenarios for the Camera2 API.
    157      */
    158     public void testBasicCamera2ActivityEviction() throws Throwable {
    159         CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
    160         assertNotNull(manager);
    161         String[] cameraIds = manager.getCameraIdList();
    162 
    163         if (cameraIds.length == 0) {
    164             Log.i(TAG, "Skipping testBasicCamera2ActivityEviction, device has no cameras.");
    165             return;
    166         }
    167 
    168         assertTrue(mContext.getMainLooper() != null);
    169 
    170         // Setup camera manager
    171         String chosenCamera = cameraIds[0];
    172         Handler cameraHandler = new Handler(mContext.getMainLooper());
    173         final CameraManager.AvailabilityCallback mockAvailCb =
    174                 mock(CameraManager.AvailabilityCallback.class);
    175 
    176         manager.registerAvailabilityCallback(mockAvailCb, cameraHandler);
    177 
    178         Thread.sleep(WAIT_TIME);
    179 
    180         verify(mockAvailCb, times(1)).onCameraAvailable(chosenCamera);
    181         verify(mockAvailCb, never()).onCameraUnavailable(chosenCamera);
    182 
    183         // Setup camera device
    184         final CameraDevice.StateCallback spyStateCb = spy(new StateCallbackImpl());
    185         manager.openCamera(chosenCamera, spyStateCb, cameraHandler);
    186 
    187         verify(spyStateCb, timeout(OPEN_TIMEOUT).times(1)).onOpened(any(CameraDevice.class));
    188         verify(spyStateCb, never()).onClosed(any(CameraDevice.class));
    189         verify(spyStateCb, never()).onDisconnected(any(CameraDevice.class));
    190         verify(spyStateCb, never()).onError(any(CameraDevice.class), anyInt());
    191 
    192         // Open camera from remote process
    193         startRemoteProcess(Camera2Activity.class, "camera2ActivityProcess");
    194 
    195         // Verify that the remote camera was opened correctly
    196         List<ErrorLoggingService.LogEvent> allEvents  = mErrorServiceConnection.getLog(SETUP_TIMEOUT,
    197                 TestConstants.EVENT_CAMERA_CONNECT);
    198         assertNotNull("Camera device not setup in remote process!", allEvents);
    199 
    200         // Filter out relevant events for other camera devices
    201         ArrayList<ErrorLoggingService.LogEvent> events = new ArrayList<>();
    202         for (ErrorLoggingService.LogEvent e : allEvents) {
    203             int eventTag = e.getEvent();
    204             if (eventTag == TestConstants.EVENT_CAMERA_UNAVAILABLE ||
    205                     eventTag == TestConstants.EVENT_CAMERA_CONNECT ||
    206                     eventTag == TestConstants.EVENT_CAMERA_AVAILABLE) {
    207                 if (!Objects.equals(e.getLogText(), chosenCamera)) {
    208                     continue;
    209                 }
    210             }
    211             events.add(e);
    212         }
    213         int[] eventList = new int[events.size()];
    214         int eventIdx = 0;
    215         for (ErrorLoggingService.LogEvent e : events) {
    216             eventList[eventIdx++] = e.getEvent();
    217         }
    218         String[] actualEvents = TestConstants.convertToStringArray(eventList);
    219         String[] expectedEvents = new String[] {TestConstants.EVENT_CAMERA_UNAVAILABLE_STR,
    220                 TestConstants.EVENT_CAMERA_CONNECT_STR};
    221         String[] ignoredEvents = new String[] { TestConstants.EVENT_CAMERA_AVAILABLE_STR,
    222                 TestConstants.EVENT_CAMERA_UNAVAILABLE_STR };
    223         assertOrderedEvents(actualEvents, expectedEvents, ignoredEvents);
    224 
    225         // Verify that the local camera was evicted properly
    226         verify(spyStateCb, times(1)).onDisconnected(any(CameraDevice.class));
    227         verify(spyStateCb, never()).onClosed(any(CameraDevice.class));
    228         verify(spyStateCb, never()).onError(any(CameraDevice.class), anyInt());
    229         verify(spyStateCb, times(1)).onOpened(any(CameraDevice.class));
    230 
    231         // Verify that we can no longer open the camera, as it is held by a higher priority process
    232         try {
    233             manager.openCamera(chosenCamera, spyStateCb, cameraHandler);
    234             fail("Didn't receive exception when trying to open camera held by higher priority " +
    235                     "process.");
    236         } catch(CameraAccessException e) {
    237             assertTrue("Received incorrect camera exception when opening camera: " + e,
    238                     e.getReason() == CameraAccessException.CAMERA_IN_USE);
    239         }
    240 
    241         // Verify that attempting to open the camera didn't cause anything weird to happen in the
    242         // other process.
    243         List<ErrorLoggingService.LogEvent> eventList2 = null;
    244         boolean timeoutExceptionHit = false;
    245         try {
    246             eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT);
    247         } catch (TimeoutException e) {
    248             timeoutExceptionHit = true;
    249         }
    250 
    251         assertNone("Remote camera service received invalid events: ", eventList2);
    252         assertTrue("Remote camera service exited early", timeoutExceptionHit);
    253         android.os.Process.killProcess(mProcessPid);
    254         mProcessPid = -1;
    255         forceCtsActivityToTop();
    256     }
    257 
    258 
    259     /**
    260      * Test basic eviction scenarios for camera used in MediaRecoder
    261      */
    262     public void testMediaRecorderCameraActivityEviction() throws Throwable {
    263         testAPI1ActivityEviction(MediaRecorderCameraActivity.class,
    264                 "mediaRecorderCameraActivityProcess");
    265     }
    266 
    267     /**
    268      * Test basic eviction scenarios for Camera1 API.
    269      *
    270      * This test will open camera, create a higher priority process to run the specified activity,
    271      * open camera again, and verify the right clients are evicted.
    272      *
    273      * @param activityKlass An activity to run in a higher priority process.
    274      * @param processName The process name.
    275      */
    276     private void testAPI1ActivityEviction (java.lang.Class<?> activityKlass, String processName)
    277             throws Throwable {
    278         // Open a camera1 client in the main CTS process's activity
    279         final Camera.ErrorCallback mockErrorCb1 = mock(Camera.ErrorCallback.class);
    280         final boolean[] skip = {false};
    281         runTestOnUiThread(new Runnable() {
    282             @Override
    283             public void run() {
    284                 // Open camera
    285                 mCamera = Camera.open();
    286                 if (mCamera == null) {
    287                     skip[0] = true;
    288                 } else {
    289                     mCamera.setErrorCallback(mockErrorCb1);
    290                 }
    291                 notifyFromUI();
    292             }
    293         });
    294         waitForUI();
    295 
    296         if (skip[0]) {
    297             Log.i(TAG, "Skipping testCamera1ActivityEviction, device has no cameras.");
    298             return;
    299         }
    300 
    301         verifyZeroInteractions(mockErrorCb1);
    302 
    303         startRemoteProcess(activityKlass, processName);
    304 
    305         // Make sure camera was setup correctly in remote activity
    306         List<ErrorLoggingService.LogEvent> events = null;
    307         try {
    308             events = mErrorServiceConnection.getLog(SETUP_TIMEOUT,
    309                     TestConstants.EVENT_CAMERA_CONNECT);
    310         } finally {
    311             if (events != null) assertOnly(TestConstants.EVENT_CAMERA_CONNECT, events);
    312         }
    313 
    314         Thread.sleep(WAIT_TIME);
    315 
    316         // Ensure UI thread has a chance to process callbacks.
    317         runTestOnUiThread(new Runnable() {
    318             @Override
    319             public void run() {
    320                 Log.i("CTS", "Did something on UI thread.");
    321                 notifyFromUI();
    322             }
    323         });
    324         waitForUI();
    325 
    326         // Make sure we received correct callback in error listener, and nothing else
    327         verify(mockErrorCb1, only()).onError(eq(Camera.CAMERA_ERROR_EVICTED), isA(Camera.class));
    328         mCamera = null;
    329 
    330         // Try to open the camera again (even though other TOP process holds the camera).
    331         final boolean[] pass = {false};
    332         runTestOnUiThread(new Runnable() {
    333             @Override
    334             public void run() {
    335                 // Open camera
    336                 try {
    337                     mCamera = Camera.open();
    338                 } catch (RuntimeException e) {
    339                     pass[0] = true;
    340                 }
    341                 notifyFromUI();
    342             }
    343         });
    344         waitForUI();
    345 
    346         assertTrue("Did not receive exception when opening camera while camera is held by a" +
    347                 " higher priority client process.", pass[0]);
    348 
    349         // Verify that attempting to open the camera didn't cause anything weird to happen in the
    350         // other process.
    351         List<ErrorLoggingService.LogEvent> eventList2 = null;
    352         boolean timeoutExceptionHit = false;
    353         try {
    354             eventList2 = mErrorServiceConnection.getLog(EVICTION_TIMEOUT);
    355         } catch (TimeoutException e) {
    356             timeoutExceptionHit = true;
    357         }
    358 
    359         assertNone("Remote camera service received invalid events: ", eventList2);
    360         assertTrue("Remote camera service exited early", timeoutExceptionHit);
    361         android.os.Process.killProcess(mProcessPid);
    362         mProcessPid = -1;
    363         forceCtsActivityToTop();
    364     }
    365 
    366     /**
    367      * Ensure the CTS activity becomes foreground again instead of launcher.
    368      */
    369     private void forceCtsActivityToTop() throws InterruptedException {
    370         Thread.sleep(WAIT_TIME);
    371         Activity a = getActivity();
    372         Intent activityIntent = new Intent(a, CameraCtsActivity.class);
    373         activityIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    374         a.startActivity(activityIntent);
    375         Thread.sleep(WAIT_TIME);
    376     }
    377 
    378     /**
    379      * Block until UI thread calls {@link #notifyFromUI()}.
    380      * @throws InterruptedException
    381      */
    382     private void waitForUI() throws InterruptedException {
    383         synchronized(mLock) {
    384             if (mCompleted) return;
    385             while (!mCompleted) {
    386                 mLock.wait();
    387             }
    388             mCompleted = false;
    389         }
    390     }
    391 
    392     /**
    393      * Wake up any threads waiting in calls to {@link #waitForUI()}.
    394      */
    395     private void notifyFromUI() {
    396         synchronized (mLock) {
    397             mCompleted = true;
    398             mLock.notifyAll();
    399         }
    400     }
    401 
    402     /**
    403      * Return the PID for the process with the given name in the given list of process info.
    404      *
    405      * @param processName the name of the process who's PID to return.
    406      * @param list a list of {@link ActivityManager.RunningAppProcessInfo} to check.
    407      * @return the PID of the given process, or -1 if it was not included in the list.
    408      */
    409     private static int getPid(String processName,
    410                               List<ActivityManager.RunningAppProcessInfo> list) {
    411         for (ActivityManager.RunningAppProcessInfo rai : list) {
    412             if (processName.equals(rai.processName))
    413                 return rai.pid;
    414         }
    415         return -1;
    416     }
    417 
    418     /**
    419      * Start an activity of the given class running in a remote process with the given name.
    420      *
    421      * @param klass the class of the {@link android.app.Activity} to start.
    422      * @param processName the remote activity name.
    423      * @throws InterruptedException
    424      */
    425     public void startRemoteProcess(java.lang.Class<?> klass, String processName)
    426             throws InterruptedException {
    427         // Ensure no running activity process with same name
    428         Activity a = getActivity();
    429         String cameraActivityName = a.getPackageName() + ":" + processName;
    430         List<ActivityManager.RunningAppProcessInfo> list =
    431                 mActivityManager.getRunningAppProcesses();
    432         assertEquals(-1, getPid(cameraActivityName, list));
    433 
    434         // Start activity in a new top foreground process
    435         Intent activityIntent = new Intent(a, klass);
    436         a.startActivity(activityIntent);
    437         Thread.sleep(WAIT_TIME);
    438 
    439         // Fail if activity isn't running
    440         list = mActivityManager.getRunningAppProcesses();
    441         mProcessPid = getPid(cameraActivityName, list);
    442         assertTrue(-1 != mProcessPid);
    443     }
    444 
    445     /**
    446      * Assert that there is only one event of the given type in the event list.
    447      *
    448      * @param event event type to check for.
    449      * @param events {@link List} of events.
    450      */
    451     public static void assertOnly(int event, List<ErrorLoggingService.LogEvent> events) {
    452         assertTrue("Remote camera activity never received event: " + event, events != null);
    453         for (ErrorLoggingService.LogEvent e : events) {
    454             assertFalse("Remote camera activity received invalid event (" + e +
    455                     ") while waiting for event: " + event,
    456                     e.getEvent() < 0 || e.getEvent() != event);
    457         }
    458         assertTrue("Remote camera activity never received event: " + event, events.size() >= 1);
    459         assertTrue("Remote camera activity received too many " + event + " events, received: " +
    460                 events.size(), events.size() == 1);
    461     }
    462 
    463     /**
    464      * Assert there were no logEvents in the given list.
    465      *
    466      * @param msg message to show on assertion failure.
    467      * @param events {@link List} of events.
    468      */
    469     public static void assertNone(String msg, List<ErrorLoggingService.LogEvent> events) {
    470         if (events == null) return;
    471         StringBuilder builder = new StringBuilder(msg + "\n");
    472         for (ErrorLoggingService.LogEvent e : events) {
    473             builder.append(e).append("\n");
    474         }
    475         assertTrue(builder.toString(), events.isEmpty());
    476     }
    477 
    478     /**
    479      * Assert array is null or empty.
    480      *
    481      * @param array array to check.
    482      */
    483     public static <T> void assertNotEmpty(T[] array) {
    484         assertNotNull(array);
    485         assertFalse("Array is empty: " + Arrays.toString(array), array.length == 0);
    486     }
    487 
    488     /**
    489      * Given an 'actual' array of objects, check that the objects given in the 'expected'
    490      * array are also present in the 'actual' array in the same order.  Objects in the 'actual'
    491      * array that are not in the 'expected' array are skipped and ignored if they are given
    492      * in the 'ignored' array, otherwise this assertion will fail.
    493      *
    494      * @param actual the ordered array of objects to check.
    495      * @param expected the ordered array of expected objects.
    496      * @param ignored the array of objects that will be ignored if present in actual,
    497      *                but not in expected (or are out of order).
    498      * @param <T>
    499      */
    500     public static <T> void assertOrderedEvents(T[] actual, T[] expected, T[] ignored) {
    501         assertNotNull(actual);
    502         assertNotNull(expected);
    503         assertNotNull(ignored);
    504 
    505         int expIndex = 0;
    506         int index = 0;
    507         for (T i : actual) {
    508             // If explicitly expected, move to next
    509             if (expIndex < expected.length && Objects.equals(i, expected[expIndex])) {
    510                 expIndex++;
    511                 continue;
    512             }
    513 
    514             // Fail if not ignored
    515             boolean canIgnore = false;
    516             for (T j : ignored) {
    517                 if (Objects.equals(i, j)) {
    518                     canIgnore = true;
    519                     break;
    520                 }
    521 
    522             }
    523 
    524             // Fail if not ignored.
    525             assertTrue("Event at index " + index + " in actual array " +
    526                     Arrays.toString(actual) + " was unexpected: expected array was " +
    527                     Arrays.toString(expected) + ", ignored array was: " +
    528                     Arrays.toString(ignored), canIgnore);
    529             index++;
    530         }
    531         assertTrue("Only had " + expIndex + " of " + expected.length +
    532                 " expected objects in array " + Arrays.toString(actual) + ", expected was " +
    533                 Arrays.toString(expected), expIndex == expected.length);
    534     }
    535 }
    536