Home | History | Annotate | Download | only in testingcamera
      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
    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
    337     public void onResume() {
    338         super.onResume();
    339         log("onResume: Setting up");
    340         setUpCamera();
    341     }
    342 
    343     @Override
    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
    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
    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
    439     public void surfaceCreated(SurfaceHolder holder) {
    440 
    441     }
    442 
    443     @Override
    444     public void surfaceDestroyed(SurfaceHolder holder) {
    445         mPreviewHolder = null;
    446     }
    447 
    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 */
    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 */
    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 
    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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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
   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
   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")
   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 
   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 
   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 
   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 
   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 
   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 
   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 */
   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 */
   1796     void log(String logLine) {
   1797         Log.d(TAG, logLine);
   1798         logAndScrollToBottom(logLine, mLogIndent);
   1799     }
   1800 
   1801     void logE(String logLine) {
   1802         Log.e(TAG, logLine);
   1803         logAndScrollToBottom(logLine, mLogIndent + "!!! ");
   1804     }
   1805 
   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