TestingCamera.java revision abe36163445fb3fe458252fd8d21c3e1ac14ed6d
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.app.Activity;
20import android.app.Dialog;
21import android.app.FragmentManager;
22import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.hardware.Camera;
25import android.hardware.Camera.Parameters;
26import android.media.CamcorderProfile;
27import android.media.MediaRecorder;
28import android.os.Bundle;
29import android.os.Environment;
30import android.view.View;
31import android.view.SurfaceHolder;
32import android.view.SurfaceView;
33import android.view.View.OnClickListener;
34import android.widget.AdapterView;
35import android.widget.AdapterView.OnItemSelectedListener;
36import android.widget.ArrayAdapter;
37import android.widget.Button;
38import android.widget.ImageView;
39import android.widget.LinearLayout.LayoutParams;
40import android.widget.Spinner;
41import android.widget.CompoundButton;
42import android.widget.TextView;
43import android.widget.ToggleButton;
44import android.text.Layout;
45import android.text.method.ScrollingMovementMethod;
46import android.util.Log;
47
48import java.io.File;
49import java.io.FileDescriptor;
50import java.io.IOException;
51import java.io.PrintWriter;
52import java.io.StringWriter;
53import java.text.FieldPosition;
54import java.text.SimpleDateFormat;
55import java.util.ArrayList;
56import java.util.Comparator;
57import java.util.Date;
58import java.util.HashSet;
59import java.util.List;
60import java.util.Set;
61import java.util.TreeSet;
62
63/**
64 * A simple test application for the camera API.
65 *
66 * The goal of this application is to allow all camera API features to be
67 * exercised, and all information provided by the API to be shown.
68 */
69public class TestingCamera extends Activity implements SurfaceHolder.Callback {
70
71    /** UI elements */
72    private SurfaceView mPreviewView;
73    private SurfaceHolder mPreviewHolder;
74
75    private Spinner mCameraSpinner;
76    private Button mInfoButton;
77    private Spinner mPreviewSizeSpinner;
78    private ToggleButton mPreviewToggle;
79    private Spinner mAutofocusModeSpinner;
80    private Button mAutofocusButton;
81    private Button mCancelAutofocusButton;
82    private Spinner mSnapshotSizeSpinner;
83    private Button  mTakePictureButton;
84    private Spinner mCamcorderProfileSpinner;
85    private ToggleButton mRecordToggle;
86
87    private TextView mLogView;
88
89    private Set<View> mPreviewOnlyControls = new HashSet<View>();
90
91    /** Camera state */
92    private int mCameraId = 0;
93    private Camera mCamera;
94    private Camera.Parameters mParams;
95    private List<Camera.Size> mPreviewSizes;
96    private int mPreviewSize = 0;
97    private List<String> mAfModes;
98    private int mAfMode = 0;
99    private List<Camera.Size> mSnapshotSizes;
100    private int mSnapshotSize = 0;
101    private List<CamcorderProfile> mCamcorderProfiles;
102    private int mCamcorderProfile = 0;
103
104    private MediaRecorder mRecorder;
105
106    private static final int CAMERA_UNINITIALIZED = 0;
107    private static final int CAMERA_OPEN = 1;
108    private static final int CAMERA_PREVIEW = 2;
109    private static final int CAMERA_TAKE_PICTURE = 3;
110    private static final int CAMERA_RECORD = 4;
111    private int mState = CAMERA_UNINITIALIZED;
112
113    /** Misc variables */
114
115    private static final String TAG = "TestingCamera";
116
117    /** Activity lifecycle */
118
119    @Override
120    public void onCreate(Bundle savedInstanceState) {
121        super.onCreate(savedInstanceState);
122
123        setContentView(R.layout.main);
124
125        mPreviewView = (SurfaceView)findViewById(R.id.preview);
126        mPreviewView.getHolder().addCallback(this);
127
128        mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner);
129        mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
130
131        mInfoButton = (Button) findViewById(R.id.info_button);
132        mInfoButton.setOnClickListener(mInfoButtonListener);
133
134        mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner);
135        mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener);
136
137        mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview);
138        mPreviewToggle.setOnClickListener(mPreviewToggleListener);
139
140        mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner);
141        mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener);
142
143        mAutofocusButton = (Button) findViewById(R.id.af_button);
144        mAutofocusButton.setOnClickListener(mAutofocusButtonListener);
145        mPreviewOnlyControls.add(mAutofocusButton);
146
147        mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button);
148        mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener);
149        mPreviewOnlyControls.add(mCancelAutofocusButton);
150
151        mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner);
152        mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener);
153
154        mTakePictureButton = (Button) findViewById(R.id.take_picture);
155        mTakePictureButton.setOnClickListener(mTakePictureListener);
156        mPreviewOnlyControls.add(mTakePictureButton);
157
158        mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner);
159        mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener);
160
161        mRecordToggle = (ToggleButton) findViewById(R.id.start_record);
162        mRecordToggle.setOnClickListener(mRecordToggleListener);
163        mPreviewOnlyControls.add(mRecordToggle);
164
165        mLogView = (TextView) findViewById(R.id.log);
166        mLogView.setMovementMethod(new ScrollingMovementMethod());
167
168        int numCameras = Camera.getNumberOfCameras();
169        String[] cameraNames = new String[numCameras];
170        for (int i = 0; i < numCameras; i++) {
171            cameraNames[i] = "Camera " + i;
172        }
173
174        mCameraSpinner.setAdapter(
175                new ArrayAdapter<String>(this,
176                        R.layout.spinner_item, cameraNames));
177    }
178
179    @Override
180    public void onResume() {
181        super.onResume();
182        log("onResume: Setting up camera");
183        mPreviewHolder = null;
184        setUpCamera();
185    }
186
187    @Override
188    public void onPause() {
189        super.onPause();
190        log("onPause: Releasing camera");
191        mCamera.release();
192        mState = CAMERA_UNINITIALIZED;
193    }
194
195    /** SurfaceHolder.Callback methods */
196    public void surfaceChanged(SurfaceHolder holder,
197            int format,
198            int width,
199            int height) {
200        if (mPreviewHolder != null) return;
201
202        log("Surface holder available: " + width + " x " + height);
203        mPreviewHolder = holder;
204        try {
205            mCamera.setPreviewDisplay(holder);
206        } catch (IOException e) {
207            logE("Unable to set up preview!");
208        }
209    }
210
211    public void surfaceCreated(SurfaceHolder holder) {
212
213    }
214
215    public void surfaceDestroyed(SurfaceHolder holder) {
216        mPreviewHolder = null;
217    }
218
219    /** UI controls enable/disable */
220    private void enablePreviewOnlyControls(boolean enabled) {
221        for (View v : mPreviewOnlyControls) {
222                v.setEnabled(enabled);
223        }
224    }
225
226    /** UI listeners */
227
228    private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
229                new AdapterView.OnItemSelectedListener() {
230        public void onItemSelected(AdapterView<?> parent,
231                        View view, int pos, long id) {
232            if (mCameraId != pos) {
233                mCameraId = pos;
234                setUpCamera();
235            }
236        }
237
238        public void onNothingSelected(AdapterView<?> parent) {
239
240        }
241    };
242
243    private OnClickListener mInfoButtonListener = new OnClickListener() {
244        public void onClick(View v) {
245            FragmentManager fm = getFragmentManager();
246            InfoDialogFragment infoDialog = new InfoDialogFragment();
247            infoDialog.updateInfo(mCameraId, mCamera);
248            infoDialog.show(fm, "info_dialog_fragment");
249        }
250    };
251
252    private AdapterView.OnItemSelectedListener mPreviewSizeListener =
253        new AdapterView.OnItemSelectedListener() {
254        public void onItemSelected(AdapterView<?> parent,
255                View view, int pos, long id) {
256            if (pos == mPreviewSize) return;
257            if (mState == CAMERA_PREVIEW) {
258                log("Stopping preview to switch resolutions");
259                mCamera.stopPreview();
260            }
261
262            mPreviewSize = pos;
263            int width = mPreviewSizes.get(mPreviewSize).width;
264            int height = mPreviewSizes.get(mPreviewSize).height;
265            mParams.setPreviewSize(width, height);
266
267            log("Setting preview size to " + width + "x" + height);
268
269            mCamera.setParameters(mParams);
270
271            if (mState == CAMERA_PREVIEW) {
272                log("Restarting preview");
273                resizePreview(width, height);
274                mCamera.startPreview();
275            }
276        }
277
278        public void onNothingSelected(AdapterView<?> parent) {
279
280        }
281    };
282
283    private View.OnClickListener mPreviewToggleListener =
284            new View.OnClickListener() {
285        public void onClick(View v) {
286            if (mState == CAMERA_TAKE_PICTURE) {
287                logE("Can't change preview state while taking picture!");
288                return;
289            }
290            if (mPreviewToggle.isChecked()) {
291                log("Starting preview");
292                resizePreview(mPreviewSizes.get(mPreviewSize).width,
293                        mPreviewSizes.get(mPreviewSize).height);
294                mCamera.startPreview();
295                mState = CAMERA_PREVIEW;
296                enablePreviewOnlyControls(true);
297            } else {
298                log("Stopping preview");
299                mCamera.stopPreview();
300                mState = CAMERA_OPEN;
301
302                enablePreviewOnlyControls(false);
303            }
304        }
305    };
306
307    private OnItemSelectedListener mAutofocusModeListener =
308                new OnItemSelectedListener() {
309        public void onItemSelected(AdapterView<?> parent,
310                        View view, int pos, long id) {
311            if (pos == mAfMode) return;
312
313            mAfMode = pos;
314            String focusMode = mAfModes.get(mAfMode);
315            log("Setting focus mode to " + focusMode);
316            mParams.setFocusMode(focusMode);
317
318            mCamera.setParameters(mParams);
319        }
320
321        public void onNothingSelected(AdapterView<?> arg0) {
322
323        }
324    };
325
326    private OnClickListener mAutofocusButtonListener =
327            new View.OnClickListener() {
328        public void onClick(View v) {
329            log("Triggering autofocus");
330            mCamera.autoFocus(mAutofocusCallback);
331        }
332    };
333
334    private OnClickListener mCancelAutofocusButtonListener =
335            new View.OnClickListener() {
336        public void onClick(View v) {
337            log("Cancelling autofocus");
338            mCamera.cancelAutoFocus();
339        }
340    };
341
342    private Camera.AutoFocusCallback mAutofocusCallback =
343            new Camera.AutoFocusCallback() {
344        public void onAutoFocus(boolean success, Camera camera) {
345            log("Autofocus completed: " + (success ? "success" : "failure") );
346        }
347    };
348
349    private Camera.AutoFocusMoveCallback mAutofocusMoveCallback =
350            new Camera.AutoFocusMoveCallback() {
351        public void onAutoFocusMoving(boolean start, Camera camera) {
352            log("Autofocus movement: " + (start ? "starting" : "stopped") );
353        }
354    };
355
356    private AdapterView.OnItemSelectedListener mSnapshotSizeListener =
357            new AdapterView.OnItemSelectedListener() {
358        public void onItemSelected(AdapterView<?> parent,
359                View view, int pos, long id) {
360            if (pos == mSnapshotSize) return;
361
362            mSnapshotSize = pos;
363            int width = mSnapshotSizes.get(mSnapshotSize).width;
364            int height = mSnapshotSizes.get(mSnapshotSize).height;
365            log("Setting snapshot size to " + width + " x " + height);
366
367            mParams.setPictureSize(width, height);
368
369            mCamera.setParameters(mParams);
370        }
371
372        public void onNothingSelected(AdapterView<?> parent) {
373
374        }
375    };
376
377    private View.OnClickListener mTakePictureListener =
378            new View.OnClickListener() {
379        public void onClick(View v) {
380            log("Taking picture");
381            if (mState == CAMERA_PREVIEW) {
382                mState = CAMERA_TAKE_PICTURE;
383                enablePreviewOnlyControls(false);
384                mPreviewToggle.setChecked(false);
385
386                mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb);
387            } else {
388                logE("Can't take picture while not running preview!");
389            }
390        }
391    };
392
393    private AdapterView.OnItemSelectedListener mCamcorderProfileListener =
394                new AdapterView.OnItemSelectedListener() {
395        public void onItemSelected(AdapterView<?> parent,
396                        View view, int pos, long id) {
397            if (pos == mCamcorderProfile) return;
398
399            log("Setting camcorder profile to " + ((TextView)view).getText());
400            mCamcorderProfile = pos;
401        }
402
403        public void onNothingSelected(AdapterView<?> parent) {
404
405        }
406    };
407
408    private View.OnClickListener mRecordToggleListener =
409            new View.OnClickListener() {
410        public void onClick(View v) {
411            mPreviewToggle.setEnabled(false);
412            if (mState == CAMERA_PREVIEW) {
413                startRecording();
414            } else if (mState == CAMERA_RECORD) {
415                stopRecording();
416            } else {
417                logE("Can't toggle recording in current state!");
418            }
419            mPreviewToggle.setEnabled(true);
420        }
421    };
422
423    private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() {
424        public void onShutter() {
425            log("Shutter callback received");
426        }
427    };
428
429    private Camera.PictureCallback mRawCb = new Camera.PictureCallback() {
430        public void onPictureTaken(byte[] data, Camera camera) {
431            log("Raw callback received");
432        }
433    };
434
435    private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() {
436        public void onPictureTaken(byte[] data, Camera camera) {
437            log("Postview callback received");
438        }
439    };
440
441    private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() {
442        public void onPictureTaken(byte[] data, Camera camera) {
443            log("JPEG picture callback received");
444            FragmentManager fm = getFragmentManager();
445            SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment();
446
447            Bitmap img = BitmapFactory.decodeByteArray(data, 0, data.length);
448            snapshotDialog.updateImage(img);
449            snapshotDialog.show(fm, "snapshot_dialog_fragment");
450
451            mPreviewToggle.setEnabled(true);
452
453            mState = CAMERA_OPEN;
454        }
455    };
456
457    // Internal methods
458
459    void setUpCamera() {
460        log("Setting up camera " + mCameraId);
461        logIndent(1);
462        if (mState >= CAMERA_OPEN) {
463            log("Closing old camera");
464            mCamera.release();
465            mState = CAMERA_UNINITIALIZED;
466        }
467        log("Opening camera " + mCameraId);
468        mCamera = Camera.open(mCameraId);
469        mState = CAMERA_OPEN;
470
471        mParams = mCamera.getParameters();
472
473        // Set up preview size selection
474
475        log("Configuring camera");
476        logIndent(1);
477
478        updatePreviewSizes(mParams);
479        updateAfModes(mParams);
480        updateSnapshotSizes(mParams);
481        updateCamcorderProfile(mCameraId);
482
483        // Update parameters based on above updates
484        mCamera.setParameters(mParams);
485
486        mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback);
487
488        if (mPreviewHolder != null) {
489            log("Setting preview display");
490            try {
491                mCamera.setPreviewDisplay(mPreviewHolder);
492            } catch(IOException e) {
493                Log.e(TAG, "Unable to set up preview!");
494            }
495        }
496
497        logIndent(-1);
498
499        mPreviewToggle.setEnabled(true);
500        mPreviewToggle.setChecked(false);
501        enablePreviewOnlyControls(false);
502
503        int width = mPreviewSizes.get(mPreviewSize).width;
504        int height = mPreviewSizes.get(mPreviewSize).height;
505        resizePreview(width, height);
506        if (mPreviewToggle.isChecked()) {
507            log("Starting preview" );
508            mCamera.startPreview();
509            mState = CAMERA_PREVIEW;
510        }
511        logIndent(-1);
512    }
513
514    private void updateAfModes(Parameters params) {
515        mAfModes = params.getSupportedFocusModes();
516
517        mAutofocusModeSpinner.setAdapter(
518                new ArrayAdapter<String>(this, R.layout.spinner_item,
519                        mAfModes.toArray(new String[0])));
520
521        mAfMode = 0;
522
523        params.setFocusMode(mAfModes.get(mAfMode));
524
525        log("Setting AF mode to " + mAfModes.get(mAfMode));
526    }
527
528    private void updatePreviewSizes(Camera.Parameters params) {
529        mPreviewSizes = params.getSupportedPreviewSizes();
530
531        String[] availableSizeNames = new String[mPreviewSizes.size()];
532        int i = 0;
533        for (Camera.Size previewSize: mPreviewSizes) {
534            availableSizeNames[i++] =
535                Integer.toString(previewSize.width) + " x " +
536                Integer.toString(previewSize.height);
537        }
538        mPreviewSizeSpinner.setAdapter(
539                new ArrayAdapter<String>(
540                        this, R.layout.spinner_item, availableSizeNames));
541
542        mPreviewSize = 0;
543
544        int width = mPreviewSizes.get(mPreviewSize).width;
545        int height = mPreviewSizes.get(mPreviewSize).height;
546        params.setPreviewSize(width, height);
547        log("Setting preview size to " + width + " x " + height);
548    }
549
550    private void updateSnapshotSizes(Camera.Parameters params) {
551        String[] availableSizeNames;
552        mSnapshotSizes = params.getSupportedPictureSizes();
553
554        availableSizeNames = new String[mSnapshotSizes.size()];
555        int i = 0;
556        for (Camera.Size snapshotSize : mSnapshotSizes) {
557            availableSizeNames[i++] =
558                Integer.toString(snapshotSize.width) + " x " +
559                Integer.toString(snapshotSize.height);
560        }
561        mSnapshotSizeSpinner.setAdapter(
562                new ArrayAdapter<String>(
563                        this, R.layout.spinner_item, availableSizeNames));
564
565        mSnapshotSize = 0;
566
567        int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width;
568        int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height;
569        params.setPictureSize(snapshotWidth, snapshotHeight);
570        log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight);
571    }
572
573    private void updateCamcorderProfile(int cameraId) {
574        // Have to query all of these individually,
575        final int PROFILES[] = new int[] {
576            CamcorderProfile.QUALITY_1080P,
577            CamcorderProfile.QUALITY_480P,
578            CamcorderProfile.QUALITY_720P,
579            CamcorderProfile.QUALITY_CIF,
580            CamcorderProfile.QUALITY_HIGH,
581            CamcorderProfile.QUALITY_LOW,
582            CamcorderProfile.QUALITY_QCIF,
583            CamcorderProfile.QUALITY_QVGA,
584            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
585            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
586            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
587            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
588            CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
589            CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
590            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
591            CamcorderProfile.QUALITY_TIME_LAPSE_QVGA
592        };
593
594        final String PROFILE_NAMES[] = new String[] {
595            "1080P",
596            "480P",
597            "720P",
598            "CIF",
599            "HIGH",
600            "LOW",
601            "QCIF",
602            "QVGA",
603            "TIME_LAPSE_1080P",
604            "TIME_LAPSE_480P",
605            "TIME_LAPSE_720P",
606            "TIME_LAPSE_CIF",
607            "TIME_LAPSE_HIGH",
608            "TIME_LAPSE_LOW",
609            "TIME_LAPSE_QCIF",
610            "TIME_LAPSE_QVGA"
611        };
612
613        List<String> availableCamcorderProfileNames = new ArrayList<String>();
614        mCamcorderProfiles = new ArrayList<CamcorderProfile>();
615
616        for (int i = 0; i < PROFILES.length; i++) {
617            if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) {
618                availableCamcorderProfileNames.add(PROFILE_NAMES[i]);
619                mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
620            }
621        }
622        String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]);
623        mCamcorderProfileSpinner.setAdapter(
624                new ArrayAdapter<String>(
625                        this, R.layout.spinner_item, nameArray));
626
627        mCamcorderProfile = 0;
628        log("Setting camcorder profile to " + nameArray[mCamcorderProfile]);
629
630    }
631
632    void resizePreview(int width, int height) {
633        if (mPreviewHolder != null) {
634            int viewHeight = mPreviewView.getHeight();
635            int viewWidth = (int)(((double)width)/height * viewHeight);
636
637            mPreviewView.setLayoutParams(
638                new LayoutParams(viewWidth, viewHeight));
639        }
640
641    }
642
643    static final int MEDIA_TYPE_IMAGE = 0;
644    static final int MEDIA_TYPE_VIDEO = 1;
645    private File getOutputMediaFile(int type){
646        // To be safe, you should check that the SDCard is mounted
647        // using Environment.getExternalStorageState() before doing this.
648
649        String state = Environment.getExternalStorageState();
650        if (!Environment.MEDIA_MOUNTED.equals(state)) {
651                return null;
652        }
653
654        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
655                  Environment.DIRECTORY_PICTURES), "TestingCamera");
656        // This location works best if you want the created images to be shared
657        // between applications and persist after your app has been uninstalled.
658
659        // Create the storage directory if it does not exist
660        if (! mediaStorageDir.exists()){
661            if (! mediaStorageDir.mkdirs()){
662                logE("Failed to create directory for pictures/video");
663                return null;
664            }
665        }
666
667        // Create a media file name
668        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
669        File mediaFile;
670        if (type == MEDIA_TYPE_IMAGE){
671            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
672            "IMG_"+ timeStamp + ".jpg");
673        } else if(type == MEDIA_TYPE_VIDEO) {
674            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
675            "VID_"+ timeStamp + ".mp4");
676        } else {
677            return null;
678        }
679
680        return mediaFile;
681    }
682
683    private void startRecording() {
684        log("Starting recording");
685        logIndent(1);
686        log("Configuring MediaRecoder");
687        mCamera.unlock();
688        mRecorder = new MediaRecorder();
689        mRecorder.setOnErrorListener(mRecordingErrorListener);
690        mRecorder.setOnInfoListener(mRecordingInfoListener);
691        mRecorder.setCamera(mCamera);
692        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
693        mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
694        mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
695        File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
696        log("File name:" + outputFile.toString());
697        mRecorder.setOutputFile(outputFile.toString());
698
699        boolean ready = false;
700        log("Preparing MediaRecorder");
701        try {
702            mRecorder.prepare();
703            ready = true;
704        } catch (Exception e) {
705            StringWriter writer = new StringWriter();
706            e.printStackTrace(new PrintWriter(writer));
707            logE("Exception preparing MediaRecorder:\n" + writer.toString());
708        }
709
710        if (ready) {
711            try {
712                log("Starting MediaRecorder");
713                mRecorder.start();
714                mState = CAMERA_RECORD;
715                log("Recording active");
716            } catch (Exception e) {
717                StringWriter writer = new StringWriter();
718                e.printStackTrace(new PrintWriter(writer));
719                logE("Exception starting MediaRecorder:\n" + writer.toString());
720            }
721        } else {
722            mPreviewToggle.setChecked(false);
723        }
724        logIndent(-1);
725    }
726
727    private MediaRecorder.OnErrorListener mRecordingErrorListener =
728            new MediaRecorder.OnErrorListener() {
729        public void onError(MediaRecorder mr, int what, int extra) {
730            logE("MediaRecorder reports error: " + what + ", extra "
731                    + extra);
732            if (mState == CAMERA_RECORD) {
733                stopRecording();
734            }
735        }
736    };
737
738    private MediaRecorder.OnInfoListener mRecordingInfoListener =
739            new MediaRecorder.OnInfoListener() {
740        public void onInfo(MediaRecorder mr, int what, int extra) {
741            log("MediaRecorder reports info: " + what + ", extra "
742                    + extra);
743        }
744    };
745
746    private void stopRecording() {
747        log("Stopping recording");
748        if (mRecorder != null) {
749            mRecorder.stop();
750            mCamera.lock();
751            mState = CAMERA_PREVIEW;
752            mRecorder.release();
753            mRecorder = null;
754        } else {
755            logE("Recorder is unexpectedly null!");
756        }
757    }
758
759    private int mLogIndentLevel = 0;
760    private String mLogIndent = "\t";
761    /** Increment or decrement log indentation level */
762    private void logIndent(int delta) {
763        mLogIndentLevel += delta;
764        if (mLogIndentLevel < 0) mLogIndentLevel = 0;
765        char[] mLogIndentArray = new char[mLogIndentLevel + 1];
766        for (int i = -1; i < mLogIndentLevel; i++) {
767            mLogIndentArray[i + 1] = '\t';
768        }
769        mLogIndent = new String(mLogIndentArray);
770    }
771
772    SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS");
773    /** Log both to log text view and to device logcat */
774    private void log(String logLine) {
775        Log.d(TAG, logLine);
776        logAndScrollToBottom(logLine, mLogIndent);
777    }
778
779    private void logE(String logLine) {
780        Log.e(TAG, logLine);
781        logAndScrollToBottom(logLine, mLogIndent + "!!! ");
782    }
783
784    private void logAndScrollToBottom(String logLine, String logIndent) {
785        StringBuffer logEntry = new StringBuffer(32);
786        logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent);
787        logEntry.append(logLine);
788        mLogView.append(logEntry);
789        final Layout layout = mLogView.getLayout();
790        if (layout != null){
791            int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1)
792                - mLogView.getScrollY() - mLogView.getHeight();
793            if(scrollDelta > 0) {
794                mLogView.scrollBy(0, scrollDelta);
795            }
796        }
797    }
798
799}