• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.testingcamera;
18 
19 import android.annotation.SuppressLint;
20 import android.app.Activity;
21 import android.app.FragmentManager;
22 import android.content.res.Resources;
23 import android.graphics.ImageFormat;
24 import android.hardware.Camera;
25 import android.hardware.Camera.Parameters;
26 import android.hardware.Camera.ErrorCallback;
27 import android.media.CamcorderProfile;
28 import android.media.MediaRecorder;
29 import android.media.MediaScannerConnection;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.Handler;
34 import android.os.SystemClock;
35 import android.view.View;
36 import android.view.SurfaceHolder;
37 import android.view.SurfaceView;
38 import android.view.View.OnClickListener;
39 import android.widget.AdapterView;
40 import android.widget.AdapterView.OnItemSelectedListener;
41 import android.widget.ArrayAdapter;
42 import android.widget.Button;
43 import android.widget.CheckBox;
44 import android.widget.LinearLayout;
45 import android.widget.LinearLayout.LayoutParams;
46 import android.widget.Spinner;
47 import android.widget.TextView;
48 import android.widget.ToggleButton;
49 import android.renderscript.RenderScript;
50 import android.text.Layout;
51 import android.text.method.ScrollingMovementMethod;
52 import android.util.Log;
53 import android.util.SparseArray;
54 
55 import java.io.File;
56 import java.io.IOException;
57 import java.io.PrintWriter;
58 import java.io.StringWriter;
59 import java.text.SimpleDateFormat;
60 import java.util.ArrayList;
61 import java.util.Date;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65 
66 /**
67  * A simple test application for the camera API.
68  *
69  * The goal of this application is to allow all camera API features to be
70  * exercised, and all information provided by the API to be shown.
71  */
72 public class TestingCamera extends Activity
73     implements SurfaceHolder.Callback, Camera.PreviewCallback,
74         Camera.ErrorCallback {
75 
76     /** UI elements */
77     private SurfaceView mPreviewView;
78     private SurfaceHolder mPreviewHolder;
79     private LinearLayout mPreviewColumn;
80 
81     private SurfaceView mCallbackView;
82     private SurfaceHolder mCallbackHolder;
83 
84     private Spinner mCameraSpinner;
85     private CheckBox mKeepOpenCheckBox;
86     private Button mInfoButton;
87     private Spinner mPreviewSizeSpinner;
88     private Spinner mPreviewFrameRateSpinner;
89     private ToggleButton mPreviewToggle;
90     private Spinner mAutofocusModeSpinner;
91     private Button mAutofocusButton;
92     private Button mCancelAutofocusButton;
93     private TextView mFlashModeSpinnerLabel;
94     private Spinner mFlashModeSpinner;
95     private ToggleButton mExposureLockToggle;
96     private Spinner mSnapshotSizeSpinner;
97     private Button  mTakePictureButton;
98     private Spinner mCamcorderProfileSpinner;
99     private Spinner mVideoRecordSizeSpinner;
100     private Spinner mVideoFrameRateSpinner;
101     private ToggleButton mRecordToggle;
102     private CheckBox mRecordHandoffCheckBox;
103     private ToggleButton mRecordStabilizationToggle;
104     private Spinner mCallbackFormatSpinner;
105     private ToggleButton mCallbackToggle;
106 
107     private TextView mLogView;
108 
109     private Set<View> mOpenOnlyControls = new HashSet<View>();
110     private Set<View> mPreviewOnlyControls = new HashSet<View>();
111 
112     private SparseArray<String> mFormatNames;
113 
114     /** Camera state */
115     private int mCameraId;
116     private Camera mCamera;
117     private Camera.Parameters mParams;
118     private List<Camera.Size> mPreviewSizes;
119     private int mPreviewSize = 0;
120     private List<Integer> mPreviewFrameRates;
121     private int mPreviewFrameRate = 0;
122     private List<Integer> mPreviewFormats;
123     private int mPreviewFormat = 0;
124     private List<String> mAfModes;
125     private int mAfMode = 0;
126     private List<String> mFlashModes;
127     private int mFlashMode = 0;
128     private List<Camera.Size> mSnapshotSizes;
129     private int mSnapshotSize = 0;
130     private List<CamcorderProfile> mCamcorderProfiles;
131     private int mCamcorderProfile = 0;
132     private List<Camera.Size> mVideoRecordSizes;
133     private int mVideoRecordSize = 0;
134     private List<Integer> mVideoFrameRates;
135     private int mVideoFrameRate = 0;
136 
137     private MediaRecorder mRecorder;
138     private File mRecordingFile;
139 
140     private RenderScript mRS;
141 
142     private boolean mCallbacksEnabled = false;
143     private CallbackProcessor mCallbackProcessor = null;
144     long mLastCallbackTimestamp = -1;
145     float mCallbackAvgFrameDuration = 30;
146     int mCallbackFrameCount = 0;
147     private static final float MEAN_FPS_HISTORY_COEFF = 0.9f;
148     private static final float MEAN_FPS_MEASUREMENT_COEFF = 0.1f;
149     private static final int   FPS_REPORTING_PERIOD = 200; // frames
150     private static final int CALLBACK_BUFFER_COUNT = 3;
151 
152     private static final int CAMERA_UNINITIALIZED = 0;
153     private static final int CAMERA_OPEN = 1;
154     private static final int CAMERA_PREVIEW = 2;
155     private static final int CAMERA_TAKE_PICTURE = 3;
156     private static final int CAMERA_RECORD = 4;
157     private int mState = CAMERA_UNINITIALIZED;
158 
159     private static final int NO_CAMERA_ID = -1;
160 
161     /** Misc variables */
162 
163     private static final String TAG = "TestingCamera";
164 
165 
166     /** Activity lifecycle */
167 
168     @Override
onCreate(Bundle savedInstanceState)169     public void onCreate(Bundle savedInstanceState) {
170         super.onCreate(savedInstanceState);
171 
172         setContentView(R.layout.main);
173 
174         mPreviewColumn = (LinearLayout) findViewById(R.id.preview_column);
175 
176         mPreviewView = (SurfaceView) findViewById(R.id.preview);
177         mPreviewView.getHolder().addCallback(this);
178 
179         mCallbackView = (SurfaceView)findViewById(R.id.callback_view);
180 
181         mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner);
182         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
183 
184         mKeepOpenCheckBox = (CheckBox) findViewById(R.id.keep_open_checkbox);
185 
186         mInfoButton = (Button) findViewById(R.id.info_button);
187         mInfoButton.setOnClickListener(mInfoButtonListener);
188         mOpenOnlyControls.add(mInfoButton);
189 
190         mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner);
191         mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener);
192         mOpenOnlyControls.add(mPreviewSizeSpinner);
193 
194         mPreviewFrameRateSpinner = (Spinner) findViewById(R.id.preview_frame_rate_spinner);
195         mPreviewFrameRateSpinner.setOnItemSelectedListener(mPreviewFrameRateListener);
196         mOpenOnlyControls.add(mPreviewFrameRateSpinner);
197 
198         mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview);
199         mPreviewToggle.setOnClickListener(mPreviewToggleListener);
200         mOpenOnlyControls.add(mPreviewToggle);
201 
202         mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner);
203         mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener);
204         mOpenOnlyControls.add(mAutofocusModeSpinner);
205 
206         mAutofocusButton = (Button) findViewById(R.id.af_button);
207         mAutofocusButton.setOnClickListener(mAutofocusButtonListener);
208         mPreviewOnlyControls.add(mAutofocusButton);
209 
210         mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button);
211         mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener);
212         mPreviewOnlyControls.add(mCancelAutofocusButton);
213 
214         mFlashModeSpinnerLabel = (TextView) findViewById(R.id.flash_mode_spinner_label);
215 
216         mFlashModeSpinner = (Spinner) findViewById(R.id.flash_mode_spinner);
217         mFlashModeSpinner.setOnItemSelectedListener(mFlashModeListener);
218         mOpenOnlyControls.add(mFlashModeSpinner);
219 
220         mExposureLockToggle = (ToggleButton) findViewById(R.id.exposure_lock);
221         mExposureLockToggle.setOnClickListener(mExposureLockToggleListener);
222         mOpenOnlyControls.add(mExposureLockToggle);
223 
224         mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner);
225         mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener);
226         mOpenOnlyControls.add(mSnapshotSizeSpinner);
227 
228         mTakePictureButton = (Button) findViewById(R.id.take_picture);
229         mTakePictureButton.setOnClickListener(mTakePictureListener);
230         mPreviewOnlyControls.add(mTakePictureButton);
231 
232         mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner);
233         mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener);
234         mOpenOnlyControls.add(mCamcorderProfileSpinner);
235 
236         mVideoRecordSizeSpinner = (Spinner) findViewById(R.id.video_record_size_spinner);
237         mVideoRecordSizeSpinner.setOnItemSelectedListener(mVideoRecordSizeListener);
238         mOpenOnlyControls.add(mVideoRecordSizeSpinner);
239 
240         mVideoFrameRateSpinner = (Spinner) findViewById(R.id.video_frame_rate_spinner);
241         mVideoFrameRateSpinner.setOnItemSelectedListener(mVideoFrameRateListener);
242         mOpenOnlyControls.add(mVideoFrameRateSpinner);
243 
244         mRecordToggle = (ToggleButton) findViewById(R.id.start_record);
245         mRecordToggle.setOnClickListener(mRecordToggleListener);
246         mPreviewOnlyControls.add(mRecordToggle);
247 
248         mRecordHandoffCheckBox = (CheckBox) findViewById(R.id.record_handoff_checkbox);
249 
250         mRecordStabilizationToggle = (ToggleButton) findViewById(R.id.record_stabilization);
251         mRecordStabilizationToggle.setOnClickListener(mRecordStabilizationToggleListener);
252         mOpenOnlyControls.add(mRecordStabilizationToggle);
253 
254         mCallbackFormatSpinner = (Spinner) findViewById(R.id.callback_format_spinner);
255         mCallbackFormatSpinner.setOnItemSelectedListener(mCallbackFormatListener);
256         mOpenOnlyControls.add(mCallbackFormatSpinner);
257 
258         mCallbackToggle = (ToggleButton) findViewById(R.id.enable_callbacks);
259         mCallbackToggle.setOnClickListener(mCallbackToggleListener);
260         mOpenOnlyControls.add(mCallbackToggle);
261 
262         mLogView = (TextView) findViewById(R.id.log);
263         mLogView.setMovementMethod(new ScrollingMovementMethod());
264 
265         mOpenOnlyControls.addAll(mPreviewOnlyControls);
266 
267         mFormatNames = new SparseArray<String>(7);
268         mFormatNames.append(ImageFormat.JPEG, "JPEG");
269         mFormatNames.append(ImageFormat.NV16, "NV16");
270         mFormatNames.append(ImageFormat.NV21, "NV21");
271         mFormatNames.append(ImageFormat.RGB_565, "RGB_565");
272         mFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN");
273         mFormatNames.append(ImageFormat.YUY2, "YUY2");
274         mFormatNames.append(ImageFormat.YV12, "YV12");
275 
276         int numCameras = Camera.getNumberOfCameras();
277         String[] cameraNames = new String[numCameras + 1];
278         cameraNames[0] = "None";
279         for (int i = 0; i < numCameras; i++) {
280             cameraNames[i + 1] = "Camera " + i;
281         }
282 
283         mCameraSpinner.setAdapter(
284                 new ArrayAdapter<String>(this,
285                         R.layout.spinner_item, cameraNames));
286         if (numCameras > 0) {
287             mCameraId = 0;
288             mCameraSpinner.setSelection(mCameraId + 1);
289         } else {
290             resetCamera();
291             mCameraSpinner.setSelection(0);
292         }
293 
294         mRS = RenderScript.create(this);
295     }
296 
297     @Override
onResume()298     public void onResume() {
299         super.onResume();
300         log("onResume: Setting up");
301         mPreviewHolder = null;
302         setUpCamera();
303     }
304 
305     @Override
onPause()306     public void onPause() {
307         super.onPause();
308         if (mState == CAMERA_RECORD) {
309             stopRecording(false);
310         }
311         if (mKeepOpenCheckBox.isChecked()) {
312             log("onPause: Not releasing camera");
313 
314             if (mState == CAMERA_PREVIEW) {
315                 mCamera.stopPreview();
316                 mState = CAMERA_OPEN;
317             }
318         } else {
319             log("onPause: Releasing camera");
320 
321             if (mCamera != null) {
322                 mCamera.release();
323             }
324             mState = CAMERA_UNINITIALIZED;
325         }
326     }
327 
328     /** SurfaceHolder.Callback methods */
329     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)330     public void surfaceChanged(SurfaceHolder holder,
331             int format,
332             int width,
333             int height) {
334         if (holder == mPreviewView.getHolder()) {
335             if (mState >= CAMERA_OPEN) {
336                 final int previewWidth =
337                         mPreviewSizes.get(mPreviewSize).width;
338                 final int previewHeight =
339                         mPreviewSizes.get(mPreviewSize).height;
340 
341                 if ( Math.abs((float)previewWidth / previewHeight -
342                         (float)width/height) > 0.01f) {
343                     Handler h = new Handler();
344                     h.post(new Runnable() {
345                         @Override
346                         public void run() {
347                             layoutPreview();
348                         }
349                     });
350                 }
351             }
352 
353             if (mPreviewHolder != null) {
354                 return;
355             }
356             log("Surface holder available: " + width + " x " + height);
357             mPreviewHolder = holder;
358             try {
359                 if (mCamera != null) {
360                     mCamera.setPreviewDisplay(holder);
361                 }
362             } catch (IOException e) {
363                 logE("Unable to set up preview!");
364             }
365         } else if (holder == mCallbackView.getHolder()) {
366             mCallbackHolder = holder;
367         }
368     }
369 
370     @Override
surfaceCreated(SurfaceHolder holder)371     public void surfaceCreated(SurfaceHolder holder) {
372 
373     }
374 
375     @Override
surfaceDestroyed(SurfaceHolder holder)376     public void surfaceDestroyed(SurfaceHolder holder) {
377         mPreviewHolder = null;
378     }
379 
380     /** UI controls enable/disable for all open-only controls */
enableOpenOnlyControls(boolean enabled)381     private void enableOpenOnlyControls(boolean enabled) {
382         for (View v : mOpenOnlyControls) {
383                 v.setEnabled(enabled);
384         }
385     }
386 
387     /** UI controls enable/disable for all preview-only controls */
enablePreviewOnlyControls(boolean enabled)388     private void enablePreviewOnlyControls(boolean enabled) {
389         for (View v : mPreviewOnlyControls) {
390                 v.setEnabled(enabled);
391         }
392     }
393 
394     /** UI listeners */
395 
396     private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
397                 new AdapterView.OnItemSelectedListener() {
398         @Override
399         public void onItemSelected(AdapterView<?> parent,
400                         View view, int pos, long id) {
401             int cameraId = pos - 1;
402             if (mCameraId != cameraId) {
403                 resetCamera();
404                 mCameraId = cameraId;
405                 mPreviewToggle.setChecked(false);
406                 setUpCamera();
407             }
408         }
409 
410         @Override
411         public void onNothingSelected(AdapterView<?> parent) {
412 
413         }
414     };
415 
416     private OnClickListener mInfoButtonListener = new OnClickListener() {
417         @Override
418         public void onClick(View v) {
419             if (mCameraId != NO_CAMERA_ID) {
420                 FragmentManager fm = getFragmentManager();
421                 InfoDialogFragment infoDialog = new InfoDialogFragment();
422                 infoDialog.updateInfo(mCameraId, mCamera);
423                 infoDialog.show(fm, "info_dialog_fragment");
424             }
425         }
426     };
427 
428     private AdapterView.OnItemSelectedListener mPreviewSizeListener =
429         new AdapterView.OnItemSelectedListener() {
430         @Override
431         public void onItemSelected(AdapterView<?> parent,
432                 View view, int pos, long id) {
433             if (pos == mPreviewSize) return;
434             if (mState == CAMERA_PREVIEW) {
435                 log("Stopping preview and callbacks to switch resolutions");
436                 stopCallbacks();
437                 mCamera.stopPreview();
438             }
439 
440             mPreviewSize = pos;
441             int width = mPreviewSizes.get(mPreviewSize).width;
442             int height = mPreviewSizes.get(mPreviewSize).height;
443             mParams.setPreviewSize(width, height);
444 
445             log("Setting preview size to " + width + "x" + height);
446 
447             mCamera.setParameters(mParams);
448             resizePreview();
449 
450             if (mState == CAMERA_PREVIEW) {
451                 log("Restarting preview");
452                 mCamera.startPreview();
453             }
454         }
455 
456         @Override
457         public void onNothingSelected(AdapterView<?> parent) {
458 
459         }
460     };
461 
462     private AdapterView.OnItemSelectedListener mPreviewFrameRateListener =
463                 new AdapterView.OnItemSelectedListener() {
464         @Override
465         public void onItemSelected(AdapterView<?> parent,
466                         View view, int pos, long id) {
467             if (pos == mPreviewFrameRate) return;
468             mPreviewFrameRate = pos;
469             mParams.setPreviewFrameRate(mPreviewFrameRates.get(mPreviewFrameRate));
470 
471             log("Setting preview frame rate to " + ((TextView)view).getText());
472 
473             mCamera.setParameters(mParams);
474         }
475 
476         @Override
477         public void onNothingSelected(AdapterView<?> parent) {
478 
479         }
480     };
481 
482     private View.OnClickListener mPreviewToggleListener =
483             new View.OnClickListener() {
484         @Override
485         public void onClick(View v) {
486             if (mState == CAMERA_TAKE_PICTURE) {
487                 logE("Can't change preview state while taking picture!");
488                 return;
489             }
490             if (mPreviewToggle.isChecked()) {
491                 log("Starting preview");
492                 mCamera.startPreview();
493                 mState = CAMERA_PREVIEW;
494                 enablePreviewOnlyControls(true);
495             } else {
496                 log("Stopping preview");
497                 mCamera.stopPreview();
498                 mState = CAMERA_OPEN;
499 
500                 enablePreviewOnlyControls(false);
501             }
502         }
503     };
504 
505     private OnItemSelectedListener mAutofocusModeListener =
506                 new OnItemSelectedListener() {
507         @Override
508         public void onItemSelected(AdapterView<?> parent,
509                         View view, int pos, long id) {
510             if (pos == mAfMode) return;
511 
512             mAfMode = pos;
513             String focusMode = mAfModes.get(mAfMode);
514             log("Setting focus mode to " + focusMode);
515             if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE ||
516                         focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) {
517                 mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback);
518             }
519             mParams.setFocusMode(focusMode);
520 
521             mCamera.setParameters(mParams);
522         }
523 
524         @Override
525         public void onNothingSelected(AdapterView<?> arg0) {
526 
527         }
528     };
529 
530     private OnClickListener mAutofocusButtonListener =
531             new View.OnClickListener() {
532         @Override
533         public void onClick(View v) {
534             log("Triggering autofocus");
535             mCamera.autoFocus(mAutofocusCallback);
536         }
537     };
538 
539     private OnClickListener mCancelAutofocusButtonListener =
540             new View.OnClickListener() {
541         @Override
542         public void onClick(View v) {
543             log("Cancelling autofocus");
544             mCamera.cancelAutoFocus();
545         }
546     };
547 
548     private Camera.AutoFocusCallback mAutofocusCallback =
549             new Camera.AutoFocusCallback() {
550         @Override
551         public void onAutoFocus(boolean success, Camera camera) {
552             log("Autofocus completed: " + (success ? "success" : "failure") );
553         }
554     };
555 
556     private Camera.AutoFocusMoveCallback mAutofocusMoveCallback =
557             new Camera.AutoFocusMoveCallback() {
558         @Override
559         public void onAutoFocusMoving(boolean start, Camera camera) {
560             log("Autofocus movement: " + (start ? "starting" : "stopped") );
561         }
562     };
563 
564     private OnItemSelectedListener mFlashModeListener =
565                 new OnItemSelectedListener() {
566         @Override
567         public void onItemSelected(AdapterView<?> parent,
568                         View view, int pos, long id) {
569             if (pos == mFlashMode) return;
570 
571             mFlashMode = pos;
572             String flashMode = mFlashModes.get(mFlashMode);
573             log("Setting flash mode to " + flashMode);
574             mParams.setFlashMode(flashMode);
575             mCamera.setParameters(mParams);
576         }
577 
578         @Override
579         public void onNothingSelected(AdapterView<?> arg0) {
580 
581         }
582     };
583 
584 
585     private AdapterView.OnItemSelectedListener mSnapshotSizeListener =
586             new AdapterView.OnItemSelectedListener() {
587         @Override
588         public void onItemSelected(AdapterView<?> parent,
589                 View view, int pos, long id) {
590             if (pos == mSnapshotSize) return;
591 
592             mSnapshotSize = pos;
593             int width = mSnapshotSizes.get(mSnapshotSize).width;
594             int height = mSnapshotSizes.get(mSnapshotSize).height;
595             log("Setting snapshot size to " + width + " x " + height);
596 
597             mParams.setPictureSize(width, height);
598 
599             mCamera.setParameters(mParams);
600         }
601 
602         @Override
603         public void onNothingSelected(AdapterView<?> parent) {
604 
605         }
606     };
607 
608     private View.OnClickListener mTakePictureListener =
609             new View.OnClickListener() {
610         @Override
611         public void onClick(View v) {
612             log("Taking picture");
613             if (mState == CAMERA_PREVIEW) {
614                 mState = CAMERA_TAKE_PICTURE;
615                 enablePreviewOnlyControls(false);
616                 mPreviewToggle.setChecked(false);
617 
618                 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb);
619             } else {
620                 logE("Can't take picture while not running preview!");
621             }
622         }
623     };
624 
625     private AdapterView.OnItemSelectedListener mCamcorderProfileListener =
626                 new AdapterView.OnItemSelectedListener() {
627         @Override
628         public void onItemSelected(AdapterView<?> parent,
629                         View view, int pos, long id) {
630             if (pos != mCamcorderProfile) {
631                 log("Setting camcorder profile to " + ((TextView)view).getText());
632                 mCamcorderProfile = pos;
633             }
634 
635             // Additionally change video recording size to match
636             mVideoRecordSize = 0; // "default", in case it's not found
637             int width = mCamcorderProfiles.get(pos).videoFrameWidth;
638             int height = mCamcorderProfiles.get(pos).videoFrameHeight;
639             for (int i = 0; i < mVideoRecordSizes.size(); i++) {
640                 Camera.Size s = mVideoRecordSizes.get(i);
641                 if (width == s.width && height == s.height) {
642                     mVideoRecordSize = i;
643                     break;
644                 }
645             }
646             log("Setting video record size to " + mVideoRecordSize);
647             mVideoRecordSizeSpinner.setSelection(mVideoRecordSize);
648         }
649 
650         @Override
651         public void onNothingSelected(AdapterView<?> parent) {
652 
653         }
654     };
655 
656     private AdapterView.OnItemSelectedListener mVideoRecordSizeListener =
657                 new AdapterView.OnItemSelectedListener() {
658         @Override
659         public void onItemSelected(AdapterView<?> parent,
660                         View view, int pos, long id) {
661             if (pos == mVideoRecordSize) return;
662 
663             log("Setting video record size to " + ((TextView)view).getText());
664             mVideoRecordSize = pos;
665         }
666 
667         @Override
668         public void onNothingSelected(AdapterView<?> parent) {
669 
670         }
671     };
672 
673     private AdapterView.OnItemSelectedListener mVideoFrameRateListener =
674                 new AdapterView.OnItemSelectedListener() {
675         @Override
676         public void onItemSelected(AdapterView<?> parent,
677                         View view, int pos, long id) {
678             if (pos == mVideoFrameRate) return;
679 
680             log("Setting video frame rate to " + ((TextView)view).getText());
681             mVideoFrameRate = pos;
682         }
683 
684         @Override
685         public void onNothingSelected(AdapterView<?> parent) {
686 
687         }
688     };
689 
690     private View.OnClickListener mRecordToggleListener =
691             new View.OnClickListener() {
692         @Override
693         public void onClick(View v) {
694             mPreviewToggle.setEnabled(false);
695             if (mState == CAMERA_PREVIEW) {
696                 startRecording();
697             } else if (mState == CAMERA_RECORD) {
698                 stopRecording(false);
699             } else {
700                 logE("Can't toggle recording in current state!");
701             }
702             mPreviewToggle.setEnabled(true);
703         }
704     };
705 
706     private View.OnClickListener mRecordStabilizationToggleListener =
707             new View.OnClickListener() {
708         @Override
709         public void onClick(View v) {
710             boolean on = ((ToggleButton) v).isChecked();
711             mParams.setVideoStabilization(on);
712 
713             mCamera.setParameters(mParams);
714         }
715     };
716 
717     private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() {
718         @Override
719         public void onShutter() {
720             log("Shutter callback received");
721         }
722     };
723 
724     private Camera.PictureCallback mRawCb = new Camera.PictureCallback() {
725         @Override
726         public void onPictureTaken(byte[] data, Camera camera) {
727             log("Raw callback received");
728         }
729     };
730 
731     private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() {
732         @Override
733         public void onPictureTaken(byte[] data, Camera camera) {
734             log("Postview callback received");
735         }
736     };
737 
738     private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() {
739         @Override
740         public void onPictureTaken(byte[] data, Camera camera) {
741             log("JPEG picture callback received");
742             FragmentManager fm = getFragmentManager();
743             SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment();
744 
745             snapshotDialog.updateImage(data);
746             snapshotDialog.show(fm, "snapshot_dialog_fragment");
747 
748             mPreviewToggle.setEnabled(true);
749 
750             mState = CAMERA_OPEN;
751         }
752     };
753 
754     private AdapterView.OnItemSelectedListener mCallbackFormatListener =
755             new AdapterView.OnItemSelectedListener() {
756         public void onItemSelected(AdapterView<?> parent,
757                         View view, int pos, long id) {
758             mPreviewFormat = pos;
759 
760             log("Setting preview format to " +
761                     mFormatNames.get(mPreviewFormats.get(mPreviewFormat)));
762 
763             switch (mState) {
764             case CAMERA_UNINITIALIZED:
765                 return;
766             case CAMERA_OPEN:
767                 break;
768             case CAMERA_PREVIEW:
769                 if (mCallbacksEnabled) {
770                     log("Stopping preview and callbacks to switch formats");
771                     stopCallbacks();
772                     mCamera.stopPreview();
773                 }
774                 break;
775             case CAMERA_RECORD:
776                 logE("Can't update format while recording active");
777                 return;
778             }
779 
780             mParams.setPreviewFormat(mPreviewFormats.get(mPreviewFormat));
781             mCamera.setParameters(mParams);
782 
783             if (mCallbacksEnabled) {
784                 if (mState == CAMERA_PREVIEW) {
785                     mCamera.startPreview();
786                 }
787             }
788 
789             configureCallbacks(mCallbackView.getWidth(), mCallbackView.getHeight());
790         }
791 
792         public void onNothingSelected(AdapterView<?> parent) {
793 
794         }
795     };
796 
797     private View.OnClickListener mCallbackToggleListener =
798                 new View.OnClickListener() {
799         public void onClick(View v) {
800             if (mCallbacksEnabled) {
801                 log("Disabling preview callbacks");
802                 stopCallbacks();
803                 mCallbacksEnabled = false;
804                 resizePreview();
805                 mCallbackView.setVisibility(View.GONE);
806 
807             } else {
808                 log("Enabling preview callbacks");
809                 mCallbacksEnabled = true;
810                 resizePreview();
811                 mCallbackView.setVisibility(View.VISIBLE);
812             }
813         }
814     };
815 
816 
817     // Internal methods
818 
setUpCamera()819     void setUpCamera() {
820         if (mCameraId == NO_CAMERA_ID) return;
821 
822         log("Setting up camera " + mCameraId);
823         logIndent(1);
824 
825         if (mState < CAMERA_OPEN) {
826             log("Opening camera " + mCameraId);
827 
828             try {
829                 mCamera = Camera.open(mCameraId);
830             } catch (RuntimeException e) {
831                 logE("Exception opening camera: " + e.getMessage());
832                 resetCamera();
833                 mCameraSpinner.setSelection(0);
834                 logIndent(-1);
835                 return;
836             }
837             mState = CAMERA_OPEN;
838         }
839 
840         mCamera.setErrorCallback(this);
841         mParams = mCamera.getParameters();
842 
843         // Set up preview size selection
844 
845         log("Configuring camera");
846         logIndent(1);
847 
848         updatePreviewSizes(mParams);
849         updatePreviewFrameRate(mCameraId);
850         updatePreviewFormats(mParams);
851         updateAfModes(mParams);
852         updateFlashModes(mParams);
853         updateSnapshotSizes(mParams);
854         updateCamcorderProfile(mCameraId);
855         updateVideoRecordSize(mCameraId);
856         updateVideoFrameRate(mCameraId);
857 
858         // Trigger updating video record size to match camcorder profile
859         mCamcorderProfileSpinner.setSelection(mCamcorderProfile);
860 
861         if (mParams.isVideoStabilizationSupported()) {
862             log("Video stabilization is supported");
863             mRecordStabilizationToggle.setEnabled(true);
864         } else {
865             log("Video stabilization not supported");
866             mRecordStabilizationToggle.setEnabled(false);
867         }
868 
869         if (mParams.isAutoExposureLockSupported()) {
870             log("Auto-Exposure locking is supported");
871             mExposureLockToggle.setEnabled(true);
872         } else {
873             log("Auto-Exposure locking is not supported");
874             mExposureLockToggle.setEnabled(false);
875         }
876 
877         // Update parameters based on above updates
878         mCamera.setParameters(mParams);
879 
880         if (mPreviewHolder != null) {
881             log("Setting preview display");
882             try {
883                 mCamera.setPreviewDisplay(mPreviewHolder);
884             } catch(IOException e) {
885                 Log.e(TAG, "Unable to set up preview!");
886             }
887         }
888 
889         logIndent(-1);
890 
891         enableOpenOnlyControls(true);
892 
893         resizePreview();
894         if (mPreviewToggle.isChecked()) {
895             log("Starting preview" );
896             mCamera.startPreview();
897             mState = CAMERA_PREVIEW;
898         } else {
899             mState = CAMERA_OPEN;
900             enablePreviewOnlyControls(false);
901         }
902         logIndent(-1);
903     }
904 
resetCamera()905     private void resetCamera() {
906         if (mState >= CAMERA_OPEN) {
907             log("Closing old camera");
908             mCamera.release();
909         }
910         mCamera = null;
911         mCameraId = NO_CAMERA_ID;
912         mState = CAMERA_UNINITIALIZED;
913 
914         enableOpenOnlyControls(false);
915     }
916 
updateAfModes(Parameters params)917     private void updateAfModes(Parameters params) {
918         mAfModes = params.getSupportedFocusModes();
919 
920         mAutofocusModeSpinner.setAdapter(
921                 new ArrayAdapter<String>(this, R.layout.spinner_item,
922                         mAfModes.toArray(new String[0])));
923 
924         mAfMode = 0;
925 
926         params.setFocusMode(mAfModes.get(mAfMode));
927 
928         log("Setting AF mode to " + mAfModes.get(mAfMode));
929     }
930 
updateFlashModes(Parameters params)931     private void updateFlashModes(Parameters params) {
932         mFlashModes = params.getSupportedFlashModes();
933 
934         if (mFlashModes != null) {
935             mFlashModeSpinnerLabel.setVisibility(View.VISIBLE);
936             mFlashModeSpinner.setVisibility(View.VISIBLE);
937             mFlashModeSpinner.setAdapter(
938                     new ArrayAdapter<String>(this, R.layout.spinner_item,
939                             mFlashModes.toArray(new String[0])));
940 
941             mFlashMode = 0;
942 
943             params.setFlashMode(mFlashModes.get(mFlashMode));
944 
945             log("Setting Flash mode to " + mFlashModes.get(mFlashMode));
946         } else {
947             // this camera has no flash
948             mFlashModeSpinnerLabel.setVisibility(View.GONE);
949             mFlashModeSpinner.setVisibility(View.GONE);
950         }
951     }
952 
953     private View.OnClickListener mExposureLockToggleListener =
954             new View.OnClickListener() {
955         public void onClick(View v) {
956             boolean on = ((ToggleButton) v).isChecked();
957             log("Auto-Exposure was " + mParams.getAutoExposureLock());
958             mParams.setAutoExposureLock(on);
959             log("Auto-Exposure is now " + mParams.getAutoExposureLock());
960         }
961     };
962 
updatePreviewSizes(Camera.Parameters params)963     private void updatePreviewSizes(Camera.Parameters params) {
964         mPreviewSizes = params.getSupportedPreviewSizes();
965 
966         String[] availableSizeNames = new String[mPreviewSizes.size()];
967         int i = 0;
968         for (Camera.Size previewSize: mPreviewSizes) {
969             availableSizeNames[i++] =
970                 Integer.toString(previewSize.width) + " x " +
971                 Integer.toString(previewSize.height);
972         }
973         mPreviewSizeSpinner.setAdapter(
974                 new ArrayAdapter<String>(
975                         this, R.layout.spinner_item, availableSizeNames));
976 
977         mPreviewSize = 0;
978 
979         int width = mPreviewSizes.get(mPreviewSize).width;
980         int height = mPreviewSizes.get(mPreviewSize).height;
981         params.setPreviewSize(width, height);
982         log("Setting preview size to " + width + " x " + height);
983     }
984 
updatePreviewFrameRate(int cameraId)985     private void updatePreviewFrameRate(int cameraId) {
986         List<Integer> frameRates = mParams.getSupportedPreviewFrameRates();
987         int defaultPreviewFrameRate = mParams.getPreviewFrameRate();
988 
989         List<String> frameRateStrings = new ArrayList<String>();
990         mPreviewFrameRates = new ArrayList<Integer>();
991 
992         int currentIndex = 0;
993         for (Integer frameRate : frameRates) {
994             mPreviewFrameRates.add(frameRate);
995             if(frameRate == defaultPreviewFrameRate) {
996                 frameRateStrings.add(frameRate.toString() + " (Default)");
997                 mPreviewFrameRate = currentIndex;
998             } else {
999                 frameRateStrings.add(frameRate.toString());
1000             }
1001             currentIndex++;
1002         }
1003 
1004         String[] nameArray = (String[])frameRateStrings.toArray(new String[0]);
1005         mPreviewFrameRateSpinner.setAdapter(
1006                 new ArrayAdapter<String>(
1007                         this, R.layout.spinner_item, nameArray));
1008 
1009         mPreviewFrameRateSpinner.setSelection(mPreviewFrameRate);
1010         log("Setting preview frame rate to " + nameArray[mPreviewFrameRate]);
1011     }
1012 
updatePreviewFormats(Camera.Parameters params)1013     private void updatePreviewFormats(Camera.Parameters params) {
1014         mPreviewFormats = params.getSupportedPreviewFormats();
1015 
1016         String[] availableFormatNames = new String[mPreviewFormats.size()];
1017         int i = 0;
1018         for (Integer previewFormat: mPreviewFormats) {
1019             availableFormatNames[i++] = mFormatNames.get(previewFormat);
1020         }
1021         mCallbackFormatSpinner.setAdapter(
1022                 new ArrayAdapter<String>(
1023                         this, R.layout.spinner_item, availableFormatNames));
1024 
1025         mPreviewFormat = 0;
1026         mCallbacksEnabled = false;
1027         mCallbackToggle.setChecked(false);
1028         mCallbackView.setVisibility(View.GONE);
1029 
1030         params.setPreviewFormat(mPreviewFormats.get(mPreviewFormat));
1031         log("Setting preview format to " +
1032                 mFormatNames.get(mPreviewFormats.get(mPreviewFormat)));
1033     }
1034 
updateSnapshotSizes(Camera.Parameters params)1035     private void updateSnapshotSizes(Camera.Parameters params) {
1036         String[] availableSizeNames;
1037         mSnapshotSizes = params.getSupportedPictureSizes();
1038 
1039         availableSizeNames = new String[mSnapshotSizes.size()];
1040         int i = 0;
1041         for (Camera.Size snapshotSize : mSnapshotSizes) {
1042             availableSizeNames[i++] =
1043                 Integer.toString(snapshotSize.width) + " x " +
1044                 Integer.toString(snapshotSize.height);
1045         }
1046         mSnapshotSizeSpinner.setAdapter(
1047                 new ArrayAdapter<String>(
1048                         this, R.layout.spinner_item, availableSizeNames));
1049 
1050         mSnapshotSize = 0;
1051 
1052         int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width;
1053         int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height;
1054         params.setPictureSize(snapshotWidth, snapshotHeight);
1055         log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight);
1056     }
1057 
updateCamcorderProfile(int cameraId)1058     private void updateCamcorderProfile(int cameraId) {
1059         // Have to query all of these individually,
1060         final int PROFILES[] = new int[] {
1061             CamcorderProfile.QUALITY_1080P,
1062             CamcorderProfile.QUALITY_480P,
1063             CamcorderProfile.QUALITY_720P,
1064             CamcorderProfile.QUALITY_CIF,
1065             CamcorderProfile.QUALITY_HIGH,
1066             CamcorderProfile.QUALITY_LOW,
1067             CamcorderProfile.QUALITY_QCIF,
1068             CamcorderProfile.QUALITY_QVGA,
1069             CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
1070             CamcorderProfile.QUALITY_TIME_LAPSE_480P,
1071             CamcorderProfile.QUALITY_TIME_LAPSE_720P,
1072             CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
1073             CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
1074             CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
1075             CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
1076             CamcorderProfile.QUALITY_TIME_LAPSE_QVGA
1077         };
1078 
1079         final String PROFILE_NAMES[] = new String[] {
1080             "1080P",
1081             "480P",
1082             "720P",
1083             "CIF",
1084             "HIGH",
1085             "LOW",
1086             "QCIF",
1087             "QVGA",
1088             "TIME_LAPSE_1080P",
1089             "TIME_LAPSE_480P",
1090             "TIME_LAPSE_720P",
1091             "TIME_LAPSE_CIF",
1092             "TIME_LAPSE_HIGH",
1093             "TIME_LAPSE_LOW",
1094             "TIME_LAPSE_QCIF",
1095             "TIME_LAPSE_QVGA"
1096         };
1097 
1098         List<String> availableCamcorderProfileNames = new ArrayList<String>();
1099         mCamcorderProfiles = new ArrayList<CamcorderProfile>();
1100 
1101         for (int i = 0; i < PROFILES.length; i++) {
1102             if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) {
1103                 availableCamcorderProfileNames.add(PROFILE_NAMES[i]);
1104                 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
1105             }
1106         }
1107         String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]);
1108         mCamcorderProfileSpinner.setAdapter(
1109                 new ArrayAdapter<String>(
1110                         this, R.layout.spinner_item, nameArray));
1111 
1112         mCamcorderProfile = 0;
1113         log("Setting camcorder profile to " + nameArray[mCamcorderProfile]);
1114 
1115     }
1116 
updateVideoRecordSize(int cameraId)1117     private void updateVideoRecordSize(int cameraId) {
1118         List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes();
1119         if (videoSizes == null) { // TODO: surface this to the user
1120             log("Failed to get video size list, using preview sizes instead");
1121             videoSizes = mParams.getSupportedPreviewSizes();
1122         }
1123 
1124         List<String> availableVideoRecordSizes = new ArrayList<String>();
1125         mVideoRecordSizes = new ArrayList<Camera.Size>();
1126 
1127         availableVideoRecordSizes.add("Default");
1128         mVideoRecordSizes.add(mCamera.new Size(0,0));
1129 
1130         for (Camera.Size s : videoSizes) {
1131               availableVideoRecordSizes.add(s.width + "x" + s.height);
1132               mVideoRecordSizes.add(s);
1133         }
1134         String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]);
1135         mVideoRecordSizeSpinner.setAdapter(
1136                 new ArrayAdapter<String>(
1137                         this, R.layout.spinner_item, nameArray));
1138 
1139         mVideoRecordSize = 0;
1140         log("Setting video record profile to " + nameArray[mVideoRecordSize]);
1141     }
1142 
updateVideoFrameRate(int cameraId)1143     private void updateVideoFrameRate(int cameraId) {
1144         // Use preview framerates as video framerates
1145         List<Integer> frameRates = mParams.getSupportedPreviewFrameRates();
1146 
1147         List<String> frameRateStrings = new ArrayList<String>();
1148         mVideoFrameRates = new ArrayList<Integer>();
1149 
1150         frameRateStrings.add("Default");
1151         mVideoFrameRates.add(0);
1152 
1153         for (Integer frameRate : frameRates) {
1154             frameRateStrings.add(frameRate.toString());
1155             mVideoFrameRates.add(frameRate);
1156         }
1157         String[] nameArray = (String[])frameRateStrings.toArray(new String[0]);
1158         mVideoFrameRateSpinner.setAdapter(
1159                 new ArrayAdapter<String>(
1160                         this, R.layout.spinner_item, nameArray));
1161 
1162         mVideoFrameRate = 0;
1163         log("Setting recording frame rate to " + nameArray[mVideoFrameRate]);
1164     }
1165 
resizePreview()1166     void resizePreview() {
1167         // Reset preview layout parameters, to trigger layout pass
1168         // This will eventually call layoutPreview below
1169         Resources res = getResources();
1170         mPreviewView.setLayoutParams(
1171                 new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0,
1172                         mCallbacksEnabled ?
1173                         res.getInteger(R.integer.preview_with_callback_weight):
1174                         res.getInteger(R.integer.preview_only_weight) ));
1175     }
1176 
layoutPreview()1177     void layoutPreview() {
1178         int width = mPreviewSizes.get(mPreviewSize).width;
1179         int height = mPreviewSizes.get(mPreviewSize).height;
1180         float previewAspect = ((float) width) / height;
1181 
1182         int viewHeight = mPreviewView.getHeight();
1183         int viewWidth = mPreviewView.getWidth();
1184         float viewAspect = ((float) viewWidth) / viewHeight;
1185         if ( previewAspect > viewAspect) {
1186             viewHeight = (int) (viewWidth / previewAspect);
1187         } else {
1188             viewWidth = (int) (viewHeight * previewAspect);
1189         }
1190         mPreviewView.setLayoutParams(
1191                 new LayoutParams(viewWidth, viewHeight));
1192 
1193         if (mCallbacksEnabled) {
1194             int callbackHeight = mCallbackView.getHeight();
1195             int callbackWidth = mCallbackView.getWidth();
1196             float callbackAspect = ((float) callbackWidth) / callbackHeight;
1197             if ( previewAspect > callbackAspect) {
1198                 callbackHeight = (int) (callbackWidth / previewAspect);
1199             } else {
1200                 callbackWidth = (int) (callbackHeight * previewAspect);
1201             }
1202             mCallbackView.setLayoutParams(
1203                     new LayoutParams(callbackWidth, callbackHeight));
1204             configureCallbacks(callbackWidth, callbackHeight);
1205         }
1206     }
1207 
1208 
configureCallbacks(int callbackWidth, int callbackHeight)1209     private void configureCallbacks(int callbackWidth, int callbackHeight) {
1210         if (mState >= CAMERA_OPEN && mCallbacksEnabled) {
1211             mCamera.setPreviewCallbackWithBuffer(null);
1212             int width = mPreviewSizes.get(mPreviewSize).width;
1213             int height = mPreviewSizes.get(mPreviewSize).height;
1214             int format = mPreviewFormats.get(mPreviewFormat);
1215 
1216             mCallbackProcessor = new CallbackProcessor(width, height, format,
1217                     getResources(), mCallbackView,
1218                     callbackWidth, callbackHeight, mRS);
1219 
1220             int size = getCallbackBufferSize(width, height, format);
1221             log("Configuring callbacks:" + width + " x " + height +
1222                     " , format " + format);
1223             for (int i = 0; i < CALLBACK_BUFFER_COUNT; i++) {
1224                 mCamera.addCallbackBuffer(new byte[size]);
1225             }
1226             mCamera.setPreviewCallbackWithBuffer(this);
1227         }
1228         mLastCallbackTimestamp = -1;
1229         mCallbackFrameCount = 0;
1230         mCallbackAvgFrameDuration = 30;
1231     }
1232 
stopCallbacks()1233     private void stopCallbacks() {
1234         if (mState >= CAMERA_OPEN) {
1235             mCamera.setPreviewCallbackWithBuffer(null);
1236             if (mCallbackProcessor != null) {
1237                 if (!mCallbackProcessor.stop()) {
1238                     logE("Can't stop preview callback processing!");
1239                 }
1240             }
1241         }
1242     }
1243 
1244     @Override
onPreviewFrame(byte[] data, Camera camera)1245     public void onPreviewFrame(byte[] data, Camera camera) {
1246         long timestamp = SystemClock.elapsedRealtime();
1247         if (mLastCallbackTimestamp != -1) {
1248             long frameDuration = timestamp - mLastCallbackTimestamp;
1249             mCallbackAvgFrameDuration =
1250                     mCallbackAvgFrameDuration * MEAN_FPS_HISTORY_COEFF +
1251                     frameDuration * MEAN_FPS_MEASUREMENT_COEFF;
1252         }
1253         mLastCallbackTimestamp = timestamp;
1254         if (mState < CAMERA_PREVIEW || !mCallbacksEnabled) {
1255             mCamera.addCallbackBuffer(data);
1256             return;
1257         }
1258         mCallbackFrameCount++;
1259         if (mCallbackFrameCount % FPS_REPORTING_PERIOD == 0) {
1260             log("Got " + FPS_REPORTING_PERIOD + " callback frames, fps "
1261                     + 1e3/mCallbackAvgFrameDuration);
1262         }
1263         mCallbackProcessor.displayCallback(data);
1264 
1265         mCamera.addCallbackBuffer(data);
1266     }
1267 
1268     @Override
onError(int error, Camera camera)1269     public void onError(int error, Camera camera) {
1270         String errorName;
1271         switch (error) {
1272         case Camera.CAMERA_ERROR_SERVER_DIED:
1273             errorName = "SERVER_DIED";
1274             break;
1275         case Camera.CAMERA_ERROR_UNKNOWN:
1276             errorName = "UNKNOWN";
1277             break;
1278         default:
1279             errorName = "?";
1280             break;
1281         }
1282         logE("Camera error received: " + errorName + " (" + error + ")" );
1283         logE("Shutting down camera");
1284         resetCamera();
1285         mCameraSpinner.setSelection(0);
1286     }
1287 
1288     static final int MEDIA_TYPE_IMAGE = 0;
1289     static final int MEDIA_TYPE_VIDEO = 1;
1290     @SuppressLint("SimpleDateFormat")
getOutputMediaFile(int type)1291     File getOutputMediaFile(int type){
1292         // To be safe, you should check that the SDCard is mounted
1293         // using Environment.getExternalStorageState() before doing this.
1294 
1295         String state = Environment.getExternalStorageState();
1296         if (!Environment.MEDIA_MOUNTED.equals(state)) {
1297                 return null;
1298         }
1299 
1300         File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
1301                   Environment.DIRECTORY_DCIM), "TestingCamera");
1302         // This location works best if you want the created images to be shared
1303         // between applications and persist after your app has been uninstalled.
1304 
1305         // Create the storage directory if it does not exist
1306         if (! mediaStorageDir.exists()){
1307             if (! mediaStorageDir.mkdirs()){
1308                 logE("Failed to create directory for pictures/video");
1309                 return null;
1310             }
1311         }
1312 
1313         // Create a media file name
1314         String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
1315         File mediaFile;
1316         if (type == MEDIA_TYPE_IMAGE){
1317             mediaFile = new File(mediaStorageDir.getPath() + File.separator +
1318             "IMG_"+ timeStamp + ".jpg");
1319         } else if(type == MEDIA_TYPE_VIDEO) {
1320             mediaFile = new File(mediaStorageDir.getPath() + File.separator +
1321             "VID_"+ timeStamp + ".mp4");
1322         } else {
1323             return null;
1324         }
1325 
1326         return mediaFile;
1327     }
1328 
notifyMediaScannerOfFile(File newFile, final MediaScannerConnection.OnScanCompletedListener listener)1329     void notifyMediaScannerOfFile(File newFile,
1330                 final MediaScannerConnection.OnScanCompletedListener listener) {
1331         final Handler h = new Handler();
1332         MediaScannerConnection.scanFile(this,
1333                 new String[] { newFile.toString() },
1334                 null,
1335                 new MediaScannerConnection.OnScanCompletedListener() {
1336                     @Override
1337                     public void onScanCompleted(final String path, final Uri uri) {
1338                         h.post(new Runnable() {
1339                             @Override
1340                             public void run() {
1341                                 log("MediaScanner notified: " +
1342                                         path + " -> " + uri);
1343                                 if (listener != null)
1344                                     listener.onScanCompleted(path, uri);
1345                             }
1346                         });
1347                     }
1348                 });
1349     }
1350 
deleteFile(File badFile)1351     private void deleteFile(File badFile) {
1352         if (badFile.exists()) {
1353             boolean success = badFile.delete();
1354             if (success) log("Deleted file " + badFile.toString());
1355             else log("Unable to delete file " + badFile.toString());
1356         }
1357     }
1358 
startRecording()1359     private void startRecording() {
1360         log("Starting recording");
1361         logIndent(1);
1362         log("Configuring MediaRecoder");
1363 
1364         mRecordHandoffCheckBox.setEnabled(false);
1365         if (mRecordHandoffCheckBox.isChecked()) {
1366             mCamera.release();
1367         } else {
1368             mCamera.unlock();
1369         }
1370 
1371         if (mRecorder != null) {
1372             mRecorder.release();
1373         }
1374 
1375         mRecorder = new MediaRecorder();
1376         mRecorder.setOnErrorListener(mRecordingErrorListener);
1377         mRecorder.setOnInfoListener(mRecordingInfoListener);
1378         if (!mRecordHandoffCheckBox.isChecked()) {
1379             mRecorder.setCamera(mCamera);
1380         }
1381         mRecorder.setPreviewDisplay(mPreviewHolder.getSurface());
1382 
1383         mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1384         mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1385         mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
1386         Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize);
1387         if (videoRecordSize.width > 0 && videoRecordSize.height > 0) {
1388             mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height);
1389         }
1390         if (mVideoFrameRates.get(mVideoFrameRate) > 0) {
1391             mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate));
1392         }
1393         File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
1394         log("File name:" + outputFile.toString());
1395         mRecorder.setOutputFile(outputFile.toString());
1396 
1397         boolean ready = false;
1398         log("Preparing MediaRecorder");
1399         try {
1400             mRecorder.prepare();
1401             ready = true;
1402         } catch (Exception e) {
1403             StringWriter writer = new StringWriter();
1404             e.printStackTrace(new PrintWriter(writer));
1405             logE("Exception preparing MediaRecorder:\n" + writer.toString());
1406         }
1407 
1408         if (ready) {
1409             try {
1410                 log("Starting MediaRecorder");
1411                 mRecorder.start();
1412                 mState = CAMERA_RECORD;
1413                 log("Recording active");
1414                 mRecordingFile = outputFile;
1415             } catch (Exception e) {
1416                 StringWriter writer = new StringWriter();
1417                 e.printStackTrace(new PrintWriter(writer));
1418                 logE("Exception starting MediaRecorder:\n" + writer.toString());
1419                 ready = false;
1420             }
1421         }
1422 
1423         if (!ready) {
1424             mRecordToggle.setChecked(false);
1425             mRecordHandoffCheckBox.setEnabled(true);
1426 
1427             if (mRecordHandoffCheckBox.isChecked()) {
1428                 mState = CAMERA_UNINITIALIZED;
1429                 setUpCamera();
1430             }
1431         }
1432         logIndent(-1);
1433     }
1434 
1435     private MediaRecorder.OnErrorListener mRecordingErrorListener =
1436             new MediaRecorder.OnErrorListener() {
1437         @Override
1438         public void onError(MediaRecorder mr, int what, int extra) {
1439             logE("MediaRecorder reports error: " + what + ", extra "
1440                     + extra);
1441             if (mState == CAMERA_RECORD) {
1442                 stopRecording(true);
1443             }
1444         }
1445     };
1446 
1447     private MediaRecorder.OnInfoListener mRecordingInfoListener =
1448             new MediaRecorder.OnInfoListener() {
1449         @Override
1450         public void onInfo(MediaRecorder mr, int what, int extra) {
1451             log("MediaRecorder reports info: " + what + ", extra "
1452                     + extra);
1453         }
1454     };
1455 
stopRecording(boolean error)1456     private void stopRecording(boolean error) {
1457         log("Stopping recording");
1458         mRecordHandoffCheckBox.setEnabled(true);
1459         mRecordToggle.setChecked(false);
1460         if (mRecorder != null) {
1461             try {
1462                 mRecorder.stop();
1463             } catch (RuntimeException e) {
1464                 // this can happen if there were no frames received by recorder
1465                 logE("Could not create output file");
1466                 error = true;
1467             }
1468 
1469             if (mRecordHandoffCheckBox.isChecked()) {
1470                 mState = CAMERA_UNINITIALIZED;
1471                 setUpCamera();
1472             } else {
1473                 mCamera.lock();
1474                 mState = CAMERA_PREVIEW;
1475             }
1476 
1477             if (!error) {
1478                 notifyMediaScannerOfFile(mRecordingFile, null);
1479             } else {
1480                 deleteFile(mRecordingFile);
1481             }
1482             mRecordingFile = null;
1483         } else {
1484             logE("Recorder is unexpectedly null!");
1485         }
1486     }
1487 
getCallbackBufferSize(int width, int height, int format)1488     static int getCallbackBufferSize(int width, int height, int format) {
1489         int size = -1;
1490         switch (format) {
1491         case ImageFormat.NV21:
1492             size = width * height * 3 / 2;
1493             break;
1494         case ImageFormat.YV12:
1495             int y_stride = (int) (Math.ceil( width / 16.) * 16);
1496             int y_size = y_stride * height;
1497             int c_stride = (int) (Math.ceil(y_stride / 32.) * 16);
1498             int c_size = c_stride * height/2;
1499             size = y_size + c_size * 2;
1500             break;
1501         case ImageFormat.NV16:
1502         case ImageFormat.RGB_565:
1503         case ImageFormat.YUY2:
1504             size = 2 * width * height;
1505             break;
1506         case ImageFormat.JPEG:
1507             Log.e(TAG, "JPEG callback buffers not supported!");
1508             size = 0;
1509             break;
1510         case ImageFormat.UNKNOWN:
1511             Log.e(TAG, "Unknown-format callback buffers not supported!");
1512             size = 0;
1513             break;
1514         }
1515         return size;
1516     }
1517 
1518     private int mLogIndentLevel = 0;
1519     private String mLogIndent = "\t";
1520     /** Increment or decrement log indentation level */
logIndent(int delta)1521     synchronized void logIndent(int delta) {
1522         mLogIndentLevel += delta;
1523         if (mLogIndentLevel < 0) mLogIndentLevel = 0;
1524         char[] mLogIndentArray = new char[mLogIndentLevel + 1];
1525         for (int i = -1; i < mLogIndentLevel; i++) {
1526             mLogIndentArray[i + 1] = '\t';
1527         }
1528         mLogIndent = new String(mLogIndentArray);
1529     }
1530 
1531     @SuppressLint("SimpleDateFormat")
1532     SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS");
1533     /** Log both to log text view and to device logcat */
log(String logLine)1534     void log(String logLine) {
1535         Log.d(TAG, logLine);
1536         logAndScrollToBottom(logLine, mLogIndent);
1537     }
1538 
logE(String logLine)1539     void logE(String logLine) {
1540         Log.e(TAG, logLine);
1541         logAndScrollToBottom(logLine, mLogIndent + "!!! ");
1542     }
1543 
logAndScrollToBottom(String logLine, String logIndent)1544     synchronized private void logAndScrollToBottom(String logLine, String logIndent) {
1545         StringBuffer logEntry = new StringBuffer(32);
1546         logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent);
1547         logEntry.append(logLine);
1548         mLogView.append(logEntry);
1549         final Layout layout = mLogView.getLayout();
1550         if (layout != null){
1551             int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1)
1552                 - mLogView.getScrollY() - mLogView.getHeight();
1553             if(scrollDelta > 0) {
1554                 mLogView.scrollBy(0, scrollDelta);
1555             }
1556         }
1557     }
1558 }
1559