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