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