Camera2: Refactor preview-based test case

- Rename the class to follow the convention: If a particular class X is being
tested, then the test class name should be XTest. This makes the test class
much less verbose.
- Extract the common code from all preview related tests, and form one base
class to avoid duplicate code.

Change-Id: Ia56432578d5cecfb277ee98e3b7c10606d6c99c8
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 0f05816..ad16635 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -53,7 +53,7 @@
     private CameraManager mCameraManager;
     private BlockingStateListener mCameraListener;
     private HandlerThread mHandlerThread;
-    private Handler mCallbackHandler;
+    private Handler mHandler;
     private int mLatestState = STATE_UNINITIALIZED;
 
     private ImageReader mReader;
@@ -114,7 +114,7 @@
         assertNotNull("Can't connect to camera manager", mCameraManager);
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
-        mCallbackHandler = new Handler(mHandlerThread.getLooper());
+        mHandler = new Handler(mHandlerThread.getLooper());
         createDefaultSurface();
     }
 
@@ -130,7 +130,7 @@
         for (int i = 0; i < ids.length; i++) {
             CameraDevice camera = null;
             try {
-                camera = CameraTestUtils.openCamera(mCameraManager, ids[i], mCallbackHandler);
+                camera = CameraTestUtils.openCamera(mCameraManager, ids[i], mHandler);
                 assertNotNull(
                         String.format("Failed to open camera device ID: %s", ids[i]), camera);
 
@@ -163,7 +163,7 @@
             CameraDevice camera = null;
             try {
                 camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
-                        mCameraListener, mCallbackHandler);
+                        mCameraListener, mHandler);
                 assertNotNull(
                         String.format("Failed to open camera device %s", ids[i]), camera);
 
@@ -224,7 +224,7 @@
             CameraDevice camera = null;
             try {
                 camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
-                        mCameraListener, mCallbackHandler);
+                        mCameraListener, mHandler);
                 assertNotNull(
                         String.format("Failed to open camera device %s", ids[i]), camera);
                 waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
@@ -291,11 +291,11 @@
                     id, template));
         }
         if (!repeating) {
-            camera.capture(requestBuilder.build(), mockCaptureListener, mCallbackHandler);
+            camera.capture(requestBuilder.build(), mockCaptureListener, mHandler);
         }
         else {
             camera.setRepeatingRequest(requestBuilder.build(), mockCaptureListener,
-                    mCallbackHandler);
+                    mHandler);
         }
         waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
 
@@ -334,10 +334,10 @@
         }
 
         if (!repeating) {
-            camera.captureBurst(requests, mockCaptureListener, mCallbackHandler);
+            camera.captureBurst(requests, mockCaptureListener, mHandler);
         }
         else {
-            camera.setRepeatingBurst(requests, mockCaptureListener, mCallbackHandler);
+            camera.setRepeatingBurst(requests, mockCaptureListener, mHandler);
         }
         waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
 
@@ -394,7 +394,7 @@
         mSurface = mReader.getSurface();
         // Create dummy image listener since we don't care the image data in this test.
         ImageReader.OnImageAvailableListener listener = new ImageDropperListener();
-        mReader.setOnImageAvailableListener(listener, mCallbackHandler);
+        mReader.setOnImageAvailableListener(listener, mHandler);
     }
 
     private void waitForState(int state, long timeout) {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraSurfaceViewPreviewTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraSurfaceViewPreviewTest.java
deleted file mode 100644
index 1e3764e..0000000
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraSurfaceViewPreviewTest.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2.cts;
-
-import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static com.android.ex.camera2.blocking.BlockingStateListener.*;
-
-import android.content.Context;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraDevice.CaptureListener;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.Size;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.Surface;
-
-import com.android.ex.camera2.blocking.BlockingStateListener;
-
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import static org.mockito.Mockito.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * CameraDevice capture use case tests, including preview, still capture, burst
- * capture etc.
- */
-public class CameraSurfaceViewPreviewTest
-        extends ActivityInstrumentationTestCase2<Camera2SurfaceViewStubActivity> {
-    // Can not use class name exactly as it exceed the log tag character limit.
-    private static final String TAG = "CameraPreviewTest";
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-    private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
-    private static final int FRAME_TIMEOUT_MS = 500;
-    private static final int NUM_FRAMES_VERIFIED = 30;
-
-    private Context mContext;
-    private CameraManager mCameraManager;
-    private String[] mCameraIds;
-    private CameraDevice mCamera;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private BlockingStateListener mCameraListener;
-
-    public CameraSurfaceViewPreviewTest() {
-        super(Camera2SurfaceViewStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getActivity();
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Unable to get CameraManager", mCameraManager);
-        mCameraIds = mCameraManager.getCameraIdList();
-        assertNotNull("Unable to get camera ids", mCameraIds);
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-        mCameraListener = new BlockingStateListener();
-
-        /**
-         * Workaround for mockito and JB-MR2 incompatibility
-         *
-         * Avoid java.lang.IllegalArgumentException: dexcache == null
-         * https://code.google.com/p/dexmaker/issues/detail?id=2
-         */
-        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mHandlerThread.quitSafely();
-        mHandler = null;
-        mCameraListener = null;
-        super.tearDown();
-    }
-
-    public void testCameraPreview() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            try {
-                Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
-                mCamera = openCamera(mCameraManager, mCameraIds[i], mCameraListener, mHandler);
-                assertNotNull(
-                        String.format("Failed to open camera device ID: %s", mCameraIds[i]),
-                        mCamera);
-                previewTestByCamera();
-            } finally {
-                if (mCamera != null) {
-                    mCamera.close();
-                    mCamera = null;
-                }
-            }
-        }
-    }
-
-    /**
-     * Test all supported preview sizes for a camera device
-     *
-     * @throws Exception
-     */
-    private void previewTestByCamera() throws Exception {
-        List<Size> previewSizes = getSupportedPreviewSizes(
-                mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
-        Camera2SurfaceViewStubActivity stubActivity = getActivity();
-
-        for (final Size sz : previewSizes) {
-            if (VERBOSE) {
-                Log.v(TAG, "Testing camera preview size: " + sz.toString());
-            }
-            // Change the preview size
-            final SurfaceHolder holder = stubActivity.getSurfaceView().getHolder();
-            Handler handler = new Handler(Looper.getMainLooper());
-            handler.post(new Runnable() {
-                @Override
-                public void run() {
-                    holder.setFixedSize(sz.getWidth(), sz.getHeight());
-                }
-            });
-
-            boolean res = stubActivity.waitForSurfaceSizeChanged(
-                    WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, sz.getWidth(), sz.getHeight());
-            assertTrue("wait for surface change to " + sz.toString() + " timed out", res);
-            Surface previewSurface = holder.getSurface();
-            assertTrue("Preview surface is invalid", previewSurface.isValid());
-
-            CaptureListener mockCaptureListener =
-                    mock(CameraDevice.CaptureListener.class);
-
-            startPreview(previewSurface, mockCaptureListener);
-
-            verifyCaptureResults(mCamera, mockCaptureListener, NUM_FRAMES_VERIFIED,
-                    NUM_FRAMES_VERIFIED * FRAME_TIMEOUT_MS);
-
-            stopPreview();
-        }
-    }
-
-    private void startPreview(Surface surface, CaptureListener listener) throws Exception {
-        List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
-        outputSurfaces.add(surface);
-        mCamera.configureOutputs(outputSurfaces);
-        mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
-        mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
-
-        // TODO: vary the different settings like crop region to cover more cases.
-        CaptureRequest.Builder captureBuilder =
-                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-
-        captureBuilder.addTarget(surface);
-        mCamera.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
-    }
-
-    private void stopPreview() throws Exception {
-        if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
-        // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        mCamera.configureOutputs(/*outputs*/ null);
-        mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
-        mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
-    }
-
-    private class IsCaptureResultValid extends ArgumentMatcher<CaptureResult> {
-        @Override
-        public boolean matches(Object obj) {
-            CaptureResult result = (CaptureResult)obj;
-            Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
-            if (timeStamp != null && timeStamp.longValue() > 0L) {
-                return true;
-            }
-            return false;
-        }
-    }
-
-    private void verifyCaptureResults(
-            CameraDevice camera,
-            CameraDevice.CaptureListener mockListener,
-            int expectResultCount,
-            int timeOutMs) {
-        // Should receive expected number of onCaptureStarted callbacks.
-        ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
-        verify(mockListener,
-                timeout(timeOutMs).atLeast(expectResultCount))
-                        .onCaptureStarted(
-                                eq(camera),
-                                isA(CaptureRequest.class),
-                                timestamps.capture());
-
-        // Validate timestamps: all timestamps should be larger than 0 and monotonically increase.
-        long timestamp = 0;
-        for (Long nextTimestamp : timestamps.getAllValues()) {
-            Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
-            assertTrue("Captures are out of order", timestamp < nextTimestamp);
-            timestamp = nextTimestamp;
-        }
-
-        // Should receive expected number of capture results.
-        verify(mockListener,
-                timeout(timeOutMs).atLeast(expectResultCount))
-                        .onCaptureCompleted(
-                                eq(camera),
-                                isA(CaptureRequest.class),
-                                argThat(new IsCaptureResultValid()));
-
-        // Should not receive any capture failed callbacks.
-        verify(mockListener, never())
-                        .onCaptureFailed(
-                                eq(camera),
-                                isA(CaptureRequest.class),
-                                isA(CaptureFailure.class));
-    }
-
-}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index 9f18d10..998f3f4 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -56,7 +56,7 @@
 /**
  * A package private utility class for wrapping up the camera2 cts test common utility functions
  */
-class CameraTestUtils extends Assert {
+public class CameraTestUtils extends Assert {
     private static final String TAG = "CameraTestUtils";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -165,7 +165,14 @@
         return openCamera(manager, cameraId, /*listener*/null, handler);
     }
 
-    public static void configureOutputs(CameraDevice camera, List<Surface> outputSurfaces,
+    /**
+     * Configure camera output surfaces.
+     *
+     * @param camera The CameraDevice to be configured.
+     * @param outputSurfaces The surface list that used for camera output.
+     * @param listener The callback CameraDevice will notify when capture results are available.
+     */
+    public static void configureCameraOutputs(CameraDevice camera, List<Surface> outputSurfaces,
             BlockingStateListener listener) throws Exception {
         camera.configureOutputs(outputSurfaces);
         listener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 1da9aad..3131ba2 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -17,30 +17,14 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static com.android.ex.camera2.blocking.BlockingStateListener.*;
 import static android.hardware.camera2.CameraMetadata.*;
 
-import android.content.Context;
-import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraDevice.CaptureListener;
-import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.Size;
-import android.hardware.camera2.cts.helpers.StaticMetadata;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.test.ActivityInstrumentationTestCase2;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-
-import com.android.ex.camera2.blocking.BlockingStateListener;
-
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * <p>
@@ -51,50 +35,21 @@
  * manual ISP control and other per-frame control and synchronization.
  * </p>
  */
-public class CaptureRequestTest extends
-        ActivityInstrumentationTestCase2<Camera2SurfaceViewStubActivity> {
+public class CaptureRequestTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "CaptureRequestTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final int NUM_FRAMES_VERIFIED = 15;
     private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
-    private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
     private static final long DEFAULT_EXP_TIME_NS = 30000000L; // 30ms
     private static final int DEFAULT_SENSITIVITY = 100; // 10ms
 
-    private Context mContext;
-    private CameraManager mCameraManager;
-    private CameraDevice mCamera;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private Surface mPreviewSurface;
-    private BlockingStateListener mCameraListener;
-    private StaticMetadata mStaticInfo;
-    private Size mPreviewSize;
-    private String[] mCameraIds;
-
-    public CaptureRequestTest() {
-        super(Camera2SurfaceViewStubActivity.class);
-    }
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mContext = getActivity();
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Unable to get CameraManager", mCameraManager);
-        mCameraIds = mCameraManager.getCameraIdList();
-        assertNotNull("Unable to get camera ids", mCameraIds);
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-        mCameraListener = new BlockingStateListener();
     }
 
     @Override
     protected void tearDown() throws Exception {
-        mHandlerThread.quitSafely();
-        mHandler = null;
-        mCameraListener = null;
         super.tearDown();
     }
 
@@ -115,14 +70,16 @@
                 requestBuilder.set(CaptureRequest.BLACK_LEVEL_LOCK, true);
                 changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
 
-                startPreview(requestBuilder, listener);
+                Size previewSz =
+                        getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+                startPreview(requestBuilder, previewSz, listener);
 
                 // No lock OFF state is allowed as the exposure is not changed.
                 verifyBlackLevelLockResults(listener, NUM_FRAMES_VERIFIED, /*maxLockOffCnt*/0);
 
-                // change exposure, with black level still being locked.
+                // Double the exposure time and gain, with black level still being locked.
                 changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS * 2, DEFAULT_SENSITIVITY * 2);
-                startPreview(requestBuilder, listener);
+                startPreview(requestBuilder, previewSz, listener);
 
                 // Allow at most one lock OFF state as the exposure is changed once.
                 verifyBlackLevelLockResults(listener, NUM_FRAMES_VERIFIED, /*maxLockOffCnt*/1);
@@ -179,69 +136,4 @@
         requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTime);
         requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
     }
-
-    /**
-     * Open a camera device and get the StaticMetadata.
-     */
-    private void openDevice(String cameraId) throws Exception {
-        mCamera = CameraTestUtils.openCamera(
-                mCameraManager, cameraId, mCameraListener, mHandler);
-        assertNotNull("Failed to open camera device " + cameraId, mCamera);
-
-        mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
-        mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId));
-    }
-
-    private void closeDevice() {
-        if (mCamera != null) {
-            mCamera.close();
-            mCamera = null;
-        }
-        mStaticInfo = null;
-    }
-
-    /**
-     * Start preview with the largest supported preview size
-     */
-    private void startPreview(CaptureRequest.Builder request, CaptureListener listener)
-            throws Exception {
-        // Prepare preview surface.
-        Size previewSz = getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
-        if (!previewSz.equals(mPreviewSize)) {
-            mPreviewSize = previewSz;
-            Camera2SurfaceViewStubActivity stubActivity = getActivity();
-            final SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
-            Handler handler = new Handler(Looper.getMainLooper());
-            handler.post(new Runnable() {
-                @Override
-                public void run() {
-                    holder.setFixedSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
-                }
-            });
-
-            boolean res = stubActivity.waitForSurfaceSizeChanged(
-                    WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, mPreviewSize.getWidth(), mPreviewSize.getHeight());
-            assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
-            mPreviewSurface = holder.getSurface();
-            assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
-        }
-
-        if (VERBOSE) {
-            Log.v(TAG, "start preview with size " + mPreviewSize.toString());
-        }
-
-        List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
-        outputSurfaces.add(mPreviewSurface);
-        configureOutputs(mCamera, outputSurfaces, mCameraListener);
-
-        request.addTarget(mPreviewSurface);
-
-        mCamera.setRepeatingRequest(request.build(), listener, mHandler);
-    }
-
-    private void stopPreview() throws Exception {
-        if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
-        // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        configureOutputs(mCamera, /*outputSurfaces*/null, mCameraListener);
-    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
similarity index 98%
rename from tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
rename to tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
index 0188ed9..4cf402d 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -39,8 +39,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class CameraCaptureResultTest extends AndroidTestCase {
-    private static final String TAG = "CameraCaptureResultTest";
+public class CaptureResultTest extends AndroidTestCase {
+    private static final String TAG = "CaptureResultTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private CameraManager mCameraManager;
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
new file mode 100644
index 0000000..d6f2d6a
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.util.Log;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+
+/**
+ * CameraDevice preview test by using SurfaceView.
+ */
+public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase {
+    private static final String TAG = "SurfaceViewPreviewTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final int FRAME_TIMEOUT_MS = 1000;
+    private static final int NUM_FRAMES_VERIFIED = 30;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Test all supported preview sizes for each camera device.
+     * <p>
+     * For the first  {@link #NUM_FRAMES_VERIFIED}  of capture results,
+     * the {@link CaptureListener} callback availability and the capture timestamp
+     * (monotonically increasing) ordering are verified.
+     * </p>
+     */
+    public void testCameraPreview() throws Exception {
+        for (int i = 0; i < mCameraIds.length; i++) {
+            try {
+                Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
+                openDevice(mCameraIds[i]);
+
+                previewTestByCamera();
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Test all supported preview sizes for a camera device
+     *
+     * @throws Exception
+     */
+    private void previewTestByCamera() throws Exception {
+        List<Size> previewSizes = getSupportedPreviewSizes(
+                mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+
+        for (final Size sz : previewSizes) {
+            if (VERBOSE) {
+                Log.v(TAG, "Testing camera preview size: " + sz.toString());
+            }
+
+            // TODO: vary the different settings like crop region to cover more cases.
+            CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            CaptureListener mockCaptureListener =
+                    mock(CameraDevice.CaptureListener.class);
+
+            startPreview(requestBuilder, sz, mockCaptureListener);
+            verifyCaptureResults(mCamera, mockCaptureListener, NUM_FRAMES_VERIFIED,
+                    NUM_FRAMES_VERIFIED * FRAME_TIMEOUT_MS);
+            stopPreview();
+        }
+    }
+
+    private class IsCaptureResultValid extends ArgumentMatcher<CaptureResult> {
+        @Override
+        public boolean matches(Object obj) {
+            CaptureResult result = (CaptureResult)obj;
+            Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+            if (timeStamp != null && timeStamp.longValue() > 0L) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void verifyCaptureResults(
+            CameraDevice camera,
+            CameraDevice.CaptureListener mockListener,
+            int expectResultCount,
+            int timeOutMs) {
+        // Should receive expected number of onCaptureStarted callbacks.
+        ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
+        verify(mockListener,
+                timeout(timeOutMs).atLeast(expectResultCount))
+                        .onCaptureStarted(
+                                eq(camera),
+                                isA(CaptureRequest.class),
+                                timestamps.capture());
+
+        // Validate timestamps: all timestamps should be larger than 0 and monotonically increase.
+        long timestamp = 0;
+        for (Long nextTimestamp : timestamps.getAllValues()) {
+            Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
+            assertNotNull("Next timestamp is null!", nextTimestamp);
+            assertTrue("Captures are out of order", timestamp < nextTimestamp);
+            timestamp = nextTimestamp;
+        }
+
+        // Should receive expected number of capture results.
+        verify(mockListener,
+                timeout(timeOutMs).atLeast(expectResultCount))
+                        .onCaptureCompleted(
+                                eq(camera),
+                                isA(CaptureRequest.class),
+                                argThat(new IsCaptureResultValid()));
+
+        // Should not receive any capture failed callbacks.
+        verify(mockListener, never())
+                        .onCaptureFailed(
+                                eq(camera),
+                                isA(CaptureRequest.class),
+                                isA(CaptureFailure.class));
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
new file mode 100644
index 0000000..7712440
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.testcases;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.content.Context;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.Size;
+import android.hardware.camera2.CameraDevice.CaptureListener;
+import android.hardware.camera2.cts.Camera2SurfaceViewStubActivity;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+
+import com.android.ex.camera2.blocking.BlockingStateListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Camera2 Preview test case base class by using SurfaceView as rendering target.
+ *
+ * <p>This class encapsulates the SurfaceView based preview common functionalities.
+ * The setup and teardown of CameraManager, test HandlerThread, Activity, Camera IDs
+ * and CameraStateListener are handled in this class. Some basic preview related utility
+ * functions are provided to facilitate the derived preview-based test classes.
+ * </p>
+ */
+
+public class Camera2SurfaceViewTestCase extends
+        ActivityInstrumentationTestCase2<Camera2SurfaceViewStubActivity> {
+    private static final String TAG = "SurfaceViewTestCase";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+
+    private Size mPreviewSize;
+    private Surface mPreviewSurface;
+
+    protected Context mContext;
+    protected CameraManager mCameraManager;
+    protected String[] mCameraIds;
+    protected HandlerThread mHandlerThread;
+    protected Handler mHandler;
+    protected BlockingStateListener mCameraListener;
+    // Per device fields:
+    protected StaticMetadata mStaticInfo;
+    protected CameraDevice mCamera;
+
+    public Camera2SurfaceViewTestCase() {
+        super(Camera2SurfaceViewStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        /**
+         * Set up the camera preview required environments, including activity,
+         * CameraManager, HandlerThread, Camera IDs, and CameraStateListener.
+         */
+        super.setUp();
+        mContext = getActivity();
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull("Unable to get CameraManager", mCameraManager);
+        mCameraIds = mCameraManager.getCameraIdList();
+        assertNotNull("Unable to get camera ids", mCameraIds);
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mCameraListener = new BlockingStateListener();
+
+        /**
+         * Workaround for mockito and JB-MR2 incompatibility
+         *
+         * Avoid java.lang.IllegalArgumentException: dexcache == null
+         * https://code.google.com/p/dexmaker/issues/detail?id=2
+         */
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Teardown the camera preview required environments.
+        mHandlerThread.quitSafely();
+        mHandler = null;
+        mCameraListener = null;
+
+        super.tearDown();
+    }
+
+    /**
+     * Start camera preview by using the given request, preview size and capture listener.
+     *
+     * @param request The request builder used to start the preview.
+     * @param previewSz The size of the camera device output preview stream.
+     * @param listener The callbacks the camera device will notify when preview capture is
+     * available.
+     */
+    protected void startPreview(CaptureRequest.Builder request, Size previewSz,
+            CaptureListener listener) throws Exception {
+        if (!previewSz.equals(mPreviewSize)) {
+            mPreviewSize = previewSz;
+            Camera2SurfaceViewStubActivity stubActivity = getActivity();
+            final SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    holder.setFixedSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+                }
+            });
+
+            boolean res = stubActivity.waitForSurfaceSizeChanged(
+                    WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, mPreviewSize.getWidth(), mPreviewSize.getHeight());
+            assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
+            mPreviewSurface = holder.getSurface();
+            assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "start preview with size " + mPreviewSize.toString());
+        }
+
+        List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
+        outputSurfaces.add(mPreviewSurface);
+        configureCameraOutputs(mCamera, outputSurfaces, mCameraListener);
+
+        request.addTarget(mPreviewSurface);
+
+        mCamera.setRepeatingRequest(request.build(), listener, mHandler);
+    }
+    /**
+     * Stop preview for current camera device.
+     */
+    protected void stopPreview() throws Exception {
+        if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
+        // Stop repeat, wait for captures to complete, and disconnect from surfaces
+        configureCameraOutputs(mCamera, /*outputSurfaces*/null, mCameraListener);
+    }
+
+    /**
+     * Open a camera device and get the StaticMetadata for a given camera id.
+     *
+     * @param cameraId The id of the camera device to be opened.
+     */
+    protected void openDevice(String cameraId) throws Exception {
+        mCamera = CameraTestUtils.openCamera(
+                mCameraManager, cameraId, mCameraListener, mHandler);
+        assertNotNull("Failed to open camera device " + cameraId, mCamera);
+        mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId));
+    }
+
+    /**
+     * Close the current actively used camera device.
+     */
+    protected void closeDevice() {
+        if (mCamera != null) {
+            mCamera.close();
+            mCamera = null;
+        }
+        mStaticInfo = null;
+    }
+}