TestingCamera.java revision 753ac7952bbb16004e5dd149dfdbfb70042827ff
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
782            if (mCallbacksEnabled) {
783                mCamera.setParameters(mParams);
784
785                if (mState == CAMERA_PREVIEW) {
786                    mCamera.startPreview();
787                }
788            }
789
790            configureCallbacks(mCallbackView.getWidth(), mCallbackView.getHeight());
791        }
792
793        public void onNothingSelected(AdapterView<?> parent) {
794
795        }
796    };
797
798    private View.OnClickListener mCallbackToggleListener =
799                new View.OnClickListener() {
800        public void onClick(View v) {
801            if (mCallbacksEnabled) {
802                log("Disabling preview callbacks");
803                stopCallbacks();
804                mCallbacksEnabled = false;
805                resizePreview();
806                mCallbackView.setVisibility(View.GONE);
807
808            } else {
809                log("Enabling preview callbacks");
810                mCallbacksEnabled = true;
811                resizePreview();
812                mCallbackView.setVisibility(View.VISIBLE);
813            }
814        }
815    };
816
817
818    // Internal methods
819
820    void setUpCamera() {
821        if (mCameraId == NO_CAMERA_ID) return;
822
823        log("Setting up camera " + mCameraId);
824        logIndent(1);
825
826        if (mState < CAMERA_OPEN) {
827            log("Opening camera " + mCameraId);
828
829            try {
830                mCamera = Camera.open(mCameraId);
831            } catch (RuntimeException e) {
832                logE("Exception opening camera: " + e.getMessage());
833                resetCamera();
834                mCameraSpinner.setSelection(0);
835                logIndent(-1);
836                return;
837            }
838            mState = CAMERA_OPEN;
839        }
840
841        mCamera.setErrorCallback(this);
842        mParams = mCamera.getParameters();
843
844        // Set up preview size selection
845
846        log("Configuring camera");
847        logIndent(1);
848
849        updatePreviewSizes(mParams);
850        updatePreviewFrameRate(mCameraId);
851        updatePreviewFormats(mParams);
852        updateAfModes(mParams);
853        updateFlashModes(mParams);
854        updateSnapshotSizes(mParams);
855        updateCamcorderProfile(mCameraId);
856        updateVideoRecordSize(mCameraId);
857        updateVideoFrameRate(mCameraId);
858
859        // Trigger updating video record size to match camcorder profile
860        mCamcorderProfileSpinner.setSelection(mCamcorderProfile);
861
862        if (mParams.isVideoStabilizationSupported()) {
863            log("Video stabilization is supported");
864            mRecordStabilizationToggle.setEnabled(true);
865        } else {
866            log("Video stabilization not supported");
867            mRecordStabilizationToggle.setEnabled(false);
868        }
869
870        if (mParams.isAutoExposureLockSupported()) {
871            log("Auto-Exposure locking is supported");
872            mExposureLockToggle.setEnabled(true);
873        } else {
874            log("Auto-Exposure locking is not supported");
875            mExposureLockToggle.setEnabled(false);
876        }
877
878        // Update parameters based on above updates
879        mCamera.setParameters(mParams);
880
881        if (mPreviewHolder != null) {
882            log("Setting preview display");
883            try {
884                mCamera.setPreviewDisplay(mPreviewHolder);
885            } catch(IOException e) {
886                Log.e(TAG, "Unable to set up preview!");
887            }
888        }
889
890        logIndent(-1);
891
892        enableOpenOnlyControls(true);
893
894        resizePreview();
895        if (mPreviewToggle.isChecked()) {
896            log("Starting preview" );
897            mCamera.startPreview();
898            mState = CAMERA_PREVIEW;
899        } else {
900            mState = CAMERA_OPEN;
901            enablePreviewOnlyControls(false);
902        }
903        logIndent(-1);
904    }
905
906    private void resetCamera() {
907        if (mState >= CAMERA_OPEN) {
908            log("Closing old camera");
909            mCamera.release();
910        }
911        mCamera = null;
912        mCameraId = NO_CAMERA_ID;
913        mState = CAMERA_UNINITIALIZED;
914
915        enableOpenOnlyControls(false);
916    }
917
918    private void updateAfModes(Parameters params) {
919        mAfModes = params.getSupportedFocusModes();
920
921        mAutofocusModeSpinner.setAdapter(
922                new ArrayAdapter<String>(this, R.layout.spinner_item,
923                        mAfModes.toArray(new String[0])));
924
925        mAfMode = 0;
926
927        params.setFocusMode(mAfModes.get(mAfMode));
928
929        log("Setting AF mode to " + mAfModes.get(mAfMode));
930    }
931
932    private void updateFlashModes(Parameters params) {
933        mFlashModes = params.getSupportedFlashModes();
934
935        if (mFlashModes != null) {
936            mFlashModeSpinnerLabel.setVisibility(View.VISIBLE);
937            mFlashModeSpinner.setVisibility(View.VISIBLE);
938            mFlashModeSpinner.setAdapter(
939                    new ArrayAdapter<String>(this, R.layout.spinner_item,
940                            mFlashModes.toArray(new String[0])));
941
942            mFlashMode = 0;
943
944            params.setFlashMode(mFlashModes.get(mFlashMode));
945
946            log("Setting Flash mode to " + mFlashModes.get(mFlashMode));
947        } else {
948            // this camera has no flash
949            mFlashModeSpinnerLabel.setVisibility(View.GONE);
950            mFlashModeSpinner.setVisibility(View.GONE);
951        }
952    }
953
954    private View.OnClickListener mExposureLockToggleListener =
955            new View.OnClickListener() {
956        public void onClick(View v) {
957            boolean on = ((ToggleButton) v).isChecked();
958            log("Auto-Exposure was " + mParams.getAutoExposureLock());
959            mParams.setAutoExposureLock(on);
960            log("Auto-Exposure is now " + mParams.getAutoExposureLock());
961        }
962    };
963
964    private void updatePreviewSizes(Camera.Parameters params) {
965        mPreviewSizes = params.getSupportedPreviewSizes();
966
967        String[] availableSizeNames = new String[mPreviewSizes.size()];
968        int i = 0;
969        for (Camera.Size previewSize: mPreviewSizes) {
970            availableSizeNames[i++] =
971                Integer.toString(previewSize.width) + " x " +
972                Integer.toString(previewSize.height);
973        }
974        mPreviewSizeSpinner.setAdapter(
975                new ArrayAdapter<String>(
976                        this, R.layout.spinner_item, availableSizeNames));
977
978        mPreviewSize = 0;
979
980        int width = mPreviewSizes.get(mPreviewSize).width;
981        int height = mPreviewSizes.get(mPreviewSize).height;
982        params.setPreviewSize(width, height);
983        log("Setting preview size to " + width + " x " + height);
984    }
985
986    private void updatePreviewFrameRate(int cameraId) {
987        List<Integer> frameRates = mParams.getSupportedPreviewFrameRates();
988        int defaultPreviewFrameRate = mParams.getPreviewFrameRate();
989
990        List<String> frameRateStrings = new ArrayList<String>();
991        mPreviewFrameRates = new ArrayList<Integer>();
992
993        int currentIndex = 0;
994        for (Integer frameRate : frameRates) {
995            mPreviewFrameRates.add(frameRate);
996            if(frameRate == defaultPreviewFrameRate) {
997                frameRateStrings.add(frameRate.toString() + " (Default)");
998                mPreviewFrameRate = currentIndex;
999            } else {
1000                frameRateStrings.add(frameRate.toString());
1001            }
1002            currentIndex++;
1003        }
1004
1005        String[] nameArray = (String[])frameRateStrings.toArray(new String[0]);
1006        mPreviewFrameRateSpinner.setAdapter(
1007                new ArrayAdapter<String>(
1008                        this, R.layout.spinner_item, nameArray));
1009
1010        mPreviewFrameRateSpinner.setSelection(mPreviewFrameRate);
1011        log("Setting preview frame rate to " + nameArray[mPreviewFrameRate]);
1012    }
1013
1014    private void updatePreviewFormats(Camera.Parameters params) {
1015        mPreviewFormats = params.getSupportedPreviewFormats();
1016
1017        String[] availableFormatNames = new String[mPreviewFormats.size()];
1018        int i = 0;
1019        for (Integer previewFormat: mPreviewFormats) {
1020            availableFormatNames[i++] = mFormatNames.get(previewFormat);
1021        }
1022        mCallbackFormatSpinner.setAdapter(
1023                new ArrayAdapter<String>(
1024                        this, R.layout.spinner_item, availableFormatNames));
1025
1026        mPreviewFormat = 0;
1027        mCallbacksEnabled = false;
1028        mCallbackToggle.setChecked(false);
1029        mCallbackView.setVisibility(View.GONE);
1030
1031        params.setPreviewFormat(mPreviewFormats.get(mPreviewFormat));
1032        log("Setting preview format to " +
1033                mFormatNames.get(mPreviewFormats.get(mPreviewFormat)));
1034    }
1035
1036    private void updateSnapshotSizes(Camera.Parameters params) {
1037        String[] availableSizeNames;
1038        mSnapshotSizes = params.getSupportedPictureSizes();
1039
1040        availableSizeNames = new String[mSnapshotSizes.size()];
1041        int i = 0;
1042        for (Camera.Size snapshotSize : mSnapshotSizes) {
1043            availableSizeNames[i++] =
1044                Integer.toString(snapshotSize.width) + " x " +
1045                Integer.toString(snapshotSize.height);
1046        }
1047        mSnapshotSizeSpinner.setAdapter(
1048                new ArrayAdapter<String>(
1049                        this, R.layout.spinner_item, availableSizeNames));
1050
1051        mSnapshotSize = 0;
1052
1053        int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width;
1054        int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height;
1055        params.setPictureSize(snapshotWidth, snapshotHeight);
1056        log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight);
1057    }
1058
1059    private void updateCamcorderProfile(int cameraId) {
1060        // Have to query all of these individually,
1061        final int PROFILES[] = new int[] {
1062            CamcorderProfile.QUALITY_1080P,
1063            CamcorderProfile.QUALITY_480P,
1064            CamcorderProfile.QUALITY_720P,
1065            CamcorderProfile.QUALITY_CIF,
1066            CamcorderProfile.QUALITY_HIGH,
1067            CamcorderProfile.QUALITY_LOW,
1068            CamcorderProfile.QUALITY_QCIF,
1069            CamcorderProfile.QUALITY_QVGA,
1070            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
1071            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
1072            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
1073            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
1074            CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
1075            CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
1076            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
1077            CamcorderProfile.QUALITY_TIME_LAPSE_QVGA
1078        };
1079
1080        final String PROFILE_NAMES[] = new String[] {
1081            "1080P",
1082            "480P",
1083            "720P",
1084            "CIF",
1085            "HIGH",
1086            "LOW",
1087            "QCIF",
1088            "QVGA",
1089            "TIME_LAPSE_1080P",
1090            "TIME_LAPSE_480P",
1091            "TIME_LAPSE_720P",
1092            "TIME_LAPSE_CIF",
1093            "TIME_LAPSE_HIGH",
1094            "TIME_LAPSE_LOW",
1095            "TIME_LAPSE_QCIF",
1096            "TIME_LAPSE_QVGA"
1097        };
1098
1099        List<String> availableCamcorderProfileNames = new ArrayList<String>();
1100        mCamcorderProfiles = new ArrayList<CamcorderProfile>();
1101
1102        for (int i = 0; i < PROFILES.length; i++) {
1103            if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) {
1104                availableCamcorderProfileNames.add(PROFILE_NAMES[i]);
1105                mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
1106            }
1107        }
1108        String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]);
1109        mCamcorderProfileSpinner.setAdapter(
1110                new ArrayAdapter<String>(
1111                        this, R.layout.spinner_item, nameArray));
1112
1113        mCamcorderProfile = 0;
1114        log("Setting camcorder profile to " + nameArray[mCamcorderProfile]);
1115
1116    }
1117
1118    private void updateVideoRecordSize(int cameraId) {
1119        List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes();
1120        if (videoSizes == null) { // TODO: surface this to the user
1121            log("Failed to get video size list, using preview sizes instead");
1122            videoSizes = mParams.getSupportedPreviewSizes();
1123        }
1124
1125        List<String> availableVideoRecordSizes = new ArrayList<String>();
1126        mVideoRecordSizes = new ArrayList<Camera.Size>();
1127
1128        availableVideoRecordSizes.add("Default");
1129        mVideoRecordSizes.add(mCamera.new Size(0,0));
1130
1131        for (Camera.Size s : videoSizes) {
1132              availableVideoRecordSizes.add(s.width + "x" + s.height);
1133              mVideoRecordSizes.add(s);
1134        }
1135        String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]);
1136        mVideoRecordSizeSpinner.setAdapter(
1137                new ArrayAdapter<String>(
1138                        this, R.layout.spinner_item, nameArray));
1139
1140        mVideoRecordSize = 0;
1141        log("Setting video record profile to " + nameArray[mVideoRecordSize]);
1142    }
1143
1144    private void updateVideoFrameRate(int cameraId) {
1145        // Use preview framerates as video framerates
1146        List<Integer> frameRates = mParams.getSupportedPreviewFrameRates();
1147
1148        List<String> frameRateStrings = new ArrayList<String>();
1149        mVideoFrameRates = new ArrayList<Integer>();
1150
1151        frameRateStrings.add("Default");
1152        mVideoFrameRates.add(0);
1153
1154        for (Integer frameRate : frameRates) {
1155            frameRateStrings.add(frameRate.toString());
1156            mVideoFrameRates.add(frameRate);
1157        }
1158        String[] nameArray = (String[])frameRateStrings.toArray(new String[0]);
1159        mVideoFrameRateSpinner.setAdapter(
1160                new ArrayAdapter<String>(
1161                        this, R.layout.spinner_item, nameArray));
1162
1163        mVideoFrameRate = 0;
1164        log("Setting recording frame rate to " + nameArray[mVideoFrameRate]);
1165    }
1166
1167    void resizePreview() {
1168        // Reset preview layout parameters, to trigger layout pass
1169        // This will eventually call layoutPreview below
1170        Resources res = getResources();
1171        mPreviewView.setLayoutParams(
1172                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0,
1173                        mCallbacksEnabled ?
1174                        res.getInteger(R.integer.preview_with_callback_weight):
1175                        res.getInteger(R.integer.preview_only_weight) ));
1176    }
1177
1178    void layoutPreview() {
1179        int width = mPreviewSizes.get(mPreviewSize).width;
1180        int height = mPreviewSizes.get(mPreviewSize).height;
1181        float previewAspect = ((float) width) / height;
1182
1183        int viewHeight = mPreviewView.getHeight();
1184        int viewWidth = mPreviewView.getWidth();
1185        float viewAspect = ((float) viewWidth) / viewHeight;
1186        if ( previewAspect > viewAspect) {
1187            viewHeight = (int) (viewWidth / previewAspect);
1188        } else {
1189            viewWidth = (int) (viewHeight * previewAspect);
1190        }
1191        mPreviewView.setLayoutParams(
1192                new LayoutParams(viewWidth, viewHeight));
1193
1194        if (mCallbacksEnabled) {
1195            int callbackHeight = mCallbackView.getHeight();
1196            int callbackWidth = mCallbackView.getWidth();
1197            float callbackAspect = ((float) callbackWidth) / callbackHeight;
1198            if ( previewAspect > callbackAspect) {
1199                callbackHeight = (int) (callbackWidth / previewAspect);
1200            } else {
1201                callbackWidth = (int) (callbackHeight * previewAspect);
1202            }
1203            mCallbackView.setLayoutParams(
1204                    new LayoutParams(callbackWidth, callbackHeight));
1205            configureCallbacks(callbackWidth, callbackHeight);
1206        }
1207    }
1208
1209
1210    private void configureCallbacks(int callbackWidth, int callbackHeight) {
1211        if (mState >= CAMERA_OPEN && mCallbacksEnabled) {
1212            mCamera.setPreviewCallbackWithBuffer(null);
1213            int width = mPreviewSizes.get(mPreviewSize).width;
1214            int height = mPreviewSizes.get(mPreviewSize).height;
1215            int format = mPreviewFormats.get(mPreviewFormat);
1216
1217            mCallbackProcessor = new CallbackProcessor(width, height, format,
1218                    getResources(), mCallbackView,
1219                    callbackWidth, callbackHeight, mRS);
1220
1221            int size = getCallbackBufferSize(width, height, format);
1222            log("Configuring callbacks:" + width + " x " + height +
1223                    " , format " + format);
1224            for (int i = 0; i < CALLBACK_BUFFER_COUNT; i++) {
1225                mCamera.addCallbackBuffer(new byte[size]);
1226            }
1227            mCamera.setPreviewCallbackWithBuffer(this);
1228        }
1229        mLastCallbackTimestamp = -1;
1230        mCallbackFrameCount = 0;
1231        mCallbackAvgFrameDuration = 30;
1232    }
1233
1234    private void stopCallbacks() {
1235        if (mState >= CAMERA_OPEN) {
1236            mCamera.setPreviewCallbackWithBuffer(null);
1237            if (mCallbackProcessor != null) {
1238                if (!mCallbackProcessor.stop()) {
1239                    logE("Can't stop preview callback processing!");
1240                }
1241            }
1242        }
1243    }
1244
1245    @Override
1246    public void onPreviewFrame(byte[] data, Camera camera) {
1247        long timestamp = SystemClock.elapsedRealtime();
1248        if (mLastCallbackTimestamp != -1) {
1249            long frameDuration = timestamp - mLastCallbackTimestamp;
1250            mCallbackAvgFrameDuration =
1251                    mCallbackAvgFrameDuration * MEAN_FPS_HISTORY_COEFF +
1252                    frameDuration * MEAN_FPS_MEASUREMENT_COEFF;
1253        }
1254        mLastCallbackTimestamp = timestamp;
1255        if (mState < CAMERA_PREVIEW || !mCallbacksEnabled) {
1256            mCamera.addCallbackBuffer(data);
1257            return;
1258        }
1259        mCallbackFrameCount++;
1260        if (mCallbackFrameCount % FPS_REPORTING_PERIOD == 0) {
1261            log("Got " + FPS_REPORTING_PERIOD + " callback frames, fps "
1262                    + 1e3/mCallbackAvgFrameDuration);
1263        }
1264        mCallbackProcessor.displayCallback(data);
1265
1266        mCamera.addCallbackBuffer(data);
1267    }
1268
1269    @Override
1270    public void onError(int error, Camera camera) {
1271        String errorName;
1272        switch (error) {
1273        case Camera.CAMERA_ERROR_SERVER_DIED:
1274            errorName = "SERVER_DIED";
1275            break;
1276        case Camera.CAMERA_ERROR_UNKNOWN:
1277            errorName = "UNKNOWN";
1278            break;
1279        default:
1280            errorName = "?";
1281            break;
1282        }
1283        logE("Camera error received: " + errorName + " (" + error + ")" );
1284        logE("Shutting down camera");
1285        resetCamera();
1286        mCameraSpinner.setSelection(0);
1287    }
1288
1289    static final int MEDIA_TYPE_IMAGE = 0;
1290    static final int MEDIA_TYPE_VIDEO = 1;
1291    @SuppressLint("SimpleDateFormat")
1292    File getOutputMediaFile(int type){
1293        // To be safe, you should check that the SDCard is mounted
1294        // using Environment.getExternalStorageState() before doing this.
1295
1296        String state = Environment.getExternalStorageState();
1297        if (!Environment.MEDIA_MOUNTED.equals(state)) {
1298                return null;
1299        }
1300
1301        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
1302                  Environment.DIRECTORY_DCIM), "TestingCamera");
1303        // This location works best if you want the created images to be shared
1304        // between applications and persist after your app has been uninstalled.
1305
1306        // Create the storage directory if it does not exist
1307        if (! mediaStorageDir.exists()){
1308            if (! mediaStorageDir.mkdirs()){
1309                logE("Failed to create directory for pictures/video");
1310                return null;
1311            }
1312        }
1313
1314        // Create a media file name
1315        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
1316        File mediaFile;
1317        if (type == MEDIA_TYPE_IMAGE){
1318            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
1319            "IMG_"+ timeStamp + ".jpg");
1320        } else if(type == MEDIA_TYPE_VIDEO) {
1321            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
1322            "VID_"+ timeStamp + ".mp4");
1323        } else {
1324            return null;
1325        }
1326
1327        return mediaFile;
1328    }
1329
1330    void notifyMediaScannerOfFile(File newFile,
1331                final MediaScannerConnection.OnScanCompletedListener listener) {
1332        final Handler h = new Handler();
1333        MediaScannerConnection.scanFile(this,
1334                new String[] { newFile.toString() },
1335                null,
1336                new MediaScannerConnection.OnScanCompletedListener() {
1337                    @Override
1338                    public void onScanCompleted(final String path, final Uri uri) {
1339                        h.post(new Runnable() {
1340                            @Override
1341                            public void run() {
1342                                log("MediaScanner notified: " +
1343                                        path + " -> " + uri);
1344                                if (listener != null)
1345                                    listener.onScanCompleted(path, uri);
1346                            }
1347                        });
1348                    }
1349                });
1350    }
1351
1352    private void deleteFile(File badFile) {
1353        if (badFile.exists()) {
1354            boolean success = badFile.delete();
1355            if (success) log("Deleted file " + badFile.toString());
1356            else log("Unable to delete file " + badFile.toString());
1357        }
1358    }
1359
1360    private void startRecording() {
1361        log("Starting recording");
1362        logIndent(1);
1363        log("Configuring MediaRecoder");
1364
1365        mRecordHandoffCheckBox.setEnabled(false);
1366        if (mRecordHandoffCheckBox.isChecked()) {
1367            mCamera.release();
1368        } else {
1369            mCamera.unlock();
1370        }
1371
1372        if (mRecorder != null) {
1373            mRecorder.release();
1374        }
1375
1376        mRecorder = new MediaRecorder();
1377        mRecorder.setOnErrorListener(mRecordingErrorListener);
1378        mRecorder.setOnInfoListener(mRecordingInfoListener);
1379        if (!mRecordHandoffCheckBox.isChecked()) {
1380            mRecorder.setCamera(mCamera);
1381        }
1382        mRecorder.setPreviewDisplay(mPreviewHolder.getSurface());
1383
1384        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1385        mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1386        mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
1387        Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize);
1388        if (videoRecordSize.width > 0 && videoRecordSize.height > 0) {
1389            mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height);
1390        }
1391        if (mVideoFrameRates.get(mVideoFrameRate) > 0) {
1392            mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate));
1393        }
1394        File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
1395        log("File name:" + outputFile.toString());
1396        mRecorder.setOutputFile(outputFile.toString());
1397
1398        boolean ready = false;
1399        log("Preparing MediaRecorder");
1400        try {
1401            mRecorder.prepare();
1402            ready = true;
1403        } catch (Exception e) {
1404            StringWriter writer = new StringWriter();
1405            e.printStackTrace(new PrintWriter(writer));
1406            logE("Exception preparing MediaRecorder:\n" + writer.toString());
1407        }
1408
1409        if (ready) {
1410            try {
1411                log("Starting MediaRecorder");
1412                mRecorder.start();
1413                mState = CAMERA_RECORD;
1414                log("Recording active");
1415                mRecordingFile = outputFile;
1416            } catch (Exception e) {
1417                StringWriter writer = new StringWriter();
1418                e.printStackTrace(new PrintWriter(writer));
1419                logE("Exception starting MediaRecorder:\n" + writer.toString());
1420                ready = false;
1421            }
1422        }
1423
1424        if (!ready) {
1425            mRecordToggle.setChecked(false);
1426            mRecordHandoffCheckBox.setEnabled(true);
1427
1428            if (mRecordHandoffCheckBox.isChecked()) {
1429                mState = CAMERA_UNINITIALIZED;
1430                setUpCamera();
1431            }
1432        }
1433        logIndent(-1);
1434    }
1435
1436    private MediaRecorder.OnErrorListener mRecordingErrorListener =
1437            new MediaRecorder.OnErrorListener() {
1438        @Override
1439        public void onError(MediaRecorder mr, int what, int extra) {
1440            logE("MediaRecorder reports error: " + what + ", extra "
1441                    + extra);
1442            if (mState == CAMERA_RECORD) {
1443                stopRecording(true);
1444            }
1445        }
1446    };
1447
1448    private MediaRecorder.OnInfoListener mRecordingInfoListener =
1449            new MediaRecorder.OnInfoListener() {
1450        @Override
1451        public void onInfo(MediaRecorder mr, int what, int extra) {
1452            log("MediaRecorder reports info: " + what + ", extra "
1453                    + extra);
1454        }
1455    };
1456
1457    private void stopRecording(boolean error) {
1458        log("Stopping recording");
1459        mRecordHandoffCheckBox.setEnabled(true);
1460        mRecordToggle.setChecked(false);
1461        if (mRecorder != null) {
1462            mRecorder.stop();
1463
1464            if (mRecordHandoffCheckBox.isChecked()) {
1465                mState = CAMERA_UNINITIALIZED;
1466                setUpCamera();
1467            } else {
1468                mCamera.lock();
1469                mState = CAMERA_PREVIEW;
1470            }
1471
1472            if (!error) {
1473                notifyMediaScannerOfFile(mRecordingFile, null);
1474            } else {
1475                deleteFile(mRecordingFile);
1476            }
1477            mRecordingFile = null;
1478        } else {
1479            logE("Recorder is unexpectedly null!");
1480        }
1481    }
1482
1483    static int getCallbackBufferSize(int width, int height, int format) {
1484        int size = -1;
1485        switch (format) {
1486        case ImageFormat.NV21:
1487            size = width * height * 3 / 2;
1488            break;
1489        case ImageFormat.YV12:
1490            int y_stride = (int) (Math.ceil( width / 16.) * 16);
1491            int y_size = y_stride * height;
1492            int c_stride = (int) (Math.ceil(y_stride / 32.) * 16);
1493            int c_size = c_stride * height/2;
1494            size = y_size + c_size * 2;
1495            break;
1496        case ImageFormat.NV16:
1497        case ImageFormat.RGB_565:
1498        case ImageFormat.YUY2:
1499            size = 2 * width * height;
1500            break;
1501        case ImageFormat.JPEG:
1502            Log.e(TAG, "JPEG callback buffers not supported!");
1503            size = 0;
1504            break;
1505        case ImageFormat.UNKNOWN:
1506            Log.e(TAG, "Unknown-format callback buffers not supported!");
1507            size = 0;
1508            break;
1509        }
1510        return size;
1511    }
1512
1513    private int mLogIndentLevel = 0;
1514    private String mLogIndent = "\t";
1515    /** Increment or decrement log indentation level */
1516    synchronized void logIndent(int delta) {
1517        mLogIndentLevel += delta;
1518        if (mLogIndentLevel < 0) mLogIndentLevel = 0;
1519        char[] mLogIndentArray = new char[mLogIndentLevel + 1];
1520        for (int i = -1; i < mLogIndentLevel; i++) {
1521            mLogIndentArray[i + 1] = '\t';
1522        }
1523        mLogIndent = new String(mLogIndentArray);
1524    }
1525
1526    @SuppressLint("SimpleDateFormat")
1527    SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS");
1528    /** Log both to log text view and to device logcat */
1529    void log(String logLine) {
1530        Log.d(TAG, logLine);
1531        logAndScrollToBottom(logLine, mLogIndent);
1532    }
1533
1534    void logE(String logLine) {
1535        Log.e(TAG, logLine);
1536        logAndScrollToBottom(logLine, mLogIndent + "!!! ");
1537    }
1538
1539    synchronized private void logAndScrollToBottom(String logLine, String logIndent) {
1540        StringBuffer logEntry = new StringBuffer(32);
1541        logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent);
1542        logEntry.append(logLine);
1543        mLogView.append(logEntry);
1544        final Layout layout = mLogView.getLayout();
1545        if (layout != null){
1546            int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1)
1547                - mLogView.getScrollY() - mLogView.getHeight();
1548            if(scrollDelta > 0) {
1549                mLogView.scrollBy(0, scrollDelta);
1550            }
1551        }
1552    }
1553}
1554