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