TestingCamera.java revision 1aa04b539399a69710d6b98877655e3bd87920ae
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
17package com.android.testingcamera;
18
19import android.annotation.SuppressLint;
20import android.app.Activity;
21import android.app.FragmentManager;
22import android.content.res.Resources;
23import android.graphics.ImageFormat;
24import android.hardware.Camera;
25import android.hardware.Camera.Parameters;
26import android.hardware.Camera.ErrorCallback;
27import android.media.CamcorderProfile;
28import android.media.MediaRecorder;
29import android.media.MediaScannerConnection;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Environment;
33import android.os.Handler;
34import android.os.SystemClock;
35import android.view.View;
36import android.view.SurfaceHolder;
37import android.view.SurfaceView;
38import android.view.View.OnClickListener;
39import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemSelectedListener;
41import android.widget.ArrayAdapter;
42import android.widget.Button;
43import android.widget.CheckBox;
44import android.widget.LinearLayout;
45import android.widget.LinearLayout.LayoutParams;
46import android.widget.Spinner;
47import android.widget.TextView;
48import android.widget.ToggleButton;
49import android.renderscript.RenderScript;
50import android.text.Layout;
51import android.text.method.ScrollingMovementMethod;
52import android.util.Log;
53import android.util.SparseArray;
54
55import java.io.File;
56import java.io.IOException;
57import java.io.PrintWriter;
58import java.io.StringWriter;
59import java.text.SimpleDateFormat;
60import java.util.ArrayList;
61import java.util.Date;
62import java.util.HashSet;
63import java.util.List;
64import 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 */
72public 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