am abc62149: am b2b3f226: am a0216884: Add Camera FOV manual test for LightCycle

* commit 'abc62149edfe478d14f13cc77cd31816d8c0b29f':
  Add Camera FOV manual test for LightCycle
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index de7e2bd..7bf8387 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -30,12 +30,14 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.FULLSCREEN" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-feature android:name="android.hardware.camera.front"
                   android:required="false" />
     <uses-feature android:name="android.hardware.camera.autofocus"
                   android:required="false" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -341,6 +343,30 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
         </activity>
 
+        <activity
+            android:name=".camera.fov.PhotoCaptureActivity"
+            android:label="@string/camera_fov_calibration"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
+            <intent-filter android:label="@string/camera_fov_calibration" >
+                <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.sensor.gyroscope" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
+        </activity>
+        <activity
+            android:name=".camera.fov.DetermineFovActivity"
+            android:label="@string/camera_fov_calibration"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
+        </activity>
+        <activity
+            android:name=".camera.fov.CalibrationPreferenceActivity"
+            android:label="@string/camera_fov_label_options" >
+        </activity>
+
         <activity android:name=".usb.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
                 android:configChanges="keyboardHidden|orientation|screenSize">
diff --git a/apps/CtsVerifier/res/layout/camera_fov_calibration_determine_fov.xml b/apps/CtsVerifier/res/layout/camera_fov_calibration_determine_fov.xml
new file mode 100644
index 0000000..529fe39
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/camera_fov_calibration_determine_fov.xml
@@ -0,0 +1,31 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".DetermineFovActivity" >
+
+    <SurfaceView
+        android:id="@+id/camera_fov_photo_surface"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center" >
+    </SurfaceView>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+        <SeekBar
+            android:id="@+id/camera_fov_seekBar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true" />
+        <Button
+            android:id="@+id/camera_fov_fov_done"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/camera_fov_calibration_done"
+            android:layout_above="@id/camera_fov_seekBar"
+            android:layout_alignParentRight="true" />
+    </RelativeLayout>
+
+</FrameLayout>
diff --git a/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml b/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
new file mode 100644
index 0000000..712960f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
@@ -0,0 +1,48 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".PhotoCaptureActivity" >
+
+    <SurfaceView
+        android:id="@+id/camera_fov_camera_preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center" />
+
+    <com.android.cts.verifier.camera.fov.CameraPreviewView
+        android:id="@+id/camera_fov_preview_overlay"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+
+        <Button
+            android:id="@+id/camera_fov_settings_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:text="@string/camera_fov_settings_button_text" />
+
+        <TextView
+            android:id="@+id/camera_fov_tap_to_take_photo"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:padding="10sp"
+            android:text="@string/camera_fov_tap_to_take_photo"
+            android:textSize="18sp" />
+
+        <Spinner
+            android:id="@+id/camera_fov_resolution_selector"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentLeft="true"
+            android:padding="10sp"
+            android:textSize="18sp" />
+    </RelativeLayout>
+
+</FrameLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 30d22a3..c6f74cf 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -551,4 +551,32 @@
         connect ...</string>
     <string name="p2p_waiting_for_peer_to_disconnect">Waiting for peer
         to disconnect ...</string>
+
+    <string name="camera_fov_calibration">Camera FOV Calibration</string>
+    <string name="camera_fov_calibration_done">Done</string>
+    <string name="camera_fov_general_settings">General settings</string>
+    <string name="camera_fov_label_options">Settings</string>
+    <string name="camera_fov_tap_to_take_photo">Tap to calibrate</string>
+    <string name="camera_fov_marker_distance">Marker distance (in cm)</string>
+    <string name="camera_fov_marker_distance_description">The distance in centimeters between
+        the solid lines on the target pattern.</string>
+    <string name="camera_fov_target_distance">Target distance (in cm)</string>
+    <string name="camera_fov_target_distance_description">The distance in centimeters from the
+        device to the target pattern.</string>
+    <string name="camera_fov_settings_button_text">Setup</string>
+    <string name="camera_fov_displayed_fov_label">Displayed FOV : </string>
+    <string name="camera_fov_reported_fov_label">Reported  FOV : </string>
+    <string name="camera_fov_panorama_wallpaper_title">Photo Sphere Live Wallpaper</string>
+    <string name="camera_fov_panorama_wallpaper_description">This live wallapper displays photo
+        spheres.</string>
+    <string name="camera_fov_select_panorama">Select panorama</string>
+    <string name="camera_fov_select_panorama_description">Select a panorama to display in the
+       live wallpaper.</string>
+    <string name="camera_fov_reset_panorama">Reset panorama</string>
+    <string name="camera_fov_reset_panorama_description">Resets the panorama to show the demo
+        file.</string>
+    <string name="camera_fov_enable_compass_mode">Enable compass mode</string>
+    <string name="camera_fov_enable_compass_mode_description">If enabled, the panorama orients
+        itself according to the current rotation of the device.</string>
+
 </resources>
diff --git a/apps/CtsVerifier/res/xml/camera_fov_calibration_preferences.xml b/apps/CtsVerifier/res/xml/camera_fov_calibration_preferences.xml
new file mode 100644
index 0000000..7cf4c40
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/camera_fov_calibration_preferences.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orderingFromXml="true"
+    android:title="@string/camera_fov_label_options" >
+
+    <PreferenceCategory android:title="@string/camera_fov_general_settings" >
+        <EditTextPreference
+            android:defaultValue="36.8"
+            android:key="markerDistance"
+            android:numeric="decimal"
+            android:summary="@string/camera_fov_marker_distance_description"
+            android:title="@string/camera_fov_marker_distance" />
+        <EditTextPreference
+            android:defaultValue="99.7"
+            android:key="targetDistanceCm"
+            android:numeric="decimal"
+            android:summary="@string/camera_fov_target_distance_description"
+            android:title="@string/camera_fov_target_distance" />
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CalibrationPreferenceActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CalibrationPreferenceActivity.java
new file mode 100644
index 0000000..a3a980ef
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CalibrationPreferenceActivity.java
@@ -0,0 +1,24 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.cts.verifier.camera.fov;
+
+import com.android.cts.verifier.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ * Preferences for the LightCycle calibration app.
+ *
+ * @author haeberling@google.com (Sascha Haeberling)
+ */
+public class CalibrationPreferenceActivity extends PreferenceActivity {
+  public static final String OPTION_MARKER_DISTANCE = "markerDistance";
+  public static final String OPTION_TARGET_DISTANCE = "targetDistanceCm";
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    addPreferencesFromResource(R.xml.camera_fov_calibration_preferences);
+  }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CameraPreviewView.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CameraPreviewView.java
new file mode 100644
index 0000000..dcb6440
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CameraPreviewView.java
@@ -0,0 +1,67 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+package com.android.cts.verifier.camera.fov;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * View that draws an overlay on the camera preview.
+ * @author settinger@google.com(Scott Ettinger)
+ */
+class CameraPreviewView extends View {
+
+    private static final int GRID_ALPHA = 50;
+    private static final int GRID_WIDTH = 50;
+    private Paint mPaint = new Paint();
+
+    public CameraPreviewView(Context context) {
+        super(context);
+        this.setWillNotDraw(false);
+    }
+
+    public CameraPreviewView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        this.setWillNotDraw(false);
+    }
+
+    public CameraPreviewView(
+            Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        this.setWillNotDraw(false);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Draw a single vertical line on the center of the image to help align
+        // the camera when setting up.
+        float centerX = canvas.getWidth() / 2.0f;
+        float centerY = canvas.getHeight() / 2.0f;
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setColor(Color.GREEN);
+        mPaint.setStrokeWidth(3);
+        canvas.drawLine(centerX, 0, centerX, canvas.getHeight(), mPaint);
+
+        // Draw the transparent grid.
+        mPaint.setAlpha(GRID_ALPHA);
+        int vertLines = canvas.getWidth() / 2 / GRID_WIDTH; 
+        int horizLines = canvas.getHeight() / 2 / GRID_WIDTH; 
+        for (int i = 0; i < horizLines; ++i) { 
+            int y = (int) centerY - i * GRID_WIDTH;
+            canvas.drawLine(0, y, canvas.getWidth(), y, mPaint);
+            y = (int) centerY + i * GRID_WIDTH;
+            canvas.drawLine(0, y, canvas.getWidth(), y, mPaint);
+        }
+        for (int i = 0; i < vertLines; ++i) { 
+            int x = (int) centerX - i * GRID_WIDTH;
+            canvas.drawLine(x, 0, x, canvas.getHeight(), mPaint);
+            x = (int) centerX + i * GRID_WIDTH;
+            canvas.drawLine(x, 0, x, canvas.getHeight(), mPaint);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CtsTestHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CtsTestHelper.java
new file mode 100644
index 0000000..c080729
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/CtsTestHelper.java
@@ -0,0 +1,60 @@
+/*
+ * 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.fov;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import java.util.List;
+
+class CtsTestHelper {
+
+    private static final String REPORTED_FOV_EXTRA = "lightcycle.reported_fov";
+    private static final String MEASURED_FOV_EXTRA = "lightcycle.measured_fov";
+
+    public static void storeCtsTestResult(Activity activity, float reportedFOV, float measuredFOV) {
+        Intent it = new Intent();
+        it.putExtra(REPORTED_FOV_EXTRA, reportedFOV);
+        it.putExtra(MEASURED_FOV_EXTRA, measuredFOV);
+        activity.setResult(Activity.RESULT_OK, it);
+    }
+
+    public static float getMeasuredFOV(Intent intent) {
+        return intent.getFloatExtra(MEASURED_FOV_EXTRA, -1f);
+    }
+
+    public static float getReportedFOV(Intent intent) {
+        return intent.getFloatExtra(REPORTED_FOV_EXTRA, -1f);
+    }
+
+    public static boolean isResultPassed(float reportedFOV, float measuredFOV) {
+        if (Math.abs(reportedFOV - measuredFOV) < 2f) return true;
+        return false;
+    }
+
+    public static String getTestDetails(List<SelectableResolution> resolutions) {
+        String details = "PhotoSphere FOV test result:\n";
+        for (int i = 0; i < resolutions.size(); i++) {
+            SelectableResolution res = resolutions.get(i);
+            details += "Resolution:" + res.width + 'x' + res.height
+                    + ", Measured FOV = " + res.measuredFOV + '\n';
+
+        }
+
+        return details;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/DetermineFovActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/DetermineFovActivity.java
new file mode 100644
index 0000000..8b989e1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/DetermineFovActivity.java
@@ -0,0 +1,255 @@
+/*
+ * 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.fov;
+
+import com.android.cts.verifier.R;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Shows the picture taken and lets the user specify the field of view (FOV).
+ */
+public class DetermineFovActivity extends Activity {
+
+    private static final float FOV_ADJUSTMENT_RANGE = 20;
+    private static final int SEEKBAR_MAX_VALUE = 100;
+    private static final float TEXT_SIZE = 16;
+    private static final float TEXT_PADDING = 0.2f;
+    private static final String DEFAULT_MARKER_DISTANCE = "36.8";
+    private static final String DEFAULT_TARGET_DISTANCE = "99.7";
+
+    private float mMarkerDistanceCm;
+    private SurfaceView mSurfaceView;
+    private SurfaceHolder mSurfaceHolder;
+    private Bitmap mPhotoBitmap;
+    private float mFovMinDegrees;
+    private float mFovMaxDegrees;
+    private float mFovDegrees;
+    private float mReportedFovDegrees;
+    private SeekBar mSeekBar;
+    private Button mDoneButton;
+    private float mTargetDistanceCm;
+    private String mMeasuredText;
+    private String mReportedText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.camera_fov_calibration_determine_fov);
+        File pictureFile = PhotoCaptureActivity.getPictureFile(this);
+        try {
+            mPhotoBitmap =
+                    BitmapFactory.decodeStream(new FileInputStream(pictureFile));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        mSurfaceView = (SurfaceView) findViewById(R.id.camera_fov_photo_surface);
+        mSurfaceHolder = mSurfaceView.getHolder();
+        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {}
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            drawContents();
+        }
+
+        @Override
+        public void surfaceChanged(
+                SurfaceHolder holder, int format, int width, int height) {
+            drawContents();
+        }
+        });
+
+        mSeekBar = (SeekBar) findViewById(R.id.camera_fov_seekBar);
+        mSeekBar.setMax(SEEKBAR_MAX_VALUE);
+        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onProgressChanged(
+                    SeekBar seekBar, int progress, boolean fromUser) {
+                mFovDegrees = seekBarProgressToFovDegrees(progress);
+                drawContents();
+            }
+        });
+
+        mDoneButton = (Button) findViewById(R.id.camera_fov_fov_done);
+        mDoneButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setResult(RESULT_OK);
+                CtsTestHelper.storeCtsTestResult(DetermineFovActivity.this,
+                        mReportedFovDegrees, mFovDegrees);
+                finish();
+            }
+        });
+    }
+
+    private int fovToSeekBarProgress(float fovDegrees) {
+        return Math.round((fovDegrees - mFovMinDegrees)
+                / (mFovMaxDegrees - mFovMinDegrees) * SEEKBAR_MAX_VALUE);
+    }
+
+    private float seekBarProgressToFovDegrees(int progress) {
+        float degrees = mFovMinDegrees + (float) progress / SEEKBAR_MAX_VALUE
+                * (mFovMaxDegrees - mFovMinDegrees);
+        // keep only 2 decimal places.
+        return (int) (degrees * 100) / 100.0f;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        setResult(RESULT_CANCELED);
+        mMarkerDistanceCm = getMarkerDistance();
+        mTargetDistanceCm = getTargetDistance();
+        mReportedFovDegrees = PhotoCaptureActivity.getReportedFovDegrees();
+
+        mFovDegrees = mReportedFovDegrees > 80 ? 60 : mReportedFovDegrees;
+        mFovMaxDegrees = mFovDegrees + FOV_ADJUSTMENT_RANGE / 2;
+        mFovMinDegrees = mFovDegrees - FOV_ADJUSTMENT_RANGE / 2;
+
+        mMeasuredText = getResources().getString(R.string.camera_fov_displayed_fov_label);
+        mReportedText = getResources().getString(R.string.camera_fov_reported_fov_label);
+
+        mSeekBar.setProgress(fovToSeekBarProgress(mFovDegrees));
+        drawContents();
+    }
+
+    private float getMarkerDistance() {
+        // Get the marker distance from the preferences.
+        SharedPreferences prefs =
+                PreferenceManager.getDefaultSharedPreferences(this);
+        return Float.parseFloat(prefs.getString(
+                CalibrationPreferenceActivity.OPTION_MARKER_DISTANCE,
+                DEFAULT_MARKER_DISTANCE));
+    }
+
+    private float getTargetDistance() {
+        // Get the marker distance from the preferences.
+        SharedPreferences prefs =
+                PreferenceManager.getDefaultSharedPreferences(this);
+        return Float.parseFloat(prefs.getString(
+                CalibrationPreferenceActivity.OPTION_TARGET_DISTANCE,
+                DEFAULT_TARGET_DISTANCE));
+    }
+
+    private float focalLengthPixels(float fovDegrees, float imageWidth) {
+        return (float) (imageWidth
+                / (2 * Math.tan(fovDegrees / 2 * Math.PI / 180.0f)));
+    }
+
+    private void drawContents() {
+        SurfaceHolder holder = mSurfaceView.getHolder();
+        Canvas canvas = holder.lockCanvas();
+        if (canvas == null || mPhotoBitmap == null) {
+            return;
+        }
+
+        int canvasWidth = canvas.getWidth();
+        int canvasHeight = canvas.getHeight();
+        int photoWidth = mPhotoBitmap.getWidth();
+        int photoHeight = mPhotoBitmap.getHeight();
+        RectF drawRect = new RectF();
+
+        // Determine if the canvas aspect ratio is larger than that of the photo.
+        float scale = (float) canvasWidth / photoWidth;
+        int scaledHeight = (int) (scale * photoHeight);
+        if (scaledHeight < canvasHeight) {
+            // If the aspect ratio is smaller, set the destination rectangle to pad
+            // vertically.
+            int pad = (canvasHeight - scaledHeight) / 2;
+            drawRect.set(0, pad, canvasWidth, pad + scaledHeight - 1);
+        } else {
+            // Set the destination rectangle to pad horizontally.
+            scale = (float) canvasHeight / photoHeight;
+            float scaledWidth = scale * photoWidth;
+            float pad = (canvasWidth - scaledWidth) / 2;
+            drawRect.set(pad, 0, pad + scaledWidth - 1, canvasHeight);
+        }
+
+        // Draw the photo.
+        canvas.drawColor(Color.BLACK);
+        canvas.drawBitmap(mPhotoBitmap, null, drawRect, null);
+
+        // Draw the fov indicator text.
+        Paint paint = new Paint();
+        paint.setColor(0xffffffff);
+        float textSize = TEXT_SIZE * DetermineFovActivity.this.getResources()
+                .getDisplayMetrics().scaledDensity;
+        paint.setTextSize(textSize);
+        canvas.drawText(mMeasuredText + " " + mFovDegrees + " degrees.", textSize,
+                2 * textSize * (1.0f + TEXT_PADDING), paint);
+        canvas.drawText(mReportedText + " " + mReportedFovDegrees + " degrees.",
+                textSize, textSize * (1.0f + TEXT_PADDING), paint);
+
+        // Draw the image center circle.
+        paint.setColor(Color.BLACK);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(3);
+        float dstWidth = drawRect.right - drawRect.left + 1;
+        float dstHeight = drawRect.bottom - drawRect.top + 1;
+        float centerX = drawRect.left + dstWidth / 2;
+        canvas.drawLine(centerX, drawRect.top, centerX, drawRect.bottom, paint);
+
+        // Project the markers into the scaled image with the given field of view.
+        float markerX = mMarkerDistanceCm / 2;
+        float markerZ = mTargetDistanceCm;
+        float focalLength = focalLengthPixels(mFovDegrees, dstWidth);
+        float dx = markerX / markerZ * focalLength;
+        float projectedMarkerLeft = dstWidth / 2 - dx;
+        float projectedMarkerRight = dstWidth / 2 + dx;
+
+        // Draw the marker lines over the image.
+        paint.setColor(Color.GREEN);
+        paint.setStrokeWidth(2);
+        float markerImageLeft = projectedMarkerLeft + drawRect.left;
+        canvas.drawLine(
+                markerImageLeft, drawRect.top, markerImageLeft, drawRect.bottom, paint);
+        float markerImageRight = projectedMarkerRight + drawRect.left;
+        canvas.drawLine(markerImageRight, drawRect.top, markerImageRight,
+                        drawRect.bottom, paint);
+
+        holder.unlockCanvasAndPost(canvas);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
new file mode 100644
index 0000000..c5957c0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
@@ -0,0 +1,339 @@
+/*
+ * 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.fov;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.hardware.Camera;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An activity for showing the camera preview and taking a picture.
+ */
+public class PhotoCaptureActivity extends Activity
+implements PictureCallback, SurfaceHolder.Callback {
+    private static final String TAG = PhotoCaptureActivity.class.getSimpleName();
+    private static final int FOV_REQUEST_CODE = 1006;
+    private static final String PICTURE_FILENAME = "photo.jpg";
+    private static float mReportedFovDegrees = 0;
+
+    private SurfaceView mPreview;
+    private SurfaceHolder mSurfaceHolder;
+    private Spinner mResolutionSpinner;
+    private List<SelectableResolution> mSupportedResolutions;
+    private ArrayAdapter<SelectableResolution> mAdapter;
+
+    private Camera mCamera;
+    private boolean mCameraInitialized = false;
+    private boolean mPreviewActive = false;
+    private int mResolutionSpinnerIndex = -1;
+    private WakeLock mWakeLock;
+
+    public static File getPictureFile(Context context) {
+        return new File(context.getExternalCacheDir(), PICTURE_FILENAME);
+    }
+
+    public static float getReportedFovDegrees() {
+        return mReportedFovDegrees;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.camera_fov_calibration_photo_capture);
+
+        mPreview = (SurfaceView) findViewById(R.id.camera_fov_camera_preview);
+        mSurfaceHolder = mPreview.getHolder();
+        mSurfaceHolder.addCallback(this);
+
+        // This is required for older versions of Android hardware.
+        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+
+        TextView textView = (TextView) findViewById(R.id.camera_fov_tap_to_take_photo);
+        textView.setTextColor(Color.WHITE);
+
+        Button setupButton = (Button) findViewById(R.id.camera_fov_settings_button);
+        setupButton.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                startActivity(new Intent(
+                        PhotoCaptureActivity.this, CalibrationPreferenceActivity.class));
+            }
+        });
+
+        View previewView = findViewById(R.id.camera_fov_preview_overlay);
+        previewView.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mCamera.takePicture(null, null, PhotoCaptureActivity.this);
+            }
+        });
+
+        mResolutionSpinner = (Spinner) findViewById(R.id.camera_fov_resolution_selector);
+        mResolutionSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(
+                    AdapterView<?> parent, View view, int position, long id) {
+                if (mSupportedResolutions != null) {
+                    SelectableResolution resolution = mSupportedResolutions.get(position);
+
+                    Camera.Parameters params = mCamera.getParameters();
+                    params.setPictureSize(resolution.width, resolution.height);
+                    mCamera.setParameters(params);
+                    mResolutionSpinnerIndex = position;
+                }
+            }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> arg0) {}
+        });
+    }
+
+    /**
+     * Get the best supported focus mode.
+     *
+     * @param camera - Android camera object.
+     * @return the best supported focus mode.
+     */
+    protected String getFocusMode(Camera camera) {
+        List<String> modes = camera.getParameters().getSupportedFocusModes();
+        if (modes != null) {
+            if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
+                Log.v(TAG, "Using Focus mode infinity");
+                return Camera.Parameters.FOCUS_MODE_INFINITY;
+            }
+            if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
+                Log.v(TAG, "Using Focus mode fixed");
+                return Camera.Parameters.FOCUS_MODE_FIXED;
+            }
+        }
+        Log.v(TAG, "Using Focus mode auto.");
+        return Camera.Parameters.FOCUS_MODE_AUTO;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCamera = Camera.open();
+
+        // Keep the device from going to sleep.
+        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+        mWakeLock.acquire();
+
+        if (mSupportedResolutions == null) {
+            // Get the supported picture sizes and fill the spinner.
+            List<Size> supportedSizes =
+                    mCamera.getParameters().getSupportedPictureSizes();
+            mSupportedResolutions = new ArrayList<SelectableResolution>();
+            for (Size size : supportedSizes) {
+                mSupportedResolutions.add(
+                        new SelectableResolution(size.width, size.height));
+            }
+        }
+
+        // find teh first untested one.
+        for (mResolutionSpinnerIndex = 0;
+                mResolutionSpinnerIndex < mSupportedResolutions.size();
+                mResolutionSpinnerIndex++) {
+            if (!mSupportedResolutions.get(mResolutionSpinnerIndex).tested) break;
+        }
+
+        mAdapter = new ArrayAdapter<SelectableResolution>(
+                this, android.R.layout.simple_spinner_dropdown_item,
+                mSupportedResolutions);
+        mResolutionSpinner.setAdapter(mAdapter);
+
+        mResolutionSpinner.setSelection(mResolutionSpinnerIndex);
+        setResult(RESULT_CANCELED);
+    }
+
+    @Override
+    public void onPause() {
+        if (mPreviewActive) {
+            mCamera.stopPreview();
+        }
+
+        mCamera.release();
+        mCamera = null;
+        mPreviewActive = false;
+        mWakeLock.release();
+
+        super.onPause();
+    }
+
+    @Override
+    public void onPictureTaken(byte[] data, Camera camera) {
+        File pictureFile = getPictureFile(this);
+        Camera.Parameters params = mCamera.getParameters();
+        mReportedFovDegrees = params.getHorizontalViewAngle();
+        try {
+            FileOutputStream fos = new FileOutputStream(pictureFile);
+            fos.write(data);
+            fos.close();
+            Log.d(TAG, "File saved to " + pictureFile.getAbsolutePath());
+
+            // Start activity which will use he taken picture to determine the FOV.
+            startActivityForResult(new Intent(this, DetermineFovActivity.class),
+                    FOV_REQUEST_CODE + mResolutionSpinnerIndex, null);
+        } catch (IOException e) {
+            Log.e(TAG, "Could not save picture file.", e);
+            Toast.makeText(this, "Could not save picture file: " + e.getMessage(),
+                           Toast.LENGTH_LONG).show();
+            return;
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode != RESULT_OK) return;
+        int testIndex = requestCode - FOV_REQUEST_CODE;
+        SelectableResolution res = mSupportedResolutions.get(testIndex);
+        res.tested = true;
+        float reportedFOV = CtsTestHelper.getReportedFOV(data);
+        float measuredFOV = CtsTestHelper.getMeasuredFOV(data);
+        res.measuredFOV = measuredFOV;
+        if (CtsTestHelper.isResultPassed(reportedFOV, measuredFOV)) {
+            res.passed = true;
+        }
+
+        boolean allTested = true;
+        for (int i = 0; i < mSupportedResolutions.size(); i++) {
+            if (!mSupportedResolutions.get(i).tested) {
+                allTested = false;
+                break;
+            }
+        }
+        if (!allTested) {
+            mAdapter.notifyDataSetChanged();
+            return;
+        }
+
+        boolean allPassed = true;
+        for (int i = 0; i < mSupportedResolutions.size(); i++) {
+            if (!mSupportedResolutions.get(i).passed) {
+                allPassed = false;
+                break;
+            }
+        }
+        if (allPassed) {
+            TestResult.setPassedResult(this, getClass().getName(),
+                                       CtsTestHelper.getTestDetails(mSupportedResolutions));
+        } else {
+            TestResult.setFailedResult(this, getClass().getName(),
+                                       CtsTestHelper.getTestDetails(mSupportedResolutions));
+        }
+        finish();
+    }
+
+    @Override
+    public void surfaceChanged(
+            SurfaceHolder holder, int format, int width, int height) {
+        if (mCamera == null || mSurfaceHolder.getSurface() == null) {
+            return;
+        }
+
+        try {
+            mCamera.setPreviewDisplay(mSurfaceHolder);
+        } catch (Throwable t) {
+            Log.e("TAG", "Could not set preview display", t);
+            Toast.makeText(this, t.getMessage(), Toast.LENGTH_LONG).show();
+            return;
+        }
+
+        // The picture size is taken and set from the spinner selection callback.
+        Camera.Parameters params = mCamera.getParameters();
+        params.setJpegThumbnailSize(0, 0);
+        params.setJpegQuality(100);
+        params.setFocusMode(getFocusMode(mCamera));
+        params.setZoom(0);
+
+        Camera.Size size = getBestPreviewSize(width, height, params);
+        if (size != null) {
+            params.setPreviewSize(size.width, size.height);
+            mCamera.setParameters(params);
+            mCameraInitialized = true;
+        }
+        startPreview();
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        // Nothing to do.
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // Nothing to do.
+    }
+
+    private void startPreview() {
+        if (mCameraInitialized && mCamera != null) {
+            mCamera.startPreview();
+            mPreviewActive = true;
+        }
+    }
+
+    private Camera.Size getBestPreviewSize(
+            int width, int height, Camera.Parameters parameters) {
+        Camera.Size result = null;
+
+        for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
+            if (size.width <= width && size.height <= height) {
+                if (result == null) {
+                    result = size;
+                } else {
+                    int resultArea = result.width * result.height;
+                    int newArea = size.width * size.height;
+
+                    if (newArea > resultArea) {
+                        result = size;
+                    }
+                }
+            }
+        }
+        return (result);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/SelectableResolution.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/SelectableResolution.java
new file mode 100644
index 0000000..5e87eee
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/SelectableResolution.java
@@ -0,0 +1,40 @@
+/*
+ * 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.fov;
+
+/**
+ * A resolution to be used in the adapter that feeds the resolution spinner.
+ */
+public class SelectableResolution {
+    public final int width;
+    public final int height;
+    public boolean passed;
+    public boolean tested;
+    public float measuredFOV;
+
+    public SelectableResolution(int width, int height) {
+        this.width = width;
+        this.height = height;
+        passed = false;
+        tested = false;
+    }
+
+    @Override
+    public String toString() {
+        return width + " x " + height + " - " + (!tested ? "untested" : "done");
+    }
+}