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();
+ }
+
+}