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