Home | History | Annotate | Download | only in testingcamera
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.testingcamera;
     18 
     19 import android.annotation.SuppressLint;
     20 import android.app.Activity;
     21 import android.app.FragmentManager;
     22 import android.content.res.Resources;
     23 import android.graphics.ImageFormat;
     24 import android.hardware.Camera;
     25 import android.hardware.Camera.Parameters;
     26 import android.hardware.Camera.ErrorCallback;
     27 import android.media.CamcorderProfile;
     28 import android.media.MediaRecorder;
     29 import android.media.MediaScannerConnection;
     30 import android.net.Uri;
     31 import android.os.Bundle;
     32 import android.os.Environment;
     33 import android.os.Handler;
     34 import android.os.SystemClock;
     35 import android.view.View;
     36 import android.view.SurfaceHolder;
     37 import android.view.SurfaceView;
     38 import android.view.View.OnClickListener;
     39 import android.widget.AdapterView;
     40 import android.widget.AdapterView.OnItemSelectedListener;
     41 import android.widget.ArrayAdapter;
     42 import android.widget.Button;
     43 import android.widget.CheckBox;
     44 import android.widget.LinearLayout;
     45 import android.widget.LinearLayout.LayoutParams;
     46 import android.widget.Spinner;
     47 import android.widget.TextView;
     48 import android.widget.ToggleButton;
     49 import android.renderscript.RenderScript;
     50 import android.text.Layout;
     51 import android.text.method.ScrollingMovementMethod;
     52 import android.util.Log;
     53 import android.util.SparseArray;
     54 
     55 import java.io.File;
     56 import java.io.IOException;
     57 import java.io.PrintWriter;
     58 import java.io.StringWriter;
     59 import java.text.SimpleDateFormat;
     60 import java.util.ArrayList;
     61 import java.util.Date;
     62 import java.util.HashSet;
     63 import java.util.List;
     64 import java.util.Set;
     65 
     66 /**
     67  * A simple test application for the camera API.
     68  *
     69  * The goal of this application is to allow all camera API features to be
     70  * exercised, and all information provided by the API to be shown.
     71  */
     72 public class TestingCamera extends Activity
     73     implements SurfaceHolder.Callback, Camera.PreviewCallback,
     74         Camera.ErrorCallback {
     75 
     76     /** UI elements */
     77     private SurfaceView mPreviewView;
     78     private SurfaceHolder mPreviewHolder;
     79     private LinearLayout mPreviewColumn;
     80 
     81     private SurfaceView mCallbackView;
     82     private SurfaceHolder mCallbackHolder;
     83 
     84     private Spinner mCameraSpinner;
     85     private CheckBox mKeepOpenCheckBox;
     86     private Button mInfoButton;
     87     private Spinner mPreviewSizeSpinner;
     88     private Spinner mPreviewFrameRateSpinner;
     89     private ToggleButton mPreviewToggle;
     90     private Spinner mAutofocusModeSpinner;
     91     private Button mAutofocusButton;
     92     private Button mCancelAutofocusButton;
     93     private TextView mFlashModeSpinnerLabel;
     94     private Spinner mFlashModeSpinner;
     95     private ToggleButton mExposureLockToggle;
     96     private Spinner mSnapshotSizeSpinner;
     97     private Button  mTakePictureButton;
     98     private Spinner mCamcorderProfileSpinner;
     99     private Spinner mVideoRecordSizeSpinner;
    100     private Spinner mVideoFrameRateSpinner;
    101     private ToggleButton mRecordToggle;
    102     private CheckBox mRecordHandoffCheckBox;
    103     private ToggleButton mRecordStabilizationToggle;
    104     private Spinner mCallbackFormatSpinner;
    105     private ToggleButton mCallbackToggle;
    106 
    107     private TextView mLogView;
    108 
    109     private Set<View> mOpenOnlyControls = new HashSet<View>();
    110     private Set<View> mPreviewOnlyControls = new HashSet<View>();
    111 
    112     private SparseArray<String> mFormatNames;
    113 
    114     /** Camera state */
    115     private int mCameraId;
    116     private Camera mCamera;
    117     private Camera.Parameters mParams;
    118     private List<Camera.Size> mPreviewSizes;
    119     private int mPreviewSize = 0;
    120     private List<Integer> mPreviewFrameRates;
    121     private int mPreviewFrameRate = 0;
    122     private List<Integer> mPreviewFormats;
    123     private int mPreviewFormat = 0;
    124     private List<String> mAfModes;
    125     private int mAfMode = 0;
    126     private List<String> mFlashModes;
    127     private int mFlashMode = 0;
    128     private List<Camera.Size> mSnapshotSizes;
    129     private int mSnapshotSize = 0;
    130     private List<CamcorderProfile> mCamcorderProfiles;
    131     private int mCamcorderProfile = 0;
    132     private List<Camera.Size> mVideoRecordSizes;
    133     private int mVideoRecordSize = 0;
    134     private List<Integer> mVideoFrameRates;
    135     private int mVideoFrameRate = 0;
    136 
    137     private MediaRecorder mRecorder;
    138     private File mRecordingFile;
    139 
    140     private RenderScript mRS;
    141 
    142     private boolean mCallbacksEnabled = false;
    143     private CallbackProcessor mCallbackProcessor = null;
    144     long mLastCallbackTimestamp = -1;
    145     float mCallbackAvgFrameDuration = 30;
    146     int mCallbackFrameCount = 0;
    147     private static final float MEAN_FPS_HISTORY_COEFF = 0.9f;
    148     private static final float MEAN_FPS_MEASUREMENT_COEFF = 0.1f;
    149     private static final int   FPS_REPORTING_PERIOD = 200; // frames
    150     private static final int CALLBACK_BUFFER_COUNT = 3;
    151 
    152     private static final int CAMERA_UNINITIALIZED = 0;
    153     private static final int CAMERA_OPEN = 1;
    154     private static final int CAMERA_PREVIEW = 2;
    155     private static final int CAMERA_TAKE_PICTURE = 3;
    156     private static final int CAMERA_RECORD = 4;
    157     private int mState = CAMERA_UNINITIALIZED;
    158 
    159     private static final int NO_CAMERA_ID = -1;
    160 
    161     /** Misc variables */
    162 
    163     private static final String TAG = "TestingCamera";
    164 
    165 
    166     /** Activity lifecycle */
    167 
    168     @Override
    169     public void onCreate(Bundle savedInstanceState) {
    170         super.onCreate(savedInstanceState);
    171 
    172         setContentView(R.layout.main);
    173 
    174         mPreviewColumn = (LinearLayout) findViewById(R.id.preview_column);
    175 
    176         mPreviewView = (SurfaceView) findViewById(R.id.preview);
    177         mPreviewView.getHolder().addCallback(this);
    178 
    179         mCallbackView = (SurfaceView)findViewById(R.id.callback_view);
    180 
    181         mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner);
    182         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
    183 
    184         mKeepOpenCheckBox = (CheckBox) findViewById(R.id.keep_open_checkbox);
    185 
    186         mInfoButton = (Button) findViewById(R.id.info_button);
    187         mInfoButton.setOnClickListener(mInfoButtonListener);
    188         mOpenOnlyControls.add(mInfoButton);
    189 
    190         mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner);
    191         mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener);
    192         mOpenOnlyControls.add(mPreviewSizeSpinner);
    193 
    194         mPreviewFrameRateSpinner = (Spinner) findViewById(R.id.preview_frame_rate_spinner);
    195         mPreviewFrameRateSpinner.setOnItemSelectedListener(mPreviewFrameRateListener);
    196         mOpenOnlyControls.add(mPreviewFrameRateSpinner);
    197 
    198         mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview);
    199         mPreviewToggle.setOnClickListener(mPreviewToggleListener);
    200         mOpenOnlyControls.add(mPreviewToggle);
    201 
    202         mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner);
    203         mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener);
    204         mOpenOnlyControls.add(mAutofocusModeSpinner);
    205 
    206         mAutofocusButton = (Button) findViewById(R.id.af_button);
    207         mAutofocusButton.setOnClickListener(mAutofocusButtonListener);
    208         mPreviewOnlyControls.add(mAutofocusButton);
    209 
    210         mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button);
    211         mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener);
    212         mPreviewOnlyControls.add(mCancelAutofocusButton);
    213 
    214         mFlashModeSpinnerLabel = (TextView) findViewById(R.id.flash_mode_spinner_label);
    215 
    216         mFlashModeSpinner = (Spinner) findViewById(R.id.flash_mode_spinner);
    217         mFlashModeSpinner.setOnItemSelectedListener(mFlashModeListener);
    218         mOpenOnlyControls.add(mFlashModeSpinner);
    219 
    220         mExposureLockToggle = (ToggleButton) findViewById(R.id.exposure_lock);
    221         mExposureLockToggle.setOnClickListener(mExposureLockToggleListener);
    222         mOpenOnlyControls.add(mExposureLockToggle);
    223 
    224         mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner);
    225         mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener);
    226         mOpenOnlyControls.add(mSnapshotSizeSpinner);
    227 
    228         mTakePictureButton = (Button) findViewById(R.id.take_picture);
    229         mTakePictureButton.setOnClickListener(mTakePictureListener);
    230         mPreviewOnlyControls.add(mTakePictureButton);
    231 
    232         mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner);
    233         mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener);
    234         mOpenOnlyControls.add(mCamcorderProfileSpinner);
    235 
    236         mVideoRecordSizeSpinner = (Spinner) findViewById(R.id.video_record_size_spinner);
    237         mVideoRecordSizeSpinner.setOnItemSelectedListener(mVideoRecordSizeListener);
    238         mOpenOnlyControls.add(mVideoRecordSizeSpinner);
    239 
    240         mVideoFrameRateSpinner = (Spinner) findViewById(R.id.video_frame_rate_spinner);
    241         mVideoFrameRateSpinner.setOnItemSelectedListener(mVideoFrameRateListener);
    242         mOpenOnlyControls.add(mVideoFrameRateSpinner);
    243 
    244         mRecordToggle = (ToggleButton) findViewById(R.id.start_record);
    245         mRecordToggle.setOnClickListener(mRecordToggleListener);
    246         mPreviewOnlyControls.add(mRecordToggle);
    247 
    248         mRecordHandoffCheckBox = (CheckBox) findViewById(R.id.record_handoff_checkbox);
    249 
    250         mRecordStabilizationToggle = (ToggleButton) findViewById(R.id.record_stabilization);
    251         mRecordStabilizationToggle.setOnClickListener(mRecordStabilizationToggleListener);
    252         mOpenOnlyControls.add(mRecordStabilizationToggle);
    253 
    254         mCallbackFormatSpinner = (Spinner) findViewById(R.id.callback_format_spinner);
    255         mCallbackFormatSpinner.setOnItemSelectedListener(mCallbackFormatListener);
    256         mOpenOnlyControls.add(mCallbackFormatSpinner);
    257 
    258         mCallbackToggle = (ToggleButton) findViewById(R.id.enable_callbacks);
    259         mCallbackToggle.setOnClickListener(mCallbackToggleListener);
    260         mOpenOnlyControls.add(mCallbackToggle);
    261 
    262         mLogView = (TextView) findViewById(R.id.log);
    263         mLogView.setMovementMethod(new ScrollingMovementMethod());
    264 
    265         mOpenOnlyControls.addAll(mPreviewOnlyControls);
    266 
    267         mFormatNames = new SparseArray<String>(7);
    268         mFormatNames.append(ImageFormat.JPEG, "JPEG");
    269         mFormatNames.append(ImageFormat.NV16, "NV16");
    270         mFormatNames.append(ImageFormat.NV21, "NV21");
    271         mFormatNames.append(ImageFormat.RGB_565, "RGB_565");
    272         mFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN");
    273         mFormatNames.append(ImageFormat.YUY2, "YUY2");
    274         mFormatNames.append(ImageFormat.YV12, "YV12");
    275 
    276         int numCameras = Camera.getNumberOfCameras();
    277         String[] cameraNames = new String[numCameras + 1];
    278         cameraNames[0] = "None";
    279         for (int i = 0; i < numCameras; i++) {
    280             cameraNames[i + 1] = "Camera " + i;
    281         }
    282 
    283         mCameraSpinner.setAdapter(
    284                 new ArrayAdapter<String>(this,
    285                         R.layout.spinner_item, cameraNames));
    286         if (numCameras > 0) {
    287             mCameraId = 0;
    288             mCameraSpinner.setSelection(mCameraId + 1);
    289         } else {
    290             resetCamera();
    291             mCameraSpinner.setSelection(0);
    292         }
    293 
    294         mRS = RenderScript.create(this);
    295     }
    296 
    297     @Override
    298     public void onResume() {
    299         super.onResume();
    300         log("onResume: Setting up");
    301         mPreviewHolder = null;
    302         setUpCamera();
    303     }
    304 
    305     @Override
    306     public void onPause() {
    307         super.onPause();
    308         if (mState == CAMERA_RECORD) {
    309             stopRecording(false);
    310         }
    311         if (mKeepOpenCheckBox.isChecked()) {
    312             log("onPause: Not releasing camera");
    313 
    314             if (mState == CAMERA_PREVIEW) {
    315                 mCamera.stopPreview();
    316                 mState = CAMERA_OPEN;
    317             }
    318         } else {
    319             log("onPause: Releasing camera");
    320 
    321             if (mCamera != null) {
    322                 mCamera.release();
    323             }
    324             mState = CAMERA_UNINITIALIZED;
    325         }
    326     }
    327 
    328     /** SurfaceHolder.Callback methods */
    329     @Override
    330     public void surfaceChanged(SurfaceHolder holder,
    331             int format,
    332             int width,
    333             int height) {
    334         if (holder == mPreviewView.getHolder()) {
    335             if (mState >= CAMERA_OPEN) {
    336                 final int previewWidth =
    337                         mPreviewSizes.get(mPreviewSize).width;
    338                 final int previewHeight =
    339                         mPreviewSizes.get(mPreviewSize).height;
    340 
    341                 if ( Math.abs((float)previewWidth / previewHeight -
    342                         (float)width/height) > 0.01f) {
    343                     Handler h = new Handler();
    344                     h.post(new Runnable() {
    345                         @Override
    346                         public void run() {
    347                             layoutPreview();
    348                         }
    349                     });
    350                 }
    351             }
    352 
    353             if (mPreviewHolder != null) {
    354                 return;
    355             }
    356             log("Surface holder available: " + width + " x " + height);
    357             mPreviewHolder = holder;
    358             try {
    359                 if (mCamera != null) {
    360                     mCamera.setPreviewDisplay(holder);
    361                 }
    362             } catch (IOException e) {
    363                 logE("Unable to set up preview!");
    364             }
    365         } else if (holder == mCallbackView.getHolder()) {
    366             mCallbackHolder = holder;
    367         }
    368     }
    369 
    370     @Override
    371     public void surfaceCreated(SurfaceHolder holder) {
    372 
    373     }
    374 
    375     @Override
    376     public void surfaceDestroyed(SurfaceHolder holder) {
    377         mPreviewHolder = null;
    378     }
    379 
    380     /** UI controls enable/disable for all open-only controls */
    381     private void enableOpenOnlyControls(boolean enabled) {
    382         for (View v : mOpenOnlyControls) {
    383                 v.setEnabled(enabled);
    384         }
    385     }
    386 
    387     /** UI controls enable/disable for all preview-only controls */
    388     private void enablePreviewOnlyControls(boolean enabled) {
    389         for (View v : mPreviewOnlyControls) {
    390                 v.setEnabled(enabled);
    391         }
    392     }
    393 
    394     /** UI listeners */
    395 
    396     private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
    397                 new AdapterView.OnItemSelectedListener() {
    398         @Override
    399         public void onItemSelected(AdapterView<?> parent,
    400                         View view, int pos, long id) {
    401             int cameraId = pos - 1;
    402             if (mCameraId != cameraId) {
    403                 resetCamera();
    404                 mCameraId = cameraId;
    405                 mPreviewToggle.setChecked(false);
    406                 setUpCamera();
    407             }
    408         }
    409 
    410         @Override
    411         public void onNothingSelected(AdapterView<?> parent) {
    412 
    413         }
    414     };
    415 
    416     private OnClickListener mInfoButtonListener = new OnClickListener() {
    417         @Override
    418         public void onClick(View v) {
    419             if (mCameraId != NO_CAMERA_ID) {
    420                 FragmentManager fm = getFragmentManager();
    421                 InfoDialogFragment infoDialog = new InfoDialogFragment();
    422                 infoDialog.updateInfo(mCameraId, mCamera);
    423                 infoDialog.show(fm, "info_dialog_fragment");
    424             }
    425         }
    426     };
    427 
    428     private AdapterView.OnItemSelectedListener mPreviewSizeListener =
    429         new AdapterView.OnItemSelectedListener() {
    430         @Override
    431         public void onItemSelected(AdapterView<?> parent,
    432                 View view, int pos, long id) {
    433             if (pos == mPreviewSize) return;
    434             if (mState == CAMERA_PREVIEW) {
    435                 log("Stopping preview and callbacks to switch resolutions");
    436                 stopCallbacks();
    437                 mCamera.stopPreview();
    438             }
    439 
    440             mPreviewSize = pos;
    441             int width = mPreviewSizes.get(mPreviewSize).width;
    442             int height = mPreviewSizes.get(mPreviewSize).height;
    443             mParams.setPreviewSize(width, height);
    444 
    445             log("Setting preview size to " + width + "x" + height);
    446 
    447             mCamera.setParameters(mParams);
    448             resizePreview();
    449 
    450             if (mState == CAMERA_PREVIEW) {
    451                 log("Restarting preview");
    452                 mCamera.startPreview();
    453             }
    454         }
    455 
    456         @Override
    457         public void onNothingSelected(AdapterView<?> parent) {
    458 
    459         }
    460     };
    461 
    462     private AdapterView.OnItemSelectedListener mPreviewFrameRateListener =
    463                 new AdapterView.OnItemSelectedListener() {
    464         @Override
    465         public void onItemSelected(AdapterView<?> parent,
    466                         View view, int pos, long id) {
    467             if (pos == mPreviewFrameRate) return;
    468             mPreviewFrameRate = pos;
    469             mParams.setPreviewFrameRate(mPreviewFrameRates.get(mPreviewFrameRate));
    470 
    471             log("Setting preview frame rate to " + ((TextView)view).getText());
    472 
    473             mCamera.setParameters(mParams);
    474         }
    475 
    476         @Override
    477         public void onNothingSelected(AdapterView<?> parent) {
    478 
    479         }
    480     };
    481 
    482     private View.OnClickListener mPreviewToggleListener =
    483             new View.OnClickListener() {
    484         @Override
    485         public void onClick(View v) {
    486             if (mState == CAMERA_TAKE_PICTURE) {
    487                 logE("Can't change preview state while taking picture!");
    488                 return;
    489             }
    490             if (mPreviewToggle.isChecked()) {
    491                 log("Starting preview");
    492                 mCamera.startPreview();
    493                 mState = CAMERA_PREVIEW;
    494                 enablePreviewOnlyControls(true);
    495             } else {
    496                 log("Stopping preview");
    497                 mCamera.stopPreview();
    498                 mState = CAMERA_OPEN;
    499 
    500                 enablePreviewOnlyControls(false);
    501             }
    502         }
    503     };
    504 
    505     private OnItemSelectedListener mAutofocusModeListener =
    506                 new OnItemSelectedListener() {
    507         @Override
    508         public void onItemSelected(AdapterView<?> parent,
    509                         View view, int pos, long id) {
    510             if (pos == mAfMode) return;
    511 
    512             mAfMode = pos;
    513             String focusMode = mAfModes.get(mAfMode);
    514             log("Setting focus mode to " + focusMode);
    515             if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE ||
    516                         focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) {
    517                 mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback);
    518             }
    519             mParams.setFocusMode(focusMode);
    520 
    521             mCamera.setParameters(mParams);
    522         }
    523 
    524         @Override
    525         public void onNothingSelected(AdapterView<?> arg0) {
    526 
    527         }
    528     };
    529 
    530     private OnClickListener mAutofocusButtonListener =
    531             new View.OnClickListener() {
    532         @Override
    533         public void onClick(View v) {
    534             log("Triggering autofocus");
    535             mCamera.autoFocus(mAutofocusCallback);
    536         }
    537     };
    538 
    539     private OnClickListener mCancelAutofocusButtonListener =
    540             new View.OnClickListener() {
    541         @Override
    542         public void onClick(View v) {
    543             log("Cancelling autofocus");
    544             mCamera.cancelAutoFocus();
    545         }
    546     };
    547 
    548     private Camera.AutoFocusCallback mAutofocusCallback =
    549             new Camera.AutoFocusCallback() {
    550         @Override
    551         public void onAutoFocus(boolean success, Camera camera) {
    552             log("Autofocus completed: " + (success ? "success" : "failure") );
    553         }
    554     };
    555 
    556     private Camera.AutoFocusMoveCallback mAutofocusMoveCallback =
    557             new Camera.AutoFocusMoveCallback() {
    558         @Override
    559         public void onAutoFocusMoving(boolean start, Camera camera) {
    560             log("Autofocus movement: " + (start ? "starting" : "stopped") );
    561         }
    562     };
    563 
    564     private OnItemSelectedListener mFlashModeListener =
    565                 new OnItemSelectedListener() {
    566         @Override
    567         public void onItemSelected(AdapterView<?> parent,
    568                         View view, int pos, long id) {
    569             if (pos == mFlashMode) return;
    570 
    571             mFlashMode = pos;
    572             String flashMode = mFlashModes.get(mFlashMode);
    573             log("Setting flash mode to " + flashMode);
    574             mParams.setFlashMode(flashMode);
    575             mCamera.setParameters(mParams);
    576         }
    577 
    578         @Override
    579         public void onNothingSelected(AdapterView<?> arg0) {
    580 
    581         }
    582     };
    583 
    584 
    585     private AdapterView.OnItemSelectedListener mSnapshotSizeListener =
    586             new AdapterView.OnItemSelectedListener() {
    587         @Override
    588         public void onItemSelected(AdapterView<?> parent,
    589                 View view, int pos, long id) {
    590             if (pos == mSnapshotSize) return;
    591 
    592             mSnapshotSize = pos;
    593             int width = mSnapshotSizes.get(mSnapshotSize).width;
    594             int height = mSnapshotSizes.get(mSnapshotSize).height;
    595             log("Setting snapshot size to " + width + " x " + height);
    596 
    597             mParams.setPictureSize(width, height);
    598 
    599             mCamera.setParameters(mParams);
    600         }
    601 
    602         @Override
    603         public void onNothingSelected(AdapterView<?> parent) {
    604 
    605         }
    606     };
    607 
    608     private View.OnClickListener mTakePictureListener =
    609             new View.OnClickListener() {
    610         @Override
    611         public void onClick(View v) {
    612             log("Taking picture");
    613             if (mState == CAMERA_PREVIEW) {
    614                 mState = CAMERA_TAKE_PICTURE;
    615                 enablePreviewOnlyControls(false);
    616                 mPreviewToggle.setChecked(false);
    617 
    618                 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb);
    619             } else {
    620                 logE("Can't take picture while not running preview!");
    621             }
    622         }
    623     };
    624 
    625     private AdapterView.OnItemSelectedListener mCamcorderProfileListener =
    626                 new AdapterView.OnItemSelectedListener() {
    627         @Override
    628         public void onItemSelected(AdapterView<?> parent,
    629                         View view, int pos, long id) {
    630             if (pos != mCamcorderProfile) {
    631                 log("Setting camcorder profile to " + ((TextView)view).getText());
    632                 mCamcorderProfile = pos;
    633             }
    634 
    635             // Additionally change video recording size to match
    636             mVideoRecordSize = 0; // "default", in case it's not found
    637             int width = mCamcorderProfiles.get(pos).videoFrameWidth;
    638             int height = mCamcorderProfiles.get(pos).videoFrameHeight;
    639             for (int i = 0; i < mVideoRecordSizes.size(); i++) {
    640                 Camera.Size s = mVideoRecordSizes.get(i);
    641                 if (width == s.width && height == s.height) {
    642                     mVideoRecordSize = i;
    643                     break;
    644                 }
    645             }
    646             log("Setting video record size to " + mVideoRecordSize);
    647             mVideoRecordSizeSpinner.setSelection(mVideoRecordSize);
    648         }
    649 
    650         @Override
    651         public void onNothingSelected(AdapterView<?> parent) {
    652 
    653         }
    654     };
    655 
    656     private AdapterView.OnItemSelectedListener mVideoRecordSizeListener =
    657                 new AdapterView.OnItemSelectedListener() {
    658         @Override
    659         public void onItemSelected(AdapterView<?> parent,
    660                         View view, int pos, long id) {
    661             if (pos == mVideoRecordSize) return;
    662 
    663             log("Setting video record size to " + ((TextView)view).getText());
    664             mVideoRecordSize = pos;
    665         }
    666 
    667         @Override
    668         public void onNothingSelected(AdapterView<?> parent) {
    669 
    670         }
    671     };
    672 
    673     private AdapterView.OnItemSelectedListener mVideoFrameRateListener =
    674                 new AdapterView.OnItemSelectedListener() {
    675         @Override
    676         public void onItemSelected(AdapterView<?> parent,
    677                         View view, int pos, long id) {
    678             if (pos == mVideoFrameRate) return;
    679 
    680             log("Setting video frame rate to " + ((TextView)view).getText());
    681             mVideoFrameRate = pos;
    682         }
    683 
    684         @Override
    685         public void onNothingSelected(AdapterView<?> parent) {
    686 
    687         }
    688     };
    689 
    690     private View.OnClickListener mRecordToggleListener =
    691             new View.OnClickListener() {
    692         @Override
    693         public void onClick(View v) {
    694             mPreviewToggle.setEnabled(false);
    695             if (mState == CAMERA_PREVIEW) {
    696                 startRecording();
    697             } else if (mState == CAMERA_RECORD) {
    698                 stopRecording(false);
    699             } else {
    700                 logE("Can't toggle recording in current state!");
    701             }
    702             mPreviewToggle.setEnabled(true);
    703         }
    704     };
    705 
    706     private View.OnClickListener mRecordStabilizationToggleListener =
    707             new View.OnClickListener() {
    708         @Override
    709         public void onClick(View v) {
    710             boolean on = ((ToggleButton) v).isChecked();
    711             mParams.setVideoStabilization(on);
    712 
    713             mCamera.setParameters(mParams);
    714         }
    715     };
    716 
    717     private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() {
    718         @Override
    719         public void onShutter() {
    720             log("Shutter callback received");
    721         }
    722     };
    723 
    724     private Camera.PictureCallback mRawCb = new Camera.PictureCallback() {
    725         @Override
    726         public void onPictureTaken(byte[] data, Camera camera) {
    727             log("Raw callback received");
    728         }
    729     };
    730 
    731     private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() {
    732         @Override
    733         public void onPictureTaken(byte[] data, Camera camera) {
    734             log("Postview callback received");
    735         }
    736     };
    737 
    738     private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() {
    739         @Override
    740         public void onPictureTaken(byte[] data, Camera camera) {
    741             log("JPEG picture callback received");
    742             FragmentManager fm = getFragmentManager();
    743             SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment();
    744 
    745             snapshotDialog.updateImage(data);
    746             snapshotDialog.show(fm, "snapshot_dialog_fragment");
    747 
    748             mPreviewToggle.setEnabled(true);
    749 
    750             mState = CAMERA_OPEN;
    751         }
    752     };
    753 
    754     private AdapterView.OnItemSelectedListener mCallbackFormatListener =
    755             new AdapterView.OnItemSelectedListener() {
    756         public void onItemSelected(AdapterView<?> parent,
    757                         View view, int pos, long id) {
    758             mPreviewFormat = pos;
    759 
    760             log("Setting preview format to " +
    761                     mFormatNames.get(mPreviewFormats.get(mPreviewFormat)));
    762 
    763             switch (mState) {
    764             case CAMERA_UNINITIALIZED:
    765                 return;
    766             case CAMERA_OPEN:
    767                 break;
    768             case CAMERA_PREVIEW:
    769                 if (mCallbacksEnabled) {
    770                     log("Stopping preview and callbacks to switch formats");
    771                     stopCallbacks();
    772                     mCamera.stopPreview();
    773                 }
    774                 break;
    775             case CAMERA_RECORD:
    776                 logE("Can't update format while recording active");
    777                 return;
    778             }
    779 
    780             mParams.setPreviewFormat(mPreviewFormats.get(mPreviewFormat));
    781             mCamera.setParameters(mParams);
    782 
    783             if (mCallbacksEnabled) {
    784                 if (mState == CAMERA_PREVIEW) {
    785                     mCamera.startPreview();
    786                 }
    787             }
    788 
    789             configureCallbacks(mCallbackView.getWidth(), mCallbackView.getHeight());
    790         }
    791 
    792         public void onNothingSelected(AdapterView<?> parent) {
    793 
    794         }
    795     };
    796 
    797     private View.OnClickListener mCallbackToggleListener =
    798                 new View.OnClickListener() {
    799         public void onClick(View v) {
    800             if (mCallbacksEnabled) {
    801                 log("Disabling preview callbacks");
    802                 stopCallbacks();
    803                 mCallbacksEnabled = false;
    804                 resizePreview();
    805                 mCallbackView.setVisibility(View.GONE);
    806 
    807             } else {
    808                 log("Enabling preview callbacks");
    809                 mCallbacksEnabled = true;
    810                 resizePreview();
    811                 mCallbackView.setVisibility(View.VISIBLE);
    812             }
    813         }
    814     };
    815 
    816 
    817     // Internal methods
    818 
    819     void setUpCamera() {
    820         if (mCameraId == NO_CAMERA_ID) return;
    821 
    822         log("Setting up camera " + mCameraId);
    823         logIndent(1);
    824 
    825         if (mState < CAMERA_OPEN) {
    826             log("Opening camera " + mCameraId);
    827 
    828             try {
    829                 mCamera = Camera.open(mCameraId);
    830             } catch (RuntimeException e) {
    831                 logE("Exception opening camera: " + e.getMessage());
    832                 resetCamera();
    833                 mCameraSpinner.setSelection(0);
    834                 logIndent(-1);
    835                 return;
    836             }
    837             mState = CAMERA_OPEN;
    838         }
    839 
    840         mCamera.setErrorCallback(this);
    841         mParams = mCamera.getParameters();
    842 
    843         // Set up preview size selection
    844 
    845         log("Configuring camera");
    846         logIndent(1);
    847 
    848         updatePreviewSizes(mParams);
    849         updatePreviewFrameRate(mCameraId);
    850         updatePreviewFormats(mParams);
    851         updateAfModes(mParams);
    852         updateFlashModes(mParams);
    853         updateSnapshotSizes(mParams);
    854         updateCamcorderProfile(mCameraId);
    855         updateVideoRecordSize(mCameraId);
    856         updateVideoFrameRate(mCameraId);
    857 
    858         // Trigger updating video record size to match camcorder profile
    859         mCamcorderProfileSpinner.setSelection(mCamcorderProfile);
    860 
    861         if (mParams.isVideoStabilizationSupported()) {
    862             log("Video stabilization is supported");
    863             mRecordStabilizationToggle.setEnabled(true);
    864         } else {
    865             log("Video stabilization not supported");
    866             mRecordStabilizationToggle.setEnabled(false);
    867         }
    868 
    869         if (mParams.isAutoExposureLockSupported()) {
    870             log("Auto-Exposure locking is supported");
    871             mExposureLockToggle.setEnabled(true);
    872         } else {
    873             log("Auto-Exposure locking is not supported");
    874             mExposureLockToggle.setEnabled(false);
    875         }
    876 
    877         // Update parameters based on above updates
    878         mCamera.setParameters(mParams);
    879 
    880         if (mPreviewHolder != null) {
    881             log("Setting preview display");
    882             try {
    883                 mCamera.setPreviewDisplay(mPreviewHolder);
    884             } catch(IOException e) {
    885                 Log.e(TAG, "Unable to set up preview!");
    886             }
    887         }
    888 
    889         logIndent(-1);
    890 
    891         enableOpenOnlyControls(true);
    892 
    893         resizePreview();
    894         if (mPreviewToggle.isChecked()) {
    895             log("Starting preview" );
    896             mCamera.startPreview();
    897             mState = CAMERA_PREVIEW;
    898         } else {
    899             mState = CAMERA_OPEN;
    900             enablePreviewOnlyControls(false);
    901         }
    902         logIndent(-1);
    903     }
    904 
    905     private void resetCamera() {
    906         if (mState >= CAMERA_OPEN) {
    907             log("Closing old camera");
    908             mCamera.release();
    909         }
    910         mCamera = null;
    911         mCameraId = NO_CAMERA_ID;
    912         mState = CAMERA_UNINITIALIZED;
    913 
    914         enableOpenOnlyControls(false);
    915     }
    916 
    917     private void updateAfModes(Parameters params) {
    918         mAfModes = params.getSupportedFocusModes();
    919 
    920         mAutofocusModeSpinner.setAdapter(
    921                 new ArrayAdapter<String>(this, R.layout.spinner_item,
    922                         mAfModes.toArray(new String[0])));
    923 
    924         mAfMode = 0;
    925 
    926         params.setFocusMode(mAfModes.get(mAfMode));
    927 
    928         log("Setting AF mode to " + mAfModes.get(mAfMode));
    929     }
    930 
    931     private void updateFlashModes(Parameters params) {
    932         mFlashModes = params.getSupportedFlashModes();
    933 
    934         if (mFlashModes != null) {
    935             mFlashModeSpinnerLabel.setVisibility(View.VISIBLE);
    936             mFlashModeSpinner.setVisibility(View.VISIBLE);
    937             mFlashModeSpinner.setAdapter(
    938                     new ArrayAdapter<String>(this, R.layout.spinner_item,
    939                             mFlashModes.toArray(new String[0])));
    940 
    941             mFlashMode = 0;
    942 
    943             params.setFlashMode(mFlashModes.get(mFlashMode));
    944 
    945             log("Setting Flash mode to " + mFlashModes.get(mFlashMode));
    946         } else {
    947             // this camera has no flash
    948             mFlashModeSpinnerLabel.setVisibility(View.GONE);
    949             mFlashModeSpinner.setVisibility(View.GONE);
    950         }
    951     }
    952 
    953     private View.OnClickListener mExposureLockToggleListener =
    954             new View.OnClickListener() {
    955         public void onClick(View v) {
    956             boolean on = ((ToggleButton) v).isChecked();
    957             log("Auto-Exposure was " + mParams.getAutoExposureLock());
    958             mParams.setAutoExposureLock(on);
    959             log("Auto-Exposure is now " + mParams.getAutoExposureLock());
    960         }
    961     };
    962 
    963     private void updatePreviewSizes(Camera.Parameters params) {
    964         mPreviewSizes = params.getSupportedPreviewSizes();
    965 
    966         String[] availableSizeNames = new String[mPreviewSizes.size()];
    967         int i = 0;
    968         for (Camera.Size previewSize: mPreviewSizes) {
    969             availableSizeNames[i++] =
    970                 Integer.toString(previewSize.width) + " x " +
    971                 Integer.toString(previewSize.height);
    972         }
    973         mPreviewSizeSpinner.setAdapter(
    974                 new ArrayAdapter<String>(
    975                         this, R.layout.spinner_item, availableSizeNames));
    976 
    977         mPreviewSize = 0;
    978 
    979         int width = mPreviewSizes.get(mPreviewSize).width;
    980         int height = mPreviewSizes.get(mPreviewSize).height;
    981         params.setPreviewSize(width, height);
    982         log("Setting preview size to " + width + " x " + height);
    983     }
    984 
    985     private void updatePreviewFrameRate(int cameraId) {
    986         List<Integer> frameRates = mParams.getSupportedPreviewFrameRates();
    987         int defaultPreviewFrameRate = mParams.getPreviewFrameRate();
    988 
    989         List<String> frameRateStrings = new ArrayList<String>();
    990         mPreviewFrameRates = new ArrayList<Integer>();
    991 
    992         int currentIndex = 0;
    993         for (Integer frameRate : frameRates) {
    994             mPreviewFrameRates.add(frameRate);
    995             if(frameRate == defaultPreviewFrameRate) {
    996                 frameRateStrings.add(frameRate.toString() + " (Default)");
    997                 mPreviewFrameRate = currentIndex;
    998             } else {
    999                 frameRateStrings.add(frameRate.toString());
   1000             }
   1001             currentIndex++;
   1002         }
   1003 
   1004         String[] nameArray = (String[])frameRateStrings.toArray(new String[0]);
   1005         mPreviewFrameRateSpinner.setAdapter(
   1006                 new ArrayAdapter<String>(
   1007                         this, R.layout.spinner_item, nameArray));
   1008 
   1009         mPreviewFrameRateSpinner.setSelection(mPreviewFrameRate);
   1010         log("Setting preview frame rate to " + nameArray[mPreviewFrameRate]);
   1011     }
   1012 
   1013     private void updatePreviewFormats(Camera.Parameters params) {
   1014         mPreviewFormats = params.getSupportedPreviewFormats();
   1015 
   1016         String[] availableFormatNames = new String[mPreviewFormats.size()];
   1017         int i = 0;
   1018         for (Integer previewFormat: mPreviewFormats) {
   1019             availableFormatNames[i++] = mFormatNames.get(previewFormat);
   1020         }
   1021         mCallbackFormatSpinner.setAdapter(
   1022                 new ArrayAdapter<String>(
   1023                         this, R.layout.spinner_item, availableFormatNames));
   1024 
   1025         mPreviewFormat = 0;
   1026         mCallbacksEnabled = false;
   1027         mCallbackToggle.setChecked(false);
   1028         mCallbackView.setVisibility(View.GONE);
   1029 
   1030         params.setPreviewFormat(mPreviewFormats.get(mPreviewFormat));
   1031         log("Setting preview format to " +
   1032                 mFormatNames.get(mPreviewFormats.get(mPreviewFormat)));
   1033     }
   1034 
   1035     private void updateSnapshotSizes(Camera.Parameters params) {
   1036         String[] availableSizeNames;
   1037         mSnapshotSizes = params.getSupportedPictureSizes();
   1038 
   1039         availableSizeNames = new String[mSnapshotSizes.size()];
   1040         int i = 0;
   1041         for (Camera.Size snapshotSize : mSnapshotSizes) {
   1042             availableSizeNames[i++] =
   1043                 Integer.toString(snapshotSize.width) + " x " +
   1044                 Integer.toString(snapshotSize.height);
   1045         }
   1046         mSnapshotSizeSpinner.setAdapter(
   1047                 new ArrayAdapter<String>(
   1048                         this, R.layout.spinner_item, availableSizeNames));
   1049 
   1050         mSnapshotSize = 0;
   1051 
   1052         int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width;
   1053         int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height;
   1054         params.setPictureSize(snapshotWidth, snapshotHeight);
   1055         log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight);
   1056     }
   1057 
   1058     private void updateCamcorderProfile(int cameraId) {
   1059         // Have to query all of these individually,
   1060         final int PROFILES[] = new int[] {
   1061             CamcorderProfile.QUALITY_1080P,
   1062             CamcorderProfile.QUALITY_480P,
   1063             CamcorderProfile.QUALITY_720P,
   1064             CamcorderProfile.QUALITY_CIF,
   1065             CamcorderProfile.QUALITY_HIGH,
   1066             CamcorderProfile.QUALITY_LOW,
   1067             CamcorderProfile.QUALITY_QCIF,
   1068             CamcorderProfile.QUALITY_QVGA,
   1069             CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
   1070             CamcorderProfile.QUALITY_TIME_LAPSE_480P,
   1071             CamcorderProfile.QUALITY_TIME_LAPSE_720P,
   1072             CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
   1073             CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
   1074             CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
   1075             CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
   1076             CamcorderProfile.QUALITY_TIME_LAPSE_QVGA
   1077         };
   1078 
   1079         final String PROFILE_NAMES[] = new String[] {
   1080             "1080P",
   1081             "480P",
   1082             "720P",
   1083             "CIF",
   1084             "HIGH",
   1085             "LOW",
   1086             "QCIF",
   1087             "QVGA",
   1088             "TIME_LAPSE_1080P",
   1089             "TIME_LAPSE_480P",
   1090             "TIME_LAPSE_720P",
   1091             "TIME_LAPSE_CIF",
   1092             "TIME_LAPSE_HIGH",
   1093             "TIME_LAPSE_LOW",
   1094             "TIME_LAPSE_QCIF",
   1095             "TIME_LAPSE_QVGA"
   1096         };
   1097 
   1098         List<String> availableCamcorderProfileNames = new ArrayList<String>();
   1099         mCamcorderProfiles = new ArrayList<CamcorderProfile>();
   1100 
   1101         for (int i = 0; i < PROFILES.length; i++) {
   1102             if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) {
   1103                 availableCamcorderProfileNames.add(PROFILE_NAMES[i]);
   1104                 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
   1105             }
   1106         }
   1107         String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]);
   1108         mCamcorderProfileSpinner.setAdapter(
   1109                 new ArrayAdapter<String>(
   1110                         this, R.layout.spinner_item, nameArray));
   1111 
   1112         mCamcorderProfile = 0;
   1113         log("Setting camcorder profile to " + nameArray[mCamcorderProfile]);
   1114 
   1115     }
   1116 
   1117     private void updateVideoRecordSize(int cameraId) {
   1118         List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes();
   1119         if (videoSizes == null) { // TODO: surface this to the user
   1120             log("Failed to get video size list, using preview sizes instead");
   1121             videoSizes = mParams.getSupportedPreviewSizes();
   1122         }
   1123 
   1124         List<String> availableVideoRecordSizes = new ArrayList<String>();
   1125         mVideoRecordSizes = new ArrayList<Camera.Size>();
   1126 
   1127         availableVideoRecordSizes.add("Default");
   1128         mVideoRecordSizes.add(mCamera.new Size(0,0));
   1129 
   1130         for (Camera.Size s : videoSizes) {
   1131               availableVideoRecordSizes.add(s.width + "x" + s.height);
   1132               mVideoRecordSizes.add(s);
   1133         }
   1134         String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]);
   1135         mVideoRecordSizeSpinner.setAdapter(
   1136                 new ArrayAdapter<String>(
   1137                         this, R.layout.spinner_item, nameArray));
   1138 
   1139         mVideoRecordSize = 0;
   1140         log("Setting video record profile to " + nameArray[mVideoRecordSize]);
   1141     }
   1142 
   1143     private void updateVideoFrameRate(int cameraId) {
   1144         // Use preview framerates as video framerates
   1145         List<Integer> frameRates = mParams.getSupportedPreviewFrameRates();
   1146 
   1147         List<String> frameRateStrings = new ArrayList<String>();
   1148         mVideoFrameRates = new ArrayList<Integer>();
   1149 
   1150         frameRateStrings.add("Default");
   1151         mVideoFrameRates.add(0);
   1152 
   1153         for (Integer frameRate : frameRates) {
   1154             frameRateStrings.add(frameRate.toString());
   1155             mVideoFrameRates.add(frameRate);
   1156         }
   1157         String[] nameArray = (String[])frameRateStrings.toArray(new String[0]);
   1158         mVideoFrameRateSpinner.setAdapter(
   1159                 new ArrayAdapter<String>(
   1160                         this, R.layout.spinner_item, nameArray));
   1161 
   1162         mVideoFrameRate = 0;
   1163         log("Setting recording frame rate to " + nameArray[mVideoFrameRate]);
   1164     }
   1165 
   1166     void resizePreview() {
   1167         // Reset preview layout parameters, to trigger layout pass
   1168         // This will eventually call layoutPreview below
   1169         Resources res = getResources();
   1170         mPreviewView.setLayoutParams(
   1171                 new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0,
   1172                         mCallbacksEnabled ?
   1173                         res.getInteger(R.integer.preview_with_callback_weight):
   1174                         res.getInteger(R.integer.preview_only_weight) ));
   1175     }
   1176 
   1177     void layoutPreview() {
   1178         int width = mPreviewSizes.get(mPreviewSize).width;
   1179         int height = mPreviewSizes.get(mPreviewSize).height;
   1180         float previewAspect = ((float) width) / height;
   1181 
   1182         int viewHeight = mPreviewView.getHeight();
   1183         int viewWidth = mPreviewView.getWidth();
   1184         float viewAspect = ((float) viewWidth) / viewHeight;
   1185         if ( previewAspect > viewAspect) {
   1186             viewHeight = (int) (viewWidth / previewAspect);
   1187         } else {
   1188             viewWidth = (int) (viewHeight * previewAspect);
   1189         }
   1190         mPreviewView.setLayoutParams(
   1191                 new LayoutParams(viewWidth, viewHeight));
   1192 
   1193         if (mCallbacksEnabled) {
   1194             int callbackHeight = mCallbackView.getHeight();
   1195             int callbackWidth = mCallbackView.getWidth();
   1196             float callbackAspect = ((float) callbackWidth) / callbackHeight;
   1197             if ( previewAspect > callbackAspect) {
   1198                 callbackHeight = (int) (callbackWidth / previewAspect);
   1199             } else {
   1200                 callbackWidth = (int) (callbackHeight * previewAspect);
   1201             }
   1202             mCallbackView.setLayoutParams(
   1203                     new LayoutParams(callbackWidth, callbackHeight));
   1204             configureCallbacks(callbackWidth, callbackHeight);
   1205         }
   1206     }
   1207 
   1208 
   1209     private void configureCallbacks(int callbackWidth, int callbackHeight) {
   1210         if (mState >= CAMERA_OPEN && mCallbacksEnabled) {
   1211             mCamera.setPreviewCallbackWithBuffer(null);
   1212             int width = mPreviewSizes.get(mPreviewSize).width;
   1213             int height = mPreviewSizes.get(mPreviewSize).height;
   1214             int format = mPreviewFormats.get(mPreviewFormat);
   1215 
   1216             mCallbackProcessor = new CallbackProcessor(width, height, format,
   1217                     getResources(), mCallbackView,
   1218                     callbackWidth, callbackHeight, mRS);
   1219 
   1220             int size = getCallbackBufferSize(width, height, format);
   1221             log("Configuring callbacks:" + width + " x " + height +
   1222                     " , format " + format);
   1223             for (int i = 0; i < CALLBACK_BUFFER_COUNT; i++) {
   1224                 mCamera.addCallbackBuffer(new byte[size]);
   1225             }
   1226             mCamera.setPreviewCallbackWithBuffer(this);
   1227         }
   1228         mLastCallbackTimestamp = -1;
   1229         mCallbackFrameCount = 0;
   1230         mCallbackAvgFrameDuration = 30;
   1231     }
   1232 
   1233     private void stopCallbacks() {
   1234         if (mState >= CAMERA_OPEN) {
   1235             mCamera.setPreviewCallbackWithBuffer(null);
   1236             if (mCallbackProcessor != null) {
   1237                 if (!mCallbackProcessor.stop()) {
   1238                     logE("Can't stop preview callback processing!");
   1239                 }
   1240             }
   1241         }
   1242     }
   1243 
   1244     @Override
   1245     public void onPreviewFrame(byte[] data, Camera camera) {
   1246         long timestamp = SystemClock.elapsedRealtime();
   1247         if (mLastCallbackTimestamp != -1) {
   1248             long frameDuration = timestamp - mLastCallbackTimestamp;
   1249             mCallbackAvgFrameDuration =
   1250                     mCallbackAvgFrameDuration * MEAN_FPS_HISTORY_COEFF +
   1251                     frameDuration * MEAN_FPS_MEASUREMENT_COEFF;
   1252         }
   1253         mLastCallbackTimestamp = timestamp;
   1254         if (mState < CAMERA_PREVIEW || !mCallbacksEnabled) {
   1255             mCamera.addCallbackBuffer(data);
   1256             return;
   1257         }
   1258         mCallbackFrameCount++;
   1259         if (mCallbackFrameCount % FPS_REPORTING_PERIOD == 0) {
   1260             log("Got " + FPS_REPORTING_PERIOD + " callback frames, fps "
   1261                     + 1e3/mCallbackAvgFrameDuration);
   1262         }
   1263         mCallbackProcessor.displayCallback(data);
   1264 
   1265         mCamera.addCallbackBuffer(data);
   1266     }
   1267 
   1268     @Override
   1269     public void onError(int error, Camera camera) {
   1270         String errorName;
   1271         switch (error) {
   1272         case Camera.CAMERA_ERROR_SERVER_DIED:
   1273             errorName = "SERVER_DIED";
   1274             break;
   1275         case Camera.CAMERA_ERROR_UNKNOWN:
   1276             errorName = "UNKNOWN";
   1277             break;
   1278         default:
   1279             errorName = "?";
   1280             break;
   1281         }
   1282         logE("Camera error received: " + errorName + " (" + error + ")" );
   1283         logE("Shutting down camera");
   1284         resetCamera();
   1285         mCameraSpinner.setSelection(0);
   1286     }
   1287 
   1288     static final int MEDIA_TYPE_IMAGE = 0;
   1289     static final int MEDIA_TYPE_VIDEO = 1;
   1290     @SuppressLint("SimpleDateFormat")
   1291     File getOutputMediaFile(int type){
   1292         // To be safe, you should check that the SDCard is mounted
   1293         // using Environment.getExternalStorageState() before doing this.
   1294 
   1295         String state = Environment.getExternalStorageState();
   1296         if (!Environment.MEDIA_MOUNTED.equals(state)) {
   1297                 return null;
   1298         }
   1299 
   1300         File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
   1301                   Environment.DIRECTORY_DCIM), "TestingCamera");
   1302         // This location works best if you want the created images to be shared
   1303         // between applications and persist after your app has been uninstalled.
   1304 
   1305         // Create the storage directory if it does not exist
   1306         if (! mediaStorageDir.exists()){
   1307             if (! mediaStorageDir.mkdirs()){
   1308                 logE("Failed to create directory for pictures/video");
   1309                 return null;
   1310             }
   1311         }
   1312 
   1313         // Create a media file name
   1314         String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
   1315         File mediaFile;
   1316         if (type == MEDIA_TYPE_IMAGE){
   1317             mediaFile = new File(mediaStorageDir.getPath() + File.separator +
   1318             "IMG_"+ timeStamp + ".jpg");
   1319         } else if(type == MEDIA_TYPE_VIDEO) {
   1320             mediaFile = new File(mediaStorageDir.getPath() + File.separator +
   1321             "VID_"+ timeStamp + ".mp4");
   1322         } else {
   1323             return null;
   1324         }
   1325 
   1326         return mediaFile;
   1327     }
   1328 
   1329     void notifyMediaScannerOfFile(File newFile,
   1330                 final MediaScannerConnection.OnScanCompletedListener listener) {
   1331         final Handler h = new Handler();
   1332         MediaScannerConnection.scanFile(this,
   1333                 new String[] { newFile.toString() },
   1334                 null,
   1335                 new MediaScannerConnection.OnScanCompletedListener() {
   1336                     @Override
   1337                     public void onScanCompleted(final String path, final Uri uri) {
   1338                         h.post(new Runnable() {
   1339                             @Override
   1340                             public void run() {
   1341                                 log("MediaScanner notified: " +
   1342                                         path + " -> " + uri);
   1343                                 if (listener != null)
   1344                                     listener.onScanCompleted(path, uri);
   1345                             }
   1346                         });
   1347                     }
   1348                 });
   1349     }
   1350 
   1351     private void deleteFile(File badFile) {
   1352         if (badFile.exists()) {
   1353             boolean success = badFile.delete();
   1354             if (success) log("Deleted file " + badFile.toString());
   1355             else log("Unable to delete file " + badFile.toString());
   1356         }
   1357     }
   1358 
   1359     private void startRecording() {
   1360         log("Starting recording");
   1361         logIndent(1);
   1362         log("Configuring MediaRecoder");
   1363 
   1364         mRecordHandoffCheckBox.setEnabled(false);
   1365         if (mRecordHandoffCheckBox.isChecked()) {
   1366             mCamera.release();
   1367         } else {
   1368             mCamera.unlock();
   1369         }
   1370 
   1371         if (mRecorder != null) {
   1372             mRecorder.release();
   1373         }
   1374 
   1375         mRecorder = new MediaRecorder();
   1376         mRecorder.setOnErrorListener(mRecordingErrorListener);
   1377         mRecorder.setOnInfoListener(mRecordingInfoListener);
   1378         if (!mRecordHandoffCheckBox.isChecked()) {
   1379             mRecorder.setCamera(mCamera);
   1380         }
   1381         mRecorder.setPreviewDisplay(mPreviewHolder.getSurface());
   1382 
   1383         mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
   1384         mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
   1385         mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
   1386         Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize);
   1387         if (videoRecordSize.width > 0 && videoRecordSize.height > 0) {
   1388             mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height);
   1389         }
   1390         if (mVideoFrameRates.get(mVideoFrameRate) > 0) {
   1391             mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate));
   1392         }
   1393         File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
   1394         log("File name:" + outputFile.toString());
   1395         mRecorder.setOutputFile(outputFile.toString());
   1396 
   1397         boolean ready = false;
   1398         log("Preparing MediaRecorder");
   1399         try {
   1400             mRecorder.prepare();
   1401             ready = true;
   1402         } catch (Exception e) {
   1403             StringWriter writer = new StringWriter();
   1404             e.printStackTrace(new PrintWriter(writer));
   1405             logE("Exception preparing MediaRecorder:\n" + writer.toString());
   1406         }
   1407 
   1408         if (ready) {
   1409             try {
   1410                 log("Starting MediaRecorder");
   1411                 mRecorder.start();
   1412                 mState = CAMERA_RECORD;
   1413                 log("Recording active");
   1414                 mRecordingFile = outputFile;
   1415             } catch (Exception e) {
   1416                 StringWriter writer = new StringWriter();
   1417                 e.printStackTrace(new PrintWriter(writer));
   1418                 logE("Exception starting MediaRecorder:\n" + writer.toString());
   1419                 ready = false;
   1420             }
   1421         }
   1422 
   1423         if (!ready) {
   1424             mRecordToggle.setChecked(false);
   1425             mRecordHandoffCheckBox.setEnabled(true);
   1426 
   1427             if (mRecordHandoffCheckBox.isChecked()) {
   1428                 mState = CAMERA_UNINITIALIZED;
   1429                 setUpCamera();
   1430             }
   1431         }
   1432         logIndent(-1);
   1433     }
   1434 
   1435     private MediaRecorder.OnErrorListener mRecordingErrorListener =
   1436             new MediaRecorder.OnErrorListener() {
   1437         @Override
   1438         public void onError(MediaRecorder mr, int what, int extra) {
   1439             logE("MediaRecorder reports error: " + what + ", extra "
   1440                     + extra);
   1441             if (mState == CAMERA_RECORD) {
   1442                 stopRecording(true);
   1443             }
   1444         }
   1445     };
   1446 
   1447     private MediaRecorder.OnInfoListener mRecordingInfoListener =
   1448             new MediaRecorder.OnInfoListener() {
   1449         @Override
   1450         public void onInfo(MediaRecorder mr, int what, int extra) {
   1451             log("MediaRecorder reports info: " + what + ", extra "
   1452                     + extra);
   1453         }
   1454     };
   1455 
   1456     private void stopRecording(boolean error) {
   1457         log("Stopping recording");
   1458         mRecordHandoffCheckBox.setEnabled(true);
   1459         mRecordToggle.setChecked(false);
   1460         if (mRecorder != null) {
   1461             try {
   1462                 mRecorder.stop();
   1463             } catch (RuntimeException e) {
   1464                 // this can happen if there were no frames received by recorder
   1465                 logE("Could not create output file");
   1466                 error = true;
   1467             }
   1468 
   1469             if (mRecordHandoffCheckBox.isChecked()) {
   1470                 mState = CAMERA_UNINITIALIZED;
   1471                 setUpCamera();
   1472             } else {
   1473                 mCamera.lock();
   1474                 mState = CAMERA_PREVIEW;
   1475             }
   1476 
   1477             if (!error) {
   1478                 notifyMediaScannerOfFile(mRecordingFile, null);
   1479             } else {
   1480                 deleteFile(mRecordingFile);
   1481             }
   1482             mRecordingFile = null;
   1483         } else {
   1484             logE("Recorder is unexpectedly null!");
   1485         }
   1486     }
   1487 
   1488     static int getCallbackBufferSize(int width, int height, int format) {
   1489         int size = -1;
   1490         switch (format) {
   1491         case ImageFormat.NV21:
   1492             size = width * height * 3 / 2;
   1493             break;
   1494         case ImageFormat.YV12:
   1495             int y_stride = (int) (Math.ceil( width / 16.) * 16);
   1496             int y_size = y_stride * height;
   1497             int c_stride = (int) (Math.ceil(y_stride / 32.) * 16);
   1498             int c_size = c_stride * height/2;
   1499             size = y_size + c_size * 2;
   1500             break;
   1501         case ImageFormat.NV16:
   1502         case ImageFormat.RGB_565:
   1503         case ImageFormat.YUY2:
   1504             size = 2 * width * height;
   1505             break;
   1506         case ImageFormat.JPEG:
   1507             Log.e(TAG, "JPEG callback buffers not supported!");
   1508             size = 0;
   1509             break;
   1510         case ImageFormat.UNKNOWN:
   1511             Log.e(TAG, "Unknown-format callback buffers not supported!");
   1512             size = 0;
   1513             break;
   1514         }
   1515         return size;
   1516     }
   1517 
   1518     private int mLogIndentLevel = 0;
   1519     private String mLogIndent = "\t";
   1520     /** Increment or decrement log indentation level */
   1521     synchronized void logIndent(int delta) {
   1522         mLogIndentLevel += delta;
   1523         if (mLogIndentLevel < 0) mLogIndentLevel = 0;
   1524         char[] mLogIndentArray = new char[mLogIndentLevel + 1];
   1525         for (int i = -1; i < mLogIndentLevel; i++) {
   1526             mLogIndentArray[i + 1] = '\t';
   1527         }
   1528         mLogIndent = new String(mLogIndentArray);
   1529     }
   1530 
   1531     @SuppressLint("SimpleDateFormat")
   1532     SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS");
   1533     /** Log both to log text view and to device logcat */
   1534     void log(String logLine) {
   1535         Log.d(TAG, logLine);
   1536         logAndScrollToBottom(logLine, mLogIndent);
   1537     }
   1538 
   1539     void logE(String logLine) {
   1540         Log.e(TAG, logLine);
   1541         logAndScrollToBottom(logLine, mLogIndent + "!!! ");
   1542     }
   1543 
   1544     synchronized private void logAndScrollToBottom(String logLine, String logIndent) {
   1545         StringBuffer logEntry = new StringBuffer(32);
   1546         logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent);
   1547         logEntry.append(logLine);
   1548         mLogView.append(logEntry);
   1549         final Layout layout = mLogView.getLayout();
   1550         if (layout != null){
   1551             int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1)
   1552                 - mLogView.getScrollY() - mLogView.getHeight();
   1553             if(scrollDelta > 0) {
   1554                 mLogView.scrollBy(0, scrollDelta);
   1555             }
   1556         }
   1557     }
   1558 }
   1559