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