Home | History | Annotate | Download | only in testcases
      1 /*
      2  * Copyright (C) 2014 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.camera2.cts.testcases;
     18 
     19 import static android.hardware.camera2.cts.CameraTestUtils.*;
     20 import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
     21 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
     22 
     23 import android.content.Context;
     24 import android.content.res.Configuration;
     25 import android.graphics.Matrix;
     26 import android.graphics.RectF;
     27 import android.graphics.SurfaceTexture;
     28 import android.hardware.camera2.CameraCaptureSession;
     29 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
     30 import android.hardware.camera2.CameraDevice;
     31 import android.hardware.camera2.CameraManager;
     32 import android.hardware.camera2.CaptureRequest;
     33 import android.hardware.camera2.cts.Camera2MultiViewCtsActivity;
     34 import android.hardware.camera2.cts.helpers.StaticMetadata;
     35 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
     36 import android.os.ConditionVariable;
     37 import android.os.Handler;
     38 import android.os.HandlerThread;
     39 import android.os.Looper;
     40 import android.os.SystemClock;
     41 import android.test.ActivityInstrumentationTestCase2;
     42 import android.util.Log;
     43 import android.util.Size;
     44 import android.view.Surface;
     45 import android.view.TextureView;
     46 
     47 import com.android.ex.camera2.blocking.BlockingCameraManager;
     48 import com.android.ex.camera2.blocking.BlockingSessionCallback;
     49 import com.android.ex.camera2.blocking.BlockingStateCallback;
     50 
     51 import junit.framework.Assert;
     52 
     53 import java.util.List;
     54 import java.util.HashMap;
     55 
     56 /**
     57  * Camera2 test case base class by using mixed SurfaceView and TextureView as rendering target.
     58  */
     59 public class Camera2MultiViewTestCase extends
     60         ActivityInstrumentationTestCase2<Camera2MultiViewCtsActivity> {
     61     private static final String TAG = "MultiViewTestCase";
     62     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     63 
     64 
     65     private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
     66 
     67     protected TextureView[] mTextureView = new TextureView[2];
     68     protected String[] mCameraIds;
     69     protected Handler mHandler;
     70 
     71     private CameraManager mCameraManager;
     72     private BlockingStateCallback mCameraListener;
     73     private HandlerThread mHandlerThread;
     74     private Context mContext;
     75 
     76     private CameraHolder[] mCameraHolders;
     77     private HashMap<String, Integer> mCameraIdMap;
     78 
     79     public Camera2MultiViewTestCase() {
     80         super(Camera2MultiViewCtsActivity.class);
     81     }
     82 
     83     @Override
     84     protected void setUp() throws Exception {
     85         super.setUp();
     86         mContext = getActivity();
     87         assertNotNull("Unable to get activity", mContext);
     88         mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
     89         assertNotNull("Unable to get CameraManager", mCameraManager);
     90         mCameraIds = mCameraManager.getCameraIdList();
     91         assertNotNull("Unable to get camera ids", mCameraIds);
     92         mHandlerThread = new HandlerThread(TAG);
     93         mHandlerThread.start();
     94         mHandler = new Handler(mHandlerThread.getLooper());
     95         mCameraListener = new BlockingStateCallback();
     96         Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mContext;
     97         mTextureView[0] = activity.getTextureView(0);
     98         mTextureView[1] = activity.getTextureView(1);
     99         assertNotNull("Unable to get texture view", mTextureView);
    100         mCameraIdMap = new HashMap<String, Integer>();
    101         int numCameras = mCameraIds.length;
    102         mCameraHolders = new CameraHolder[numCameras];
    103         for (int i = 0; i < numCameras; i++) {
    104             mCameraHolders[i] = new CameraHolder(mCameraIds[i]);
    105             mCameraIdMap.put(mCameraIds[i], i);
    106         }
    107     }
    108 
    109     @Override
    110     protected void tearDown() throws Exception {
    111         mHandlerThread.quitSafely();
    112         mHandler = null;
    113         mCameraListener = null;
    114         for (CameraHolder camera : mCameraHolders) {
    115             if (camera.isOpenned()) {
    116                 camera.close();
    117                 camera = null;
    118             }
    119         }
    120         super.tearDown();
    121     }
    122 
    123     /**
    124      * Update preview TextureView rotation to accommodate discrepancy between preview
    125      * buffer and the view window orientation.
    126      *
    127      * Assumptions:
    128      * - Aspect ratio for the sensor buffers is in landscape orientation,
    129      * - Dimensions of buffers received are rotated to the natural device orientation.
    130      * - The contents of each buffer are rotated by the inverse of the display rotation.
    131      * - Surface scales the buffer to fit the current view bounds.
    132      * TODO: Make this method works for all orientations
    133      *
    134      */
    135     protected void updatePreviewDisplayRotation(Size previewSize, TextureView textureView) {
    136         int rotationDegrees = 0;
    137         Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mContext;
    138         int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    139         Configuration config = activity.getResources().getConfiguration();
    140 
    141         // Get UI display rotation
    142         switch (displayRotation) {
    143             case Surface.ROTATION_0:
    144                 rotationDegrees = 0;
    145                 break;
    146             case Surface.ROTATION_90:
    147                 rotationDegrees = 90;
    148             break;
    149             case Surface.ROTATION_180:
    150                 rotationDegrees = 180;
    151             break;
    152             case Surface.ROTATION_270:
    153                 rotationDegrees = 270;
    154             break;
    155         }
    156 
    157         // Get device natural orientation
    158         int deviceOrientation = Configuration.ORIENTATION_PORTRAIT;
    159         if ((rotationDegrees % 180 == 0 &&
    160                 config.orientation == Configuration.ORIENTATION_LANDSCAPE) ||
    161                 ((rotationDegrees % 180 != 0 &&
    162                 config.orientation == Configuration.ORIENTATION_PORTRAIT))) {
    163             deviceOrientation = Configuration.ORIENTATION_LANDSCAPE;
    164         }
    165 
    166         // Rotate the buffer dimensions if device orientation is portrait.
    167         int effectiveWidth = previewSize.getWidth();
    168         int effectiveHeight = previewSize.getHeight();
    169         if (deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
    170             effectiveWidth = previewSize.getHeight();
    171             effectiveHeight = previewSize.getWidth();
    172         }
    173 
    174         // Find and center view rect and buffer rect
    175         Matrix transformMatrix =  textureView.getTransform(null);
    176         int viewWidth = textureView.getWidth();
    177         int viewHeight = textureView.getHeight();
    178         RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
    179         RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
    180         float centerX = viewRect.centerX();
    181         float centerY = viewRect.centerY();
    182         bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
    183 
    184         // Undo ScaleToFit.FILL done by the surface
    185         transformMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
    186 
    187         // Rotate buffer contents to proper orientation
    188         transformMatrix.postRotate((360 - rotationDegrees) % 360, centerX, centerY);
    189         if ((rotationDegrees % 180) == 90) {
    190             int temp = effectiveWidth;
    191             effectiveWidth = effectiveHeight;
    192             effectiveHeight = temp;
    193         }
    194 
    195         // Scale to fit view, cropping the longest dimension
    196         float scale =
    197                 Math.max(viewWidth / (float) effectiveWidth, viewHeight / (float) effectiveHeight);
    198         transformMatrix.postScale(scale, scale, centerX, centerY);
    199 
    200         Handler handler = new Handler(Looper.getMainLooper());
    201         class TransformUpdater implements Runnable {
    202             TextureView mView;
    203             Matrix mTransformMatrix;
    204             TransformUpdater(TextureView view, Matrix matrix) {
    205                 mView = view;
    206                 mTransformMatrix = matrix;
    207             }
    208 
    209             @Override
    210             public void run() {
    211                 mView.setTransform(mTransformMatrix);
    212             }
    213         }
    214         handler.post(new TransformUpdater(textureView, transformMatrix));
    215     }
    216 
    217     protected void openCamera(String cameraId) throws Exception {
    218         CameraHolder camera = getCameraHolder(cameraId);
    219         assertFalse("Camera has already opened", camera.isOpenned());
    220         camera.open();
    221         return;
    222     }
    223 
    224     protected void closeCamera(String cameraId) throws Exception {
    225         CameraHolder camera = getCameraHolder(cameraId);
    226         camera.close();
    227     }
    228 
    229     protected void startPreview(
    230             String cameraId, List<Surface> outputSurfaces, CaptureCallback listener)
    231             throws Exception {
    232         CameraHolder camera = getCameraHolder(cameraId);
    233         assertTrue("Camera " + cameraId + " is not openned", camera.isOpenned());
    234         camera.startPreview(outputSurfaces, listener);
    235     }
    236 
    237     protected void stopPreview(String cameraId) throws Exception {
    238         CameraHolder camera = getCameraHolder(cameraId);
    239         assertTrue("Camera " + cameraId + " preview is not running", camera.isPreviewStarted());
    240         camera.stopPreview();
    241     }
    242 
    243     protected StaticMetadata getStaticInfo(String cameraId) {
    244         CameraHolder camera = getCameraHolder(cameraId);
    245         assertTrue("Camera is not openned", camera.isOpenned());
    246         return camera.getStaticInfo();
    247     }
    248 
    249     protected List<Size> getOrderedPreviewSizes(String cameraId) {
    250         CameraHolder camera = getCameraHolder(cameraId);
    251         assertTrue("Camera is not openned", camera.isOpenned());
    252         return camera.getOrderedPreviewSizes();
    253     }
    254 
    255     /**
    256      * Wait until the SurfaceTexture available from the TextureView, then return it.
    257      * Return null if the wait times out.
    258      *
    259      * @param timeOutMs The timeout value for the wait
    260      * @return The available SurfaceTexture, return null if the wait times out.
    261      */
    262     protected SurfaceTexture getAvailableSurfaceTexture(long timeOutMs, TextureView view) {
    263         long waitTime = timeOutMs;
    264 
    265         while (!view.isAvailable() && waitTime > 0) {
    266             long startTimeMs = SystemClock.elapsedRealtime();
    267             SystemClock.sleep(SHORT_SLEEP_WAIT_TIME_MS);
    268             waitTime -= (SystemClock.elapsedRealtime() - startTimeMs);
    269         }
    270 
    271         if (view.isAvailable()) {
    272             return view.getSurfaceTexture();
    273         } else {
    274             Log.w(TAG, "Wait for SurfaceTexture available timed out after " + timeOutMs + "ms");
    275             return null;
    276         }
    277     }
    278 
    279     public static class CameraPreviewListener implements TextureView.SurfaceTextureListener {
    280         private boolean mFirstPreviewAvailable = false;
    281         private final ConditionVariable mPreviewDone = new ConditionVariable();
    282 
    283         @Override
    284         public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    285             // Ignored. The SurfaceTexture is polled by getAvailableSurfaceTexture.
    286         }
    287 
    288         @Override
    289         public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    290             // Ignored. The CameraDevice should already know the changed size.
    291         }
    292 
    293         @Override
    294         public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    295             /**
    296              * Return true, assume that client detaches the surface before it is
    297              * destroyed. For example, CameraDevice should detach this surface when
    298              * stopping preview. No need to release the SurfaceTexture here as it
    299              * is released by TextureView after onSurfaceTextureDestroyed is called.
    300              */
    301             Log.i(TAG, "onSurfaceTextureDestroyed called.");
    302             return true;
    303         }
    304 
    305         @Override
    306         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    307             // Invoked every time there's a new Camera preview frame
    308             if (!mFirstPreviewAvailable) {
    309                 mFirstPreviewAvailable = true;
    310                 mPreviewDone.open();
    311             }
    312         }
    313 
    314         /** Waits until the camera preview is up running */
    315         public boolean waitForPreviewDone(long timeOutMs) {
    316             if (!mPreviewDone.block(timeOutMs)) {
    317                 // timeout could be expected or unexpected. The caller will decide.
    318                 Log.w(TAG, "waitForPreviewDone timed out after " + timeOutMs + "ms");
    319                 return false;
    320             }
    321             mPreviewDone.close();
    322             return true;
    323         }
    324     }
    325 
    326     private CameraHolder getCameraHolder(String cameraId) {
    327         Integer cameraIdx = mCameraIdMap.get(cameraId);
    328         if (cameraIdx == null) {
    329             Assert.fail("Unknown camera Id");
    330         }
    331         return mCameraHolders[cameraIdx];
    332     }
    333 
    334     // Per device fields
    335     private class CameraHolder {
    336         private String mCameraId;
    337         private CameraCaptureSession mSession;
    338         private CameraDevice mCamera;
    339         private StaticMetadata mStaticInfo;
    340         private List<Size> mOrderedPreviewSizes;
    341         private BlockingSessionCallback mSessionListener;
    342 
    343         public CameraHolder(String id){
    344             mCameraId = id;
    345         }
    346 
    347         public StaticMetadata getStaticInfo() {
    348             return mStaticInfo;
    349         }
    350 
    351         public List<Size> getOrderedPreviewSizes() {
    352             return mOrderedPreviewSizes;
    353         }
    354 
    355         public void open() throws Exception {
    356             assertNull("Camera is already opened", mCamera);
    357             mCamera = (new BlockingCameraManager(mCameraManager)).openCamera(
    358                     mCameraId, mCameraListener, mHandler);
    359             mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraId),
    360                     CheckLevel.ASSERT, /*collector*/null);
    361             mOrderedPreviewSizes = getSupportedPreviewSizes(
    362                     mCameraId, mCameraManager, PREVIEW_SIZE_BOUND);
    363             assertNotNull(String.format("Failed to open camera device ID: %s", mCameraId), mCamera);
    364         }
    365 
    366         public boolean isOpenned() {
    367             return (mCamera != null);
    368         }
    369 
    370         public void close() throws Exception {
    371             if (!isOpenned()) {
    372                 return;
    373             }
    374             mCamera.close();
    375             mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
    376             mCamera = null;
    377             mSession = null;
    378             mStaticInfo = null;
    379             mOrderedPreviewSizes = null;
    380         }
    381 
    382         public void startPreview(List<Surface> outputSurfaces, CaptureCallback listener)
    383                 throws Exception {
    384             mSessionListener = new BlockingSessionCallback();
    385             mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
    386 
    387             // TODO: vary the different settings like crop region to cover more cases.
    388             CaptureRequest.Builder captureBuilder =
    389                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    390 
    391             for (Surface surface : outputSurfaces) {
    392                 captureBuilder.addTarget(surface);
    393             }
    394             mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
    395         }
    396 
    397         public boolean isPreviewStarted() {
    398             return (mSession != null);
    399         }
    400 
    401         public void stopPreview() throws Exception {
    402             if (VERBOSE) Log.v(TAG,
    403                     "Stopping camera " + mCameraId +" preview and waiting for idle");
    404             // Stop repeat, wait for captures to complete, and disconnect from surfaces
    405             mSession.close();
    406             mSessionListener.getStateWaiter().waitForState(
    407                     SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
    408             mSessionListener = null;
    409         }
    410     }
    411 }
    412 
    413