CTS Verifier: Camera Video test with different resolutions.

-- This test shows preview in left window and playback in right window.
-- Push "Test" button to record for 3 seconds, and wait for playback.
-- Use the spinners to choose all combinations.

Bug: 9222143
Change-Id: Ibf931a4201d221ee70c9b4fd0e9654590b2cb5f0
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index d0758f3..1920102 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -335,6 +335,7 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
         </activity>
 
+
         <activity android:name=".camera.orientation.CameraOrientationActivity"
                  android:label="@string/camera_orientation"
                  android:screenOrientation="landscape">
@@ -371,6 +372,19 @@
             android:label="@string/camera_fov_label_options" >
         </activity>
 
+
+        <activity android:name=".camera.video.CameraVideoActivity"
+                 android:label="@string/camera_video"
+                 android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_camera" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.hardware.camera.any"/>
+        </activity>
+
         <activity android:name=".usb.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -392,7 +406,7 @@
             <meta-data android:name="test_category" android:value="@string/test_category_networking" />
             <meta-data android:name="test_required_features" android:value="android.hardware.wifi.direct" />
         </activity>
-        
+
         <activity android:name=".nls.NotificationListenerVerifierActivity"
                 android:label="@string/nls_test">
             <intent-filter>
diff --git a/apps/CtsVerifier/res/layout/camera_video.xml b/apps/CtsVerifier/res/layout/camera_video.xml
new file mode 100644
index 0000000..b81721b
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/camera_video.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2013 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" >
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3"
+            android:gravity="center" >
+
+            <TextureView
+                android:id="@+id/video_capture"
+                android:layout_height="0dp"
+                android:layout_width="fill_parent"
+                android:layout_weight="3" />
+            <TextView
+                android:id="@+id/camera_video_capture_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:text="@string/video_capture_label"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3"
+            android:gravity="center" >
+
+            <VideoView
+                android:id="@+id/video_playback"
+                android:layout_height="0dp"
+                android:layout_width="fill_parent"
+                android:layout_weight="3" />
+            <TextView
+                android:id="@+id/camera_video_playback_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:text="@string/video_playback_label"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="2" >
+
+            <Spinner
+                android:id="@+id/cameras_selection"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"/>
+            <Spinner
+                android:id="@+id/resolution_selection"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"/>
+            <Button
+                android:id="@+id/record_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/record_button_text"/>
+            <TextView
+                android:id="@+id/status_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/status_ready"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 6df4b42..9d9e8ae 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -393,6 +393,29 @@
     <string name="cf_preview_label">Normal preview</string>
     <string name="cf_format_label">Processed callback data</string>
 
+    <!-- Strings for Camera Video -->
+    <string name="record_button_text">Test</string>
+    <string name="camera_video">Camera Video</string>
+    <string name="video_info"> This test checks video capture
+    at different resolutions. \n - The left view window shows the preview.
+    \n - Pressing the test button will trigger three
+    seconds of video recording. Playback will show up in the right view
+    window after recording is complete. \n - Use the spinners to choose
+    camera and resolution combinations. The playback should be similar
+    to what you saw in preview. \n - After all possible combinations
+    are tested, the pass button will be enabled. You may press the pass
+    button to indicate a pass. \n - You may press fail button any time during
+    the test to indicate failure.
+    </string>
+    <string name="video_capture_label">Video capture</string>
+    <string name="video_playback_label">Video playback</string>
+    <string name="dialog_fail_test">Test failed</string>
+    <string name="fail_quit">Fail and quit</string>
+    <string name="cancel">Cancel</string>
+    <string name="status_ready">Ready</string>
+    <string name="status_recording">Recording</string>
+    <string name="status_playback">Playing back</string>
+
     <!-- Strings for USB accessory test activity -->
     <string name="usb_accessory_test">USB Accessory Test</string>
     <string name="usb_accessory_test_info">
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
index 9989057..7856591 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
@@ -172,6 +172,7 @@
         super.onPause();
 
         shutdownCamera();
+        mPreviewTexture = null;
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
new file mode 100644
index 0000000..a67a498
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
@@ -0,0 +1,738 @@
+/*
+ * Copyright (C) 2013 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 com.android.cts.verifier.camera.video;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Size;
+import android.media.CamcorderProfile;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.VideoView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.TreeSet;
+
+
+/**
+ * Tests for manual verification of camera video capture
+ */
+public class CameraVideoActivity extends PassFailButtons.Activity
+        implements TextureView.SurfaceTextureListener {
+
+    private static final String TAG = "CtsCameraVideo";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final int MEDIA_TYPE_IMAGE = 1;
+    private static final int MEDIA_TYPE_VIDEO = 2;
+    private static final int VIDEO_LENGTH = 3000; // in ms
+
+    private TextureView mPreviewView;
+    private SurfaceTexture mPreviewTexture;
+    private int mPreviewTexWidth;
+    private int mPreviewTexHeight;
+    private int mPreviewRotation;
+
+    private VideoView mPlaybackView;
+
+    private Spinner mCameraSpinner;
+    private Spinner mResolutionSpinner;
+
+    private int mCurrentCameraId = -1;
+    private Camera mCamera;
+
+    private MediaRecorder mMediaRecorder;
+
+    private List<Camera.Size> mPreviewSizes;
+    private Camera.Size mNextPreviewSize;
+    private Camera.Size mPreviewSize;
+    private List<Integer> mVideoSizeIds;
+    private int mCurrentVideoSizeId;
+
+    private boolean isRecording = false;
+    private boolean isPlayingBack = false;
+    private Button captureButton;
+    private Button mPassButton;
+    private Button mFailButton;
+
+    private TextView mStatusLabel;
+
+    private TreeSet<String> mTestedCombinations = new TreeSet<String>();
+    private TreeSet<String> mUntestedCombinations = new TreeSet<String>();
+
+    private File outputVideoFile;
+
+    /**
+     * @see #MEDIA_TYPE_IMAGE
+     * @see #MEDIA_TYPE_VIDEO
+     */
+    private static File getOutputMediaFile(int type) {
+        // Question: why do I need to comment this to get it working?
+        // Logcat says "external storage not ready"
+        // if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
+        //     Log.e(TAG, "external storage not ready");
+        //     return null;
+        // }
+
+        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_MOVIES), TAG);
+
+        if (!mediaStorageDir.exists()) {
+            if (!mediaStorageDir.mkdirs()) {
+                Log.d(TAG, "failed to create directory");
+                return null;
+            }
+        }
+
+        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+        File mediaFile;
+        if (type == MEDIA_TYPE_IMAGE) {
+            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
+                    "IMG_" + timeStamp + ".jpg");
+        } else if (type == MEDIA_TYPE_VIDEO) {
+            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
+                    "VID_" + timeStamp + ".mp4");
+            if (VERBOSE) {
+                Log.v(TAG, "getOutputMediaFile: output file " + mediaFile.getPath());
+            }
+        } else {
+            return null;
+        }
+
+        return mediaFile;
+    }
+
+    private boolean prepareVideoRecorder() {
+
+        mMediaRecorder = new MediaRecorder();
+
+        // Step 1: unlock and set camera to MediaRecorder
+        mCamera.unlock();
+        mMediaRecorder.setCamera(mCamera);
+
+        // Step 2: set sources
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+
+        // Step 3: set a CamcorderProfile
+        mMediaRecorder.setProfile(CamcorderProfile.get(mCurrentCameraId, mCurrentVideoSizeId));
+
+        // Step 4: set output file
+        outputVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
+        mMediaRecorder.setOutputFile(outputVideoFile.toString());
+
+        // Step 5: set preview output
+        // This is not necessary since preview has been taken care of
+
+        // Step 6: prepare configured MediaRecorder
+        try {
+            mMediaRecorder.prepare();
+        } catch (IOException e) {
+            Log.e(TAG, "IOException preparing MediaRecorder: ", e);
+            releaseMediaRecorder();
+            throw new AssertionError(e);
+        }
+
+        mMediaRecorder.setOnErrorListener(
+                new MediaRecorder.OnErrorListener() {
+                    @Override
+                    public void onError(MediaRecorder mr, int what, int extra) {
+                        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+                            Log.e(TAG, "unknown error in media recorder, error: " + extra);
+                        } else {
+                            Log.e(TAG, "media recorder server died, error: " + extra);
+                        }
+
+                        failTest("Media recorder error.");
+                    }
+                });
+
+        if (VERBOSE) {
+            Log.v(TAG, "prepareVideoRecorder: prepared configured MediaRecorder");
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.camera_video);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.camera_video, R.string.video_info, /*viewId*/-1);
+
+        mPreviewView = (TextureView) findViewById(R.id.video_capture);
+        mPlaybackView = (VideoView) findViewById(R.id.video_playback);
+        mPlaybackView.setOnCompletionListener(mPlaybackViewListener);
+
+        captureButton = (Button) findViewById(R.id.record_button);
+        mPassButton = (Button) findViewById(R.id.pass_button);
+        mFailButton = (Button) findViewById(R.id.fail_button);
+        mPassButton.setEnabled(false);
+        mFailButton.setEnabled(true);
+
+        mPreviewView.setSurfaceTextureListener(this);
+
+        int numCameras = Camera.getNumberOfCameras();
+        String[] cameraNames = new String[numCameras];
+        for (int i = 0; i < numCameras; i++) {
+            cameraNames[i] = "Camera " + i;
+            mUntestedCombinations.add("All combinations for Camera " + i + "\n");
+        }
+        if (VERBOSE) {
+            Log.v(TAG, "onCreate: number of cameras=" + numCameras);
+        }
+        mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
+        mCameraSpinner.setAdapter(
+            new ArrayAdapter<String>(
+                this, R.layout.cf_format_list_item, cameraNames));
+        mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
+
+        mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection);
+        mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener);
+
+        mStatusLabel = (TextView) findViewById(R.id.status_label);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        setUpCamera(mCameraSpinner.getSelectedItemPosition());
+        if (VERBOSE) {
+            Log.v(TAG, "onResume: camera has been setup");
+        }
+
+        setUpCaptureButton();
+        if (VERBOSE) {
+            Log.v(TAG, "onResume: captureButton has been setup");
+        }
+
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        releaseMediaRecorder();
+        shutdownCamera();
+        mPreviewTexture = null;
+    }
+
+    private MediaPlayer.OnCompletionListener mPlaybackViewListener =
+            new MediaPlayer.OnCompletionListener() {
+
+                @Override
+                public void onCompletion(MediaPlayer mp) {
+                    isPlayingBack = false;
+                    captureButton.setEnabled(true);
+                    mStatusLabel.setText(getResources().getString(R.string.status_ready));
+                }
+
+    };
+
+    private void releaseMediaRecorder() {
+        if (mMediaRecorder != null) {
+            mMediaRecorder.reset();
+            mMediaRecorder.release();
+            mMediaRecorder = null;
+            mCamera.lock(); // check here, lock camera for later use
+        }
+    }
+
+    @Override
+    public String getTestDetails() {
+        StringBuilder reportBuilder = new StringBuilder();
+        reportBuilder.append("Tested combinations:\n");
+        for (String combination : mTestedCombinations) {
+            reportBuilder.append(combination);
+        }
+        reportBuilder.append("Untested combinations:\n");
+        for (String combination : mUntestedCombinations) {
+            reportBuilder.append(combination);
+        }
+        return reportBuilder.toString();
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface,
+            int width, int height) {
+        mPreviewTexture = surface;
+        mPreviewTexWidth = width;
+        mPreviewTexHeight = height;
+        if (mCamera != null) {
+            startPreview();
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored, Camera does all the work for us
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        return true;
+    }
+
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Invoked every time there's a new Camera preview frame
+    }
+
+    private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
+            new AdapterView.OnItemSelectedListener() {
+                @Override
+                public void onItemSelected(AdapterView<?> parent,
+                        View view, int pos, long id) {
+                    if (mCurrentCameraId != pos) {
+                        setUpCamera(pos);
+                    }
+                }
+
+                @Override
+                public void onNothingSelected(AdapterView<?> parent) {
+                    // Intentionally left blank
+                }
+
+            };
+
+    private AdapterView.OnItemSelectedListener mResolutionSelectedListener =
+            new AdapterView.OnItemSelectedListener() {
+                @Override
+                public void onItemSelected(AdapterView<?> parent,
+                        View view, int position, long id) {
+                    if (mVideoSizeIds.get(position) != mCurrentVideoSizeId) {
+                        mCurrentVideoSizeId = mVideoSizeIds.get(position);
+                        if (VERBOSE) {
+                            Log.v(TAG, "onItemSelected: mCurrentVideoSizeId = " +
+                                    mCurrentVideoSizeId);
+                        }
+                        mNextPreviewSize = matchPreviewRecordSize();
+                        if (VERBOSE) {
+                            Log.v(TAG, "onItemSelected: setting preview size "
+                                    + mNextPreviewSize.width + "x" + mNextPreviewSize.height);
+                        }
+
+                        startPreview();
+                        if (VERBOSE) {
+                            Log.v(TAG, "onItemSelected: started new preview");
+                        }
+                    }
+                }
+
+                @Override
+                public void onNothingSelected(AdapterView<?> parent) {
+                    // Intentionally left blank
+                }
+
+            };
+
+
+    private void setUpCaptureButton() {
+        captureButton.setOnClickListener (
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View V) {
+                        if ((!isRecording) && (!isPlayingBack)) {
+                            if (prepareVideoRecorder()) {
+                                mMediaRecorder.start();
+                                if (VERBOSE) {
+                                    Log.v(TAG, "onClick: started mMediaRecorder");
+                                }
+                                isRecording = true;
+                                captureButton.setEnabled(false);
+                                mStatusLabel.setText(getResources()
+                                        .getString(R.string.status_recording));
+                            } else {
+                                releaseMediaRecorder();
+                                Log.e(TAG, "media recorder cannot be set up");
+                                failTest("Unable to set up media recorder.");
+                            }
+                            Handler h = new Handler();
+                            Runnable mDelayedPreview = new Runnable() {
+                                @Override
+                                public void run() {
+                                    mMediaRecorder.stop();
+                                    releaseMediaRecorder();
+
+                                    mPlaybackView.setVideoPath(outputVideoFile.getPath());
+                                    mPlaybackView.start();
+                                    isRecording = false;
+                                    isPlayingBack = true;
+                                    mStatusLabel.setText(getResources()
+                                            .getString(R.string.status_playback));
+                                    String combination = "Camera " + mCurrentCameraId + ", " +
+                                            mCurrentVideoSizeId + "\n";
+                                    mUntestedCombinations.remove(combination);
+                                    mTestedCombinations.add(combination);
+
+                                    if (mUntestedCombinations.isEmpty()) {
+                                        mPassButton.setEnabled(true);
+                                        if (VERBOSE) {
+                                            Log.v(TAG, "run: test success");
+                                        }
+                                    }
+                                }
+                            };
+                            h.postDelayed(mDelayedPreview, VIDEO_LENGTH);
+                        }
+
+                    }
+                }
+        );
+    }
+
+    private class VideoSizeNamePair {
+        private int sizeId;
+        private String sizeName;
+
+        public VideoSizeNamePair(int id, String name) {
+            sizeId = id;
+            sizeName = name;
+        }
+
+        public int getSizeId() {
+            return sizeId;
+        }
+
+        public String getSizeName() {
+            return sizeName;
+        }
+    }
+
+    private ArrayList<VideoSizeNamePair> getVideoSizeNamePairs(int cameraId) {
+        int[] qualityArray = {
+                CamcorderProfile.QUALITY_LOW,
+                CamcorderProfile.QUALITY_HIGH,
+                CamcorderProfile.QUALITY_QCIF,
+                CamcorderProfile.QUALITY_CIF,
+                CamcorderProfile.QUALITY_480P,
+                CamcorderProfile.QUALITY_720P,
+                CamcorderProfile.QUALITY_1080P
+        };
+
+        String[] nameArray = {
+                "LOW",
+                "HIGH",
+                "QCIF",
+                "CIF",
+                "480P",
+                "720P",
+                "1080P"
+        };
+
+        ArrayList<VideoSizeNamePair> availableSizes =
+                new ArrayList<VideoSizeNamePair> ();
+
+        for (int i = 0; i < qualityArray.length; i++) {
+            if (CamcorderProfile.hasProfile(cameraId, qualityArray[i])) {
+                VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]);
+                availableSizes.add(pair);
+            }
+        }
+        return availableSizes;
+    }
+
+    static class ResolutionQuality {
+        private int videoSizeId;
+        private int width;
+        private int height;
+
+        public ResolutionQuality() {
+            // intentionally left blank
+        }
+        public ResolutionQuality(int newSizeId, int newWidth, int newHeight) {
+            videoSizeId = newSizeId;
+            width = newWidth;
+            height = newHeight;
+        }
+    }
+
+    // Match preview size with recording size
+    private Camera.Size matchPreviewRecordSize() {
+        int[] possibleQuality = {
+                CamcorderProfile.QUALITY_QCIF,
+                CamcorderProfile.QUALITY_CIF,
+                CamcorderProfile.QUALITY_480P,
+                CamcorderProfile.QUALITY_720P,
+                CamcorderProfile.QUALITY_1080P
+        };
+
+        Size[] sizes = new Size[] {
+                mCamera.new Size(176, 144),
+                mCamera.new Size(352, 288),
+                mCamera.new Size(720, 480),
+                mCamera.new Size(1280, 720),
+                mCamera.new Size(1920, 1080)
+        };
+
+        Camera.Size minSize = mCamera.new Size(Integer.MAX_VALUE, Integer.MAX_VALUE);
+        Camera.Size maxSize = mCamera.new Size(0, 0);
+
+        for (int i = 0; i < possibleQuality.length; i++) {
+            if (mVideoSizeIds.contains(possibleQuality[i])) {
+                if (sizes[i].height < minSize.height) {
+                    minSize = sizes[i];
+                }
+                if (sizes[i].height > maxSize.height) {
+                    maxSize = sizes[i];
+                }
+            }
+        }
+
+        ArrayList<ResolutionQuality> qualityList = new ArrayList<ResolutionQuality>();
+        qualityList.add(new ResolutionQuality(CamcorderProfile.QUALITY_LOW, minSize.width,
+                minSize.height));
+        qualityList.add(new ResolutionQuality(CamcorderProfile.QUALITY_HIGH, maxSize.width,
+                maxSize.height));
+        for (int i = 0; i < possibleQuality.length; i++) {
+            qualityList.add(new ResolutionQuality(possibleQuality[i], sizes[i].width,
+                    sizes[i].height));
+        }
+
+        Camera.Size matchedSize = null;
+        for (int i = 0; i < qualityList.size(); i++) {
+            if (mCurrentVideoSizeId == qualityList.get(i).videoSizeId) {
+                matchedSize = mCamera.new Size(qualityList.get(i).width,
+                        qualityList.get(i).height);
+                break;
+            }
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "matchPreviewRecordSize: found a match");
+        }
+        if (matchedSize == null) {
+            Log.e(TAG, "matchPreviewRecordSize: did not find a match");
+            failTest("Cannot match preview size with video size");
+        }
+        return matchedSize;
+    }
+
+    private void setUpCamera(int id) {
+        shutdownCamera();
+
+        mCurrentCameraId = id;
+        try {
+            mCamera = Camera.open(id);
+        }
+        catch (Exception e) {
+            Log.e(TAG, "camera is not available", e);
+            failTest("camera not available" + e.getMessage());
+            return;
+        }
+
+        Camera.Parameters p = mCamera.getParameters();
+        if (VERBOSE) {
+            Log.v(TAG, "setUpCamera: setUpCamera got camera parameters");
+        }
+
+        // Get preview resolutions
+        List<Camera.Size> unsortedSizes = p.getSupportedPreviewSizes();
+
+        class SizeCompare implements Comparator<Camera.Size> {
+            @Override
+            public int compare(Camera.Size lhs, Camera.Size rhs) {
+                if (lhs.width < rhs.width) return -1;
+                if (lhs.width > rhs.width) return 1;
+                if (lhs.height < rhs.height) return -1;
+                if (lhs.height > rhs.height) return 1;
+                return 0;
+            }
+        };
+
+        SizeCompare s = new SizeCompare();
+        TreeSet<Camera.Size> sortedResolutions = new TreeSet<Camera.Size>(s);
+        sortedResolutions.addAll(unsortedSizes);
+
+        mPreviewSizes = new ArrayList<Camera.Size>(sortedResolutions);
+
+        ArrayList<VideoSizeNamePair> availableVideoSizes = getVideoSizeNamePairs(id);
+        String[] availableVideoSizeNames = new String[availableVideoSizes.size()];
+        mVideoSizeIds = new ArrayList<Integer>();
+        for (int i = 0; i < availableVideoSizes.size(); i++) {
+            availableVideoSizeNames[i] = availableVideoSizes.get(i).getSizeName();
+            mVideoSizeIds.add(availableVideoSizes.get(i).getSizeId());
+        }
+
+        mResolutionSpinner.setAdapter(
+            new ArrayAdapter<String>(
+                this, R.layout.cf_format_list_item, availableVideoSizeNames));
+
+        // Update untested
+        mUntestedCombinations.remove("All combinations for Camera " + id + "\n");
+        for (int videoSizeId: mVideoSizeIds) {
+            String combination = "Camera " + id + ", " + videoSizeId + "\n";
+            if (!mTestedCombinations.contains(combination)) {
+                mUntestedCombinations.add(combination);
+            }
+        }
+
+        // Set initial values
+        mNextPreviewSize = mPreviewSizes.get(0);
+        mCurrentVideoSizeId = mVideoSizeIds.get(0);
+        mResolutionSpinner.setSelection(0);
+
+        // Set up correct display orientation
+        CameraInfo info = new CameraInfo();
+        Camera.getCameraInfo(id, info);
+        int rotation = getWindowManager().getDefaultDisplay().getRotation();
+        int degrees = 0;
+        switch (rotation) {
+            case Surface.ROTATION_0: degrees = 0; break;
+            case Surface.ROTATION_90: degrees = 90; break;
+            case Surface.ROTATION_180: degrees = 180; break;
+            case Surface.ROTATION_270: degrees = 270; break;
+        }
+
+        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            mPreviewRotation = (info.orientation + degrees) % 360;
+            mPreviewRotation = (360 - mPreviewRotation) % 360;  // compensate the mirror
+        } else {  // back-facing
+            mPreviewRotation = (info.orientation - degrees + 360) % 360;
+        }
+        if (mPreviewRotation != 0 && mPreviewRotation != 180) {
+            Log.w(TAG,
+                "Display orientation correction is not 0 or 180, as expected!");
+        }
+
+        mCamera.setDisplayOrientation(mPreviewRotation);
+
+        // Start up preview if display is ready
+        if (mPreviewTexture != null) {
+            startPreview();
+        }
+    }
+
+    private void shutdownCamera() {
+        if (mCamera != null) {
+            mCamera.setPreviewCallback(null);
+            mCamera.stopPreview();
+            mCamera.release();
+            mCamera = null;
+        }
+    }
+
+    /**
+     * starts capturing and drawing frames on screen
+     */
+    private void startPreview() {
+
+        mCamera.stopPreview();
+
+        Matrix transform = new Matrix();
+        float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth;
+        float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight;
+        if (VERBOSE) {
+            Log.v(TAG, "startPreview: widthRatio=" + widthRatio + " " + "heightRatio=" +
+                    heightRatio);
+        }
+
+        if (heightRatio < widthRatio) {
+            transform.setScale(1, heightRatio / widthRatio);
+            transform.postTranslate(0,
+                    mPreviewTexHeight * (1 - heightRatio / widthRatio) / 2);
+            if (VERBOSE) {
+                Log.v(TAG, "startPreview: shrink vertical by " + heightRatio / widthRatio);
+            }
+        } else {
+            transform.setScale(widthRatio / heightRatio, 1);
+            transform.postTranslate(mPreviewTexWidth * (1 - widthRatio / heightRatio) / 2, 0);
+            if (VERBOSE) {
+                Log.v(TAG, "startPreview: shrink horizontal by " + widthRatio / heightRatio);
+            }
+        }
+
+        mPreviewView.setTransform(transform);
+
+        mPreviewSize = mNextPreviewSize;
+
+        Camera.Parameters p = mCamera.getParameters();
+        p.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
+        mCamera.setParameters(p);
+
+        try {
+            mCamera.setPreviewTexture(mPreviewTexture);
+            if (mPreviewTexture == null) {
+                Log.e(TAG, "preview texture is null.");
+            }
+            if (VERBOSE) {
+                Log.v(TAG, "startPreview: set preview texture in startPreview");
+            }
+            mCamera.startPreview();
+            if (VERBOSE) {
+                Log.v(TAG, "startPreview: started preview in startPreview");
+            }
+        } catch (IOException ioe) {
+            Log.e(TAG, "Unable to start up preview", ioe);
+            // Show a dialog box to tell user test failed
+            failTest("Unable to start preview.");
+        }
+    }
+
+    private void failTest(String failMessage) {
+        DialogInterface.OnClickListener dialogClickListener =
+                new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        switch (which) {
+                            case DialogInterface.BUTTON_POSITIVE:
+                                PassFailButtons.setTestResultAndFinish(CameraVideoActivity.this,
+                                        CameraVideoActivity.this.getTestId(),
+                                        CameraVideoActivity.this.getTestDetails(),
+                                        /* passed */false);
+                                break;
+                            case DialogInterface.BUTTON_NEGATIVE:
+                                break;
+                        }
+                    }
+                };
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(CameraVideoActivity.this);
+        builder.setMessage(getString(R.string.dialog_fail_test) + ". " + failMessage)
+                .setPositiveButton(R.string.fail_quit, dialogClickListener)
+                .setNegativeButton(R.string.cancel, dialogClickListener)
+                .show();
+    }
+
+}