TestingCamera: Autofocus, recording, logging, code cleanup

- Add logging into onscreen textview
- Clean up code (whitespace, refactor some long methods)
- Add AF mode selection, AF trigger, AF cancel
- Add recording support

Bug: 6243944
Change-Id: I799a99e24009677b558875a2d980bb980dd8d004
diff --git a/apps/TestingCamera/AndroidManifest.xml b/apps/TestingCamera/AndroidManifest.xml
index bb48524..cfed74b 100644
--- a/apps/TestingCamera/AndroidManifest.xml
+++ b/apps/TestingCamera/AndroidManifest.xml
@@ -22,8 +22,11 @@
   <uses-feature android:name="android.hardware.camera" />
   <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
 
-  <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="16"/>
+  <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16"/>
   <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
+  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
   <application
        android:icon="@mipmap/launcher_testingcamera"
        android:label="@string/app_name"
diff --git a/apps/TestingCamera/project.properties b/apps/TestingCamera/project.properties
index e484b3c..9b84a6b 100644
--- a/apps/TestingCamera/project.properties
+++ b/apps/TestingCamera/project.properties
@@ -11,4 +11,4 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=Google Inc.:Google APIs:16
+target=android-16
diff --git a/apps/TestingCamera/res/layout/main.xml b/apps/TestingCamera/res/layout/main.xml
index 06f3628..4478379 100644
--- a/apps/TestingCamera/res/layout/main.xml
+++ b/apps/TestingCamera/res/layout/main.xml
@@ -32,12 +32,14 @@
              android:layout_height="0px"
              android:layout_weight="6"
              />
+
         <TextView
-             android:id="@+id/log"
-             android:layout_height="wrap_content"
-             android:layout_width="fill_parent"
-             android:layout_weight="1"
-             />
+            android:id="@+id/log"
+            android:layout_width="fill_parent"
+            android:layout_height="0px"
+            android:layout_weight="1.5"
+            android:freezesText="true" />
+
     </LinearLayout>
 
     <ScrollView
@@ -74,8 +76,10 @@
 
             <Button
                 android:id="@+id/info_button"
+                style="?android:attr/buttonStyleSmall"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
+                android:layout_gravity="center"
                 android:layout_weight="1"
                 android:text="@string/show_info" />
 
@@ -106,6 +110,7 @@
                 android:id="@+id/start_preview"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
+                android:layout_gravity="center"
                 android:layout_weight="1"
                 android:textColorLink="@android:color/holo_blue_light"
                 android:textOff="@string/preview_off_label"
@@ -121,6 +126,47 @@
                 android:background="@color/horiz_rule_color" />
 
             <TextView
+                android:id="@+id/af_mode_spinner_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/af_mode_prompt"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <Spinner
+                android:id="@+id/af_mode_spinner"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <Button
+                android:id="@+id/af_button"
+                style="?android:attr/buttonStyleSmall"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:layout_weight="1"
+                android:text="@string/trigger_autofocus" />
+
+            <Button
+                android:id="@+id/af_cancel_button"
+                style="?android:attr/buttonStyleSmall"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:layout_weight="1"
+                android:text="@string/cancel_autofocus" />
+
+            <View
+                android:id="@+id/horizontal_rule_3"
+                android:layout_width="fill_parent"
+                android:layout_height="1dip"
+                android:layout_marginBottom="@dimen/horiz_rule_btm_margin"
+                android:layout_marginTop="@dimen/horiz_rule_top_margin"
+                android:layout_weight="1"
+                android:background="@color/horiz_rule_color" />
+
+            <TextView
                 android:id="@+id/textView1"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
@@ -136,13 +182,14 @@
 
             <Button
                 android:id="@+id/take_picture"
+                style="?android:attr/buttonStyleSmall"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:text="@string/take_picture_label" />
 
             <View
-                android:id="@+id/horizontal_rule_3"
+                android:id="@+id/horizontal_rule_4"
                 android:layout_width="fill_parent"
                 android:layout_height="1dip"
                 android:layout_marginBottom="@dimen/horiz_rule_btm_margin"
diff --git a/apps/TestingCamera/res/values/strings.xml b/apps/TestingCamera/res/values/strings.xml
index 69c57da..5ce8262 100644
--- a/apps/TestingCamera/res/values/strings.xml
+++ b/apps/TestingCamera/res/values/strings.xml
@@ -22,7 +22,7 @@
     <string name="preview_off_label">Preview Off</string>
     <string name="take_picture_label">Take picture</string>
     <string name="preview_resolution_prompt">Preview size</string>
-    <string name="camera_selection_prompt">Camera ID</string>
+    <string name="camera_selection_prompt">Active camera</string>
     <string name="default_camera_entry">No cameras found</string>
     <string name="snapshot_text_default">Snapshot text</string>
     <string name="snapshot_size_spinner_label">Still capture size</string>
@@ -34,4 +34,7 @@
     <string name="info_ok_button_label">OK</string>
     <string name="snapshot_ok_label">OK</string>
     <string name="still_image_description">Captured still image</string>
+    <string name="af_mode_prompt">Autofocus mode</string>
+    <string name="trigger_autofocus">Trigger autofocus</string>
+    <string name="cancel_autofocus">Cancel Autofocus</string>
 </resources>
diff --git a/apps/TestingCamera/src/com/android/testingcamera/InfoDialogFragment.java b/apps/TestingCamera/src/com/android/testingcamera/InfoDialogFragment.java
index 9499778..da5496a 100644
--- a/apps/TestingCamera/src/com/android/testingcamera/InfoDialogFragment.java
+++ b/apps/TestingCamera/src/com/android/testingcamera/InfoDialogFragment.java
@@ -37,7 +37,6 @@
         return view;
     }
 
-    @Override
     public void onClick(View v) {
         this.dismiss();
     }
diff --git a/apps/TestingCamera/src/com/android/testingcamera/SnapshotDialogFragment.java b/apps/TestingCamera/src/com/android/testingcamera/SnapshotDialogFragment.java
index 28ba3aa..9b13b95 100644
--- a/apps/TestingCamera/src/com/android/testingcamera/SnapshotDialogFragment.java
+++ b/apps/TestingCamera/src/com/android/testingcamera/SnapshotDialogFragment.java
@@ -41,9 +41,7 @@
         return view;
     }
 
-    @Override
     public void onClick(View v) {
-        // TODO Auto-generated method stub
         this.dismiss();
     }
 
diff --git a/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java b/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
index 5cf7a5e..d3403c7 100644
--- a/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
+++ b/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
@@ -22,13 +22,17 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
 import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
 import android.os.Bundle;
+import android.os.Environment;
 import android.view.View;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 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.ImageView;
@@ -37,19 +41,30 @@
 import android.widget.CompoundButton;
 import android.widget.TextView;
 import android.widget.ToggleButton;
+import android.text.Layout;
+import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
 
+import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.TreeSet;
 
 /**
  * A simple test application for the camera API.
  *
  * The goal of this application is to allow all camera API features to be
- * excercised, and all information provided by the API to be shown.
+ * exercised, and all information provided by the API to be shown.
  */
 public class TestingCamera extends Activity implements SurfaceHolder.Callback {
 
@@ -61,33 +76,46 @@
     private Button mInfoButton;
     private Spinner mPreviewSizeSpinner;
     private ToggleButton mPreviewToggle;
+    private Spinner mAutofocusModeSpinner;
+    private Button mAutofocusButton;
+    private Button mCancelAutofocusButton;
     private Spinner mSnapshotSizeSpinner;
     private Button  mTakePictureButton;
     private Spinner mCamcorderProfileSpinner;
     private ToggleButton mRecordToggle;
 
+    private TextView mLogView;
+
+    private Set<View> mPreviewOnlyControls = new HashSet<View>();
+
     /** Camera state */
     private int mCameraId = 0;
     private Camera mCamera;
     private Camera.Parameters mParams;
     private List<Camera.Size> mPreviewSizes;
     private int mPreviewSize = 0;
+    private List<String> mAfModes;
+    private int mAfMode = 0;
     private List<Camera.Size> mSnapshotSizes;
     private int mSnapshotSize = 0;
     private List<CamcorderProfile> mCamcorderProfiles;
     private int mCamcorderProfile = 0;
 
+    private MediaRecorder mRecorder;
+
     private static final int CAMERA_UNINITIALIZED = 0;
     private static final int CAMERA_OPEN = 1;
     private static final int CAMERA_PREVIEW = 2;
     private static final int CAMERA_TAKE_PICTURE = 3;
+    private static final int CAMERA_RECORD = 4;
     private int mState = CAMERA_UNINITIALIZED;
 
     /** Misc variables */
 
     private static final String TAG = "TestingCamera";
 
-    // Activity methods
+    /** Activity lifecycle */
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -109,20 +137,33 @@
         mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview);
         mPreviewToggle.setOnClickListener(mPreviewToggleListener);
 
+        mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner);
+        mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener);
+
+        mAutofocusButton = (Button) findViewById(R.id.af_button);
+        mAutofocusButton.setOnClickListener(mAutofocusButtonListener);
+        mPreviewOnlyControls.add(mAutofocusButton);
+
+        mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button);
+        mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener);
+        mPreviewOnlyControls.add(mCancelAutofocusButton);
+
         mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner);
         mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener);
 
         mTakePictureButton = (Button) findViewById(R.id.take_picture);
         mTakePictureButton.setOnClickListener(mTakePictureListener);
+        mPreviewOnlyControls.add(mTakePictureButton);
 
         mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner);
         mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener);
 
         mRecordToggle = (ToggleButton) findViewById(R.id.start_record);
         mRecordToggle.setOnClickListener(mRecordToggleListener);
+        mPreviewOnlyControls.add(mRecordToggle);
 
-        // TODO: Implement recording support
-        mRecordToggle.setVisibility(View.GONE);
+        mLogView = (TextView) findViewById(R.id.log);
+        mLogView.setMovementMethod(new ScrollingMovementMethod());
 
         int numCameras = Camera.getNumberOfCameras();
         String[] cameraNames = new String[numCameras];
@@ -138,6 +179,7 @@
     @Override
     public void onResume() {
         super.onResume();
+        log("onResume: Setting up camera");
         mPreviewHolder = null;
         setUpCamera();
     }
@@ -145,41 +187,43 @@
     @Override
     public void onPause() {
         super.onPause();
-
+        log("onPause: Releasing camera");
         mCamera.release();
         mState = CAMERA_UNINITIALIZED;
     }
 
-    // SurfaceHolder.Callback methods
-    @Override
+    /** SurfaceHolder.Callback methods */
     public void surfaceChanged(SurfaceHolder holder,
             int format,
             int width,
             int height) {
         if (mPreviewHolder != null) return;
 
-        Log.d(TAG, "Surface holder available: " + width + " x " + height);
+        log("Surface holder available: " + width + " x " + height);
         mPreviewHolder = holder;
         try {
             mCamera.setPreviewDisplay(holder);
         } catch (IOException e) {
-            Log.e(TAG, "Unable to set up preview!");
+            logE("Unable to set up preview!");
         }
-        resizePreview(mPreviewSizes.get(mPreviewSize).width,
-                mPreviewSizes.get(mPreviewSize).height);
     }
 
-    @Override
     public void surfaceCreated(SurfaceHolder holder) {
 
     }
 
-    @Override
     public void surfaceDestroyed(SurfaceHolder holder) {
-
+        mPreviewHolder = null;
     }
 
-    // UI listeners
+    /** UI controls enable/disable */
+    private void enablePreviewOnlyControls(boolean enabled) {
+        for (View v : mPreviewOnlyControls) {
+                v.setEnabled(enabled);
+        }
+    }
+
+    /** UI listeners */
 
     private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
                 new AdapterView.OnItemSelectedListener() {
@@ -191,13 +235,12 @@
             }
         }
 
-        public void onNothingSelected(AdapterView parent) {
+        public void onNothingSelected(AdapterView<?> parent) {
 
         }
     };
 
     private OnClickListener mInfoButtonListener = new OnClickListener() {
-        @Override
         public void onClick(View v) {
             FragmentManager fm = getFragmentManager();
             InfoDialogFragment infoDialog = new InfoDialogFragment();
@@ -208,13 +251,11 @@
 
     private AdapterView.OnItemSelectedListener mPreviewSizeListener =
         new AdapterView.OnItemSelectedListener() {
-        @Override
         public void onItemSelected(AdapterView<?> parent,
                 View view, int pos, long id) {
             if (pos == mPreviewSize) return;
-            Log.d(TAG, "Switching preview sizes");
-
             if (mState == CAMERA_PREVIEW) {
+                log("Stopping preview to switch resolutions");
                 mCamera.stopPreview();
             }
 
@@ -223,15 +264,18 @@
             int height = mPreviewSizes.get(mPreviewSize).height;
             mParams.setPreviewSize(width, height);
 
+            log("Setting preview size to " + width + "x" + height);
+
             mCamera.setParameters(mParams);
-            resizePreview(width, height);
 
             if (mState == CAMERA_PREVIEW) {
+                log("Restarting preview");
+                resizePreview(width, height);
                 mCamera.startPreview();
             }
         }
 
-        public void onNothingSelected(AdapterView parent) {
+        public void onNothingSelected(AdapterView<?> parent) {
 
         }
     };
@@ -240,42 +284,92 @@
             new View.OnClickListener() {
         public void onClick(View v) {
             if (mState == CAMERA_TAKE_PICTURE) {
-                Log.e(TAG, "Can't change preview state while taking picture!");
+                logE("Can't change preview state while taking picture!");
                 return;
             }
             if (mPreviewToggle.isChecked()) {
-                Log.d(TAG, "Starting preview");
-
+                log("Starting preview");
+                resizePreview(mPreviewSizes.get(mPreviewSize).width,
+                        mPreviewSizes.get(mPreviewSize).height);
                 mCamera.startPreview();
                 mState = CAMERA_PREVIEW;
-
-                mTakePictureButton.setEnabled(true);
+                enablePreviewOnlyControls(true);
             } else {
-                Log.d(TAG, "Stopping preview");
+                log("Stopping preview");
                 mCamera.stopPreview();
                 mState = CAMERA_OPEN;
 
-                mTakePictureButton.setEnabled(false);
+                enablePreviewOnlyControls(false);
             }
         }
     };
 
+    private OnItemSelectedListener mAutofocusModeListener =
+                new OnItemSelectedListener() {
+        public void onItemSelected(AdapterView<?> parent,
+                        View view, int pos, long id) {
+            if (pos == mAfMode) return;
+
+            mAfMode = pos;
+            String focusMode = mAfModes.get(mAfMode);
+            log("Setting focus mode to " + focusMode);
+            mParams.setFocusMode(focusMode);
+
+            mCamera.setParameters(mParams);
+        }
+
+        public void onNothingSelected(AdapterView<?> arg0) {
+
+        }
+    };
+
+    private OnClickListener mAutofocusButtonListener =
+            new View.OnClickListener() {
+        public void onClick(View v) {
+            log("Triggering autofocus");
+            mCamera.autoFocus(mAutofocusCallback);
+        }
+    };
+
+    private OnClickListener mCancelAutofocusButtonListener =
+            new View.OnClickListener() {
+        public void onClick(View v) {
+            log("Cancelling autofocus");
+            mCamera.cancelAutoFocus();
+        }
+    };
+
+    private Camera.AutoFocusCallback mAutofocusCallback =
+            new Camera.AutoFocusCallback() {
+        public void onAutoFocus(boolean success, Camera camera) {
+            log("Autofocus completed: " + (success ? "success" : "failure") );
+        }
+    };
+
+    private Camera.AutoFocusMoveCallback mAutofocusMoveCallback =
+            new Camera.AutoFocusMoveCallback() {
+        public void onAutoFocusMoving(boolean start, Camera camera) {
+            log("Autofocus movement: " + (start ? "starting" : "stopped") );
+        }
+    };
+
     private AdapterView.OnItemSelectedListener mSnapshotSizeListener =
-        new AdapterView.OnItemSelectedListener() {
+            new AdapterView.OnItemSelectedListener() {
         public void onItemSelected(AdapterView<?> parent,
                 View view, int pos, long id) {
             if (pos == mSnapshotSize) return;
-            Log.d(TAG, "Switching snapshot sizes");
 
             mSnapshotSize = pos;
             int width = mSnapshotSizes.get(mSnapshotSize).width;
             int height = mSnapshotSizes.get(mSnapshotSize).height;
+            log("Setting snapshot size to " + width + " x " + height);
+
             mParams.setPictureSize(width, height);
 
             mCamera.setParameters(mParams);
         }
 
-        public void onNothingSelected(AdapterView parent) {
+        public void onNothingSelected(AdapterView<?> parent) {
 
         }
     };
@@ -283,17 +377,15 @@
     private View.OnClickListener mTakePictureListener =
             new View.OnClickListener() {
         public void onClick(View v) {
-            Log.d(TAG, "Taking picture");
+            log("Taking picture");
             if (mState == CAMERA_PREVIEW) {
                 mState = CAMERA_TAKE_PICTURE;
-
-                mTakePictureButton.setEnabled(false);
-                mPreviewToggle.setEnabled(false);
+                enablePreviewOnlyControls(false);
                 mPreviewToggle.setChecked(false);
 
                 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb);
             } else {
-                Log.e(TAG, "Can't take picture while not running preview!");
+                logE("Can't take picture while not running preview!");
             }
         }
     };
@@ -302,41 +394,53 @@
                 new AdapterView.OnItemSelectedListener() {
         public void onItemSelected(AdapterView<?> parent,
                         View view, int pos, long id) {
-                mCamcorderProfile = pos;
+            if (pos == mCamcorderProfile) return;
+
+            log("Setting camcorder profile to " + ((TextView)view).getText());
+            mCamcorderProfile = pos;
         }
 
-        public void onNothingSelected(AdapterView parent) {
+        public void onNothingSelected(AdapterView<?> parent) {
 
         }
     };
 
     private View.OnClickListener mRecordToggleListener =
-                new View.OnClickListener() {
+            new View.OnClickListener() {
         public void onClick(View v) {
+            mPreviewToggle.setEnabled(false);
+            if (mState == CAMERA_PREVIEW) {
+                startRecording();
+            } else if (mState == CAMERA_RECORD) {
+                stopRecording();
+            } else {
+                logE("Can't toggle recording in current state!");
+            }
+            mPreviewToggle.setEnabled(true);
         }
     };
 
     private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() {
         public void onShutter() {
-            Log.d(TAG, "Shutter cb fired");
+            log("Shutter callback received");
         }
     };
 
     private Camera.PictureCallback mRawCb = new Camera.PictureCallback() {
         public void onPictureTaken(byte[] data, Camera camera) {
-            Log.d(TAG, "Raw cb fired");
+            log("Raw callback received");
         }
     };
 
     private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() {
         public void onPictureTaken(byte[] data, Camera camera) {
-            Log.d(TAG, "Postview cb fired");
+            log("Postview callback received");
         }
     };
 
     private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() {
         public void onPictureTaken(byte[] data, Camera camera) {
-            Log.d(TAG, "JPEG cb fired");
+            log("JPEG picture callback received");
             FragmentManager fm = getFragmentManager();
             SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment();
 
@@ -353,67 +457,36 @@
     // Internal methods
 
     void setUpCamera() {
-        Log.d(TAG, "Setting up camera " + mCameraId);
+        log("Setting up camera " + mCameraId);
+        logIndent(1);
         if (mState >= CAMERA_OPEN) {
-            Log.d(TAG, "Closing old camera");
+            log("Closing old camera");
             mCamera.release();
             mState = CAMERA_UNINITIALIZED;
         }
-        Log.d(TAG, "Opening camera " + mCameraId);
+        log("Opening camera " + mCameraId);
         mCamera = Camera.open(mCameraId);
         mState = CAMERA_OPEN;
 
         mParams = mCamera.getParameters();
 
         // Set up preview size selection
-        mPreviewSizes = mParams.getSupportedPreviewSizes();
 
-        String[] availableSizeNames = new String[mPreviewSizes.size()];
-        int i = 0;
-        for (Camera.Size previewSize: mPreviewSizes) {
-            availableSizeNames[i++] =
-                    Integer.toString(previewSize.width) + " x " +
-                    Integer.toString(previewSize.height);
-        }
-        mPreviewSizeSpinner.setAdapter(
-            new ArrayAdapter<String>(
-                this, R.layout.spinner_item, availableSizeNames));
+        log("Configuring camera");
+        logIndent(1);
 
-        mPreviewSize = 0;
-
-        int width = mPreviewSizes.get(mPreviewSize).width;
-        int height = mPreviewSizes.get(mPreviewSize).height;
-        mParams.setPreviewSize(width, height);
-
-        // Set up snapshot size selection
-
-        mSnapshotSizes = mParams.getSupportedPictureSizes();
-
-        availableSizeNames = new String[mSnapshotSizes.size()];
-        i = 0;
-        for (Camera.Size snapshotSize : mSnapshotSizes) {
-            availableSizeNames[i++] =
-                    Integer.toString(snapshotSize.width) + " x " +
-                    Integer.toString(snapshotSize.height);
-        }
-        mSnapshotSizeSpinner.setAdapter(
-            new ArrayAdapter<String>(
-                this, R.layout.spinner_item, availableSizeNames));
-
-        mSnapshotSize = 0;
-
-        width = mSnapshotSizes.get(mSnapshotSize).width;
-        height = mSnapshotSizes.get(mSnapshotSize).height;
-        mParams.setPictureSize(width, height);
-
-        // Set up camcorder profile selection
+        updatePreviewSizes(mParams);
+        updateAfModes(mParams);
+        updateSnapshotSizes(mParams);
         updateCamcorderProfile(mCameraId);
 
-        // Update parameters based on above defaults
-
+        // Update parameters based on above updates
         mCamera.setParameters(mParams);
 
+        mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback);
+
         if (mPreviewHolder != null) {
+            log("Setting preview display");
             try {
                 mCamera.setPreviewDisplay(mPreviewHolder);
             } catch(IOException e) {
@@ -421,71 +494,139 @@
             }
         }
 
+        logIndent(-1);
+
         mPreviewToggle.setEnabled(true);
         mPreviewToggle.setChecked(false);
-        mTakePictureButton.setEnabled(false);
+        enablePreviewOnlyControls(false);
 
+        int width = mPreviewSizes.get(mPreviewSize).width;
+        int height = mPreviewSizes.get(mPreviewSize).height;
         resizePreview(width, height);
         if (mPreviewToggle.isChecked()) {
-            Log.d(TAG, "Starting preview" );
-             mCamera.startPreview();
+            log("Starting preview" );
+            mCamera.startPreview();
             mState = CAMERA_PREVIEW;
         }
+        logIndent(-1);
+    }
+
+    private void updateAfModes(Parameters params) {
+        mAfModes = params.getSupportedFocusModes();
+
+        mAutofocusModeSpinner.setAdapter(
+                new ArrayAdapter<String>(this, R.layout.spinner_item,
+                        mAfModes.toArray(new String[0])));
+
+        mAfMode = 0;
+
+        params.setFocusMode(mAfModes.get(mAfMode));
+
+        log("Setting AF mode to " + mAfModes.get(mAfMode));
+    }
+
+    private void updatePreviewSizes(Camera.Parameters params) {
+        mPreviewSizes = params.getSupportedPreviewSizes();
+
+        String[] availableSizeNames = new String[mPreviewSizes.size()];
+        int i = 0;
+        for (Camera.Size previewSize: mPreviewSizes) {
+            availableSizeNames[i++] =
+                Integer.toString(previewSize.width) + " x " +
+                Integer.toString(previewSize.height);
+        }
+        mPreviewSizeSpinner.setAdapter(
+                new ArrayAdapter<String>(
+                        this, R.layout.spinner_item, availableSizeNames));
+
+        mPreviewSize = 0;
+
+        int width = mPreviewSizes.get(mPreviewSize).width;
+        int height = mPreviewSizes.get(mPreviewSize).height;
+        params.setPreviewSize(width, height);
+        log("Setting preview size to " + width + " x " + height);
+    }
+
+    private void updateSnapshotSizes(Camera.Parameters params) {
+        String[] availableSizeNames;
+        mSnapshotSizes = params.getSupportedPictureSizes();
+
+        availableSizeNames = new String[mSnapshotSizes.size()];
+        int i = 0;
+        for (Camera.Size snapshotSize : mSnapshotSizes) {
+            availableSizeNames[i++] =
+                Integer.toString(snapshotSize.width) + " x " +
+                Integer.toString(snapshotSize.height);
+        }
+        mSnapshotSizeSpinner.setAdapter(
+                new ArrayAdapter<String>(
+                        this, R.layout.spinner_item, availableSizeNames));
+
+        mSnapshotSize = 0;
+
+        int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width;
+        int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height;
+        params.setPictureSize(snapshotWidth, snapshotHeight);
+        log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight);
     }
 
     private void updateCamcorderProfile(int cameraId) {
         // Have to query all of these individually,
         final int PROFILES[] = new int[] {
-                        CamcorderProfile.QUALITY_1080P,
-                        CamcorderProfile.QUALITY_480P,
-                        CamcorderProfile.QUALITY_720P,
-                        CamcorderProfile.QUALITY_CIF,
-                        CamcorderProfile.QUALITY_HIGH,
-                        CamcorderProfile.QUALITY_LOW,
-                        CamcorderProfile.QUALITY_QCIF,
-                        CamcorderProfile.QUALITY_QVGA,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_480P,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_720P,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
-                        CamcorderProfile.QUALITY_TIME_LAPSE_QVGA
+            CamcorderProfile.QUALITY_1080P,
+            CamcorderProfile.QUALITY_480P,
+            CamcorderProfile.QUALITY_720P,
+            CamcorderProfile.QUALITY_CIF,
+            CamcorderProfile.QUALITY_HIGH,
+            CamcorderProfile.QUALITY_LOW,
+            CamcorderProfile.QUALITY_QCIF,
+            CamcorderProfile.QUALITY_QVGA,
+            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
+            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
+            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
+            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
+            CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
+            CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
+            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
+            CamcorderProfile.QUALITY_TIME_LAPSE_QVGA
         };
 
         final String PROFILE_NAMES[] = new String[] {
-                        "1080P",
-                        "480P",
-                        "720P",
-                        "CIF",
-                        "HIGH",
-                        "LOW",
-                        "QCIF",
-                        "QVGA",
-                        "TIME_LAPSE_1080P",
-                        "TIME_LAPSE_480P",
-                        "TIME_LAPSE_720P",
-                        "TIME_LAPSE_CIF",
-                        "TIME_LAPSE_HIGH",
-                        "TIME_LAPSE_LOW",
-                        "TIME_LAPSE_QCIF",
-                        "TIME_LAPSE_QVGA"
+            "1080P",
+            "480P",
+            "720P",
+            "CIF",
+            "HIGH",
+            "LOW",
+            "QCIF",
+            "QVGA",
+            "TIME_LAPSE_1080P",
+            "TIME_LAPSE_480P",
+            "TIME_LAPSE_720P",
+            "TIME_LAPSE_CIF",
+            "TIME_LAPSE_HIGH",
+            "TIME_LAPSE_LOW",
+            "TIME_LAPSE_QCIF",
+            "TIME_LAPSE_QVGA"
         };
 
         List<String> availableCamcorderProfileNames = new ArrayList<String>();
         mCamcorderProfiles = new ArrayList<CamcorderProfile>();
 
         for (int i = 0; i < PROFILES.length; i++) {
-                if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) {
-                        availableCamcorderProfileNames.add(PROFILE_NAMES[i]);
-                        mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
-                }
+            if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) {
+                availableCamcorderProfileNames.add(PROFILE_NAMES[i]);
+                mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
+            }
         }
         String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]);
         mCamcorderProfileSpinner.setAdapter(
-                        new ArrayAdapter<String>(
-                                        this, R.layout.spinner_item, nameArray));
+                new ArrayAdapter<String>(
+                        this, R.layout.spinner_item, nameArray));
+
+        mCamcorderProfile = 0;
+        log("Setting camcorder profile to " + nameArray[mCamcorderProfile]);
+
     }
 
     void resizePreview(int width, int height) {
@@ -498,4 +639,161 @@
         }
 
     }
+
+    static final int MEDIA_TYPE_IMAGE = 0;
+    static final int MEDIA_TYPE_VIDEO = 1;
+    private File getOutputMediaFile(int type){
+        // To be safe, you should check that the SDCard is mounted
+        // using Environment.getExternalStorageState() before doing this.
+
+        String state = Environment.getExternalStorageState();
+        if (!Environment.MEDIA_MOUNTED.equals(state)) {
+                return null;
+        }
+
+        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
+                  Environment.DIRECTORY_PICTURES), "TestingCamera");
+        // This location works best if you want the created images to be shared
+        // between applications and persist after your app has been uninstalled.
+
+        // Create the storage directory if it does not exist
+        if (! mediaStorageDir.exists()){
+            if (! mediaStorageDir.mkdirs()){
+                logE("Failed to create directory for pictures/video");
+                return null;
+            }
+        }
+
+        // Create a media file name
+        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");
+        } else {
+            return null;
+        }
+
+        return mediaFile;
+    }
+
+    private void startRecording() {
+        log("Starting recording");
+        logIndent(1);
+        log("Configuring MediaRecoder");
+        mCamera.unlock();
+        mRecorder = new MediaRecorder();
+        mRecorder.setOnErrorListener(mRecordingErrorListener);
+        mRecorder.setOnInfoListener(mRecordingInfoListener);
+        mRecorder.setCamera(mCamera);
+        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+        mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
+        File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
+        log("File name:" + outputFile.toString());
+        mRecorder.setOutputFile(outputFile.toString());
+
+        boolean ready = false;
+        log("Preparing MediaRecorder");
+        try {
+            mRecorder.prepare();
+            ready = true;
+        } catch (Exception e) {
+            StringWriter writer = new StringWriter();
+            e.printStackTrace(new PrintWriter(writer));
+            logE("Exception preparing MediaRecorder:\n" + writer.toString());
+        }
+
+        if (ready) {
+            try {
+                log("Starting MediaRecorder");
+                mRecorder.start();
+                mState = CAMERA_RECORD;
+                log("Recording active");
+            } catch (Exception e) {
+                StringWriter writer = new StringWriter();
+                e.printStackTrace(new PrintWriter(writer));
+                logE("Exception starting MediaRecorder:\n" + writer.toString());
+            }
+        } else {
+            mPreviewToggle.setChecked(false);
+        }
+        logIndent(-1);
+    }
+
+    private MediaRecorder.OnErrorListener mRecordingErrorListener =
+            new MediaRecorder.OnErrorListener() {
+        public void onError(MediaRecorder mr, int what, int extra) {
+            logE("MediaRecorder reports error: " + what + ", extra "
+                    + extra);
+            if (mState == CAMERA_RECORD) {
+                stopRecording();
+            }
+        }
+    };
+
+    private MediaRecorder.OnInfoListener mRecordingInfoListener =
+            new MediaRecorder.OnInfoListener() {
+        public void onInfo(MediaRecorder mr, int what, int extra) {
+            log("MediaRecorder reports info: " + what + ", extra "
+                    + extra);
+        }
+    };
+
+    private void stopRecording() {
+        log("Stopping recording");
+        if (mRecorder != null) {
+            mRecorder.stop();
+            mCamera.lock();
+            mState = CAMERA_PREVIEW;
+            mRecorder.release();
+            mRecorder = null;
+        } else {
+            logE("Recorder is unexpectedly null!");
+        }
+    }
+
+    private int mLogIndentLevel = 0;
+    private String mLogIndent = "\t";
+    /** Increment or decrement log indentation level */
+    private void logIndent(int delta) {
+        mLogIndentLevel += delta;
+        if (mLogIndentLevel < 0) mLogIndentLevel = 0;
+        char[] mLogIndentArray = new char[mLogIndentLevel + 1];
+        for (int i = -1; i < mLogIndentLevel; i++) {
+            mLogIndentArray[i + 1] = '\t';
+        }
+        mLogIndent = new String(mLogIndentArray);
+    }
+
+    SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS");
+    /** Log both to log text view and to device logcat */
+    private void log(String logLine) {
+        Log.d(TAG, logLine);
+        logAndScrollToBottom(logLine, mLogIndent);
+    }
+
+    private void logE(String logLine) {
+        Log.e(TAG, logLine);
+        logAndScrollToBottom(logLine, mLogIndent + "!!! ");
+    }
+
+    private void logAndScrollToBottom(String logLine, String logIndent) {
+        StringBuffer logEntry = new StringBuffer(32);
+        logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent);
+        logEntry.append(logLine);
+        mLogView.append(logEntry);
+        final Layout layout = mLogView.getLayout();
+        if (layout != null){
+            int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1)
+                - mLogView.getScrollY() - mLogView.getHeight();
+            if(scrollDelta > 0) {
+                mLogView.scrollBy(0, scrollDelta);
+            }
+        }
+    }
+
 }
\ No newline at end of file