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