Home | History | Annotate | Download | only in camera
      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.camera;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.Activity;
     21 import android.content.ActivityNotFoundException;
     22 import android.content.BroadcastReceiver;
     23 import android.content.ContentResolver;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.content.SharedPreferences.Editor;
     29 import android.content.res.Configuration;
     30 import android.graphics.Bitmap;
     31 import android.graphics.SurfaceTexture;
     32 import android.hardware.Camera.CameraInfo;
     33 import android.hardware.Camera.Parameters;
     34 import android.hardware.Camera.Size;
     35 import android.location.Location;
     36 import android.media.CamcorderProfile;
     37 import android.media.CameraProfile;
     38 import android.media.MediaRecorder;
     39 import android.net.Uri;
     40 import android.os.Build;
     41 import android.os.Bundle;
     42 import android.os.Handler;
     43 import android.os.Message;
     44 import android.os.ParcelFileDescriptor;
     45 import android.os.SystemClock;
     46 import android.provider.MediaStore;
     47 import android.provider.MediaStore.MediaColumns;
     48 import android.provider.MediaStore.Video;
     49 import android.util.Log;
     50 import android.view.KeyEvent;
     51 import android.view.OrientationEventListener;
     52 import android.view.View;
     53 import android.view.WindowManager;
     54 import android.widget.Toast;
     55 
     56 import com.android.camera.CameraManager.CameraPictureCallback;
     57 import com.android.camera.CameraManager.CameraProxy;
     58 import com.android.camera.app.OrientationManager;
     59 import com.android.camera.exif.ExifInterface;
     60 import com.android.camera.ui.RotateTextToast;
     61 import com.android.camera.util.AccessibilityUtils;
     62 import com.android.camera.util.ApiHelper;
     63 import com.android.camera.util.CameraUtil;
     64 import com.android.camera.util.UsageStatistics;
     65 import com.android.camera2.R;
     66 
     67 import java.io.File;
     68 import java.io.IOException;
     69 import java.text.SimpleDateFormat;
     70 import java.util.Date;
     71 import java.util.Iterator;
     72 import java.util.List;
     73 
     74 public class VideoModule implements CameraModule,
     75     VideoController,
     76     CameraPreference.OnPreferenceChangedListener,
     77     ShutterButton.OnShutterButtonListener,
     78     MediaRecorder.OnErrorListener,
     79     MediaRecorder.OnInfoListener {
     80 
     81     private static final String TAG = "CAM_VideoModule";
     82 
     83     private static final int CHECK_DISPLAY_ROTATION = 3;
     84     private static final int CLEAR_SCREEN_DELAY = 4;
     85     private static final int UPDATE_RECORD_TIME = 5;
     86     private static final int ENABLE_SHUTTER_BUTTON = 6;
     87     private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
     88     private static final int SWITCH_CAMERA = 8;
     89     private static final int SWITCH_CAMERA_START_ANIMATION = 9;
     90 
     91     private static final int SCREEN_DELAY = 2 * 60 * 1000;
     92 
     93     private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
     94 
     95     /**
     96      * An unpublished intent flag requesting to start recording straight away
     97      * and return as soon as recording is stopped.
     98      * TODO: consider publishing by moving into MediaStore.
     99      */
    100     private static final String EXTRA_QUICK_CAPTURE =
    101             "android.intent.extra.quickCapture";
    102 
    103     // module fields
    104     private CameraActivity mActivity;
    105     private boolean mPaused;
    106     private int mCameraId;
    107     private Parameters mParameters;
    108 
    109     private boolean mIsInReviewMode;
    110     private boolean mSnapshotInProgress = false;
    111 
    112     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
    113 
    114     private ComboPreferences mPreferences;
    115     private PreferenceGroup mPreferenceGroup;
    116     // Preference must be read before starting preview. We check this before starting
    117     // preview.
    118     private boolean mPreferenceRead;
    119 
    120     private boolean mIsVideoCaptureIntent;
    121     private boolean mQuickCapture;
    122 
    123     private MediaRecorder mMediaRecorder;
    124 
    125     private boolean mSwitchingCamera;
    126     private boolean mMediaRecorderRecording = false;
    127     private long mRecordingStartTime;
    128     private boolean mRecordingTimeCountsDown = false;
    129     private long mOnResumeTime;
    130     // The video file that the hardware camera is about to record into
    131     // (or is recording into.)
    132     private String mVideoFilename;
    133     private ParcelFileDescriptor mVideoFileDescriptor;
    134 
    135     // The video file that has already been recorded, and that is being
    136     // examined by the user.
    137     private String mCurrentVideoFilename;
    138     private Uri mCurrentVideoUri;
    139     private boolean mCurrentVideoUriFromMediaSaved;
    140     private ContentValues mCurrentVideoValues;
    141 
    142     private CamcorderProfile mProfile;
    143 
    144     // The video duration limit. 0 menas no limit.
    145     private int mMaxVideoDurationInMs;
    146 
    147     // Time Lapse parameters.
    148     private boolean mCaptureTimeLapse = false;
    149     // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
    150     private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
    151 
    152     boolean mPreviewing = false; // True if preview is started.
    153     // The display rotation in degrees. This is only valid when mPreviewing is
    154     // true.
    155     private int mDisplayRotation;
    156     private int mCameraDisplayOrientation;
    157 
    158     private int mDesiredPreviewWidth;
    159     private int mDesiredPreviewHeight;
    160     private ContentResolver mContentResolver;
    161 
    162     private LocationManager mLocationManager;
    163     private OrientationManager mOrientationManager;
    164 
    165     private int mPendingSwitchCameraId;
    166     private final Handler mHandler = new MainHandler();
    167     private VideoUI mUI;
    168     private CameraProxy mCameraDevice;
    169 
    170     // The degrees of the device rotated clockwise from its natural orientation.
    171     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
    172 
    173     private int mZoomValue;  // The current zoom value.
    174 
    175     private final MediaSaveService.OnMediaSavedListener mOnVideoSavedListener =
    176             new MediaSaveService.OnMediaSavedListener() {
    177                 @Override
    178                 public void onMediaSaved(Uri uri) {
    179                     if (uri != null) {
    180                         mCurrentVideoUri = uri;
    181                         mCurrentVideoUriFromMediaSaved = true;
    182                         onVideoSaved();
    183                         mActivity.notifyNewMedia(uri);
    184                     }
    185                 }
    186             };
    187 
    188     private final MediaSaveService.OnMediaSavedListener mOnPhotoSavedListener =
    189             new MediaSaveService.OnMediaSavedListener() {
    190                 @Override
    191                 public void onMediaSaved(Uri uri) {
    192                     if (uri != null) {
    193                         mActivity.notifyNewMedia(uri);
    194                     }
    195                 }
    196             };
    197 
    198 
    199     protected class CameraOpenThread extends Thread {
    200         @Override
    201         public void run() {
    202             openCamera();
    203         }
    204     }
    205 
    206     private void openCamera() {
    207         if (mCameraDevice == null) {
    208             mCameraDevice = CameraUtil.openCamera(
    209                     mActivity, mCameraId, mHandler,
    210                     mActivity.getCameraOpenErrorCallback());
    211         }
    212         if (mCameraDevice == null) {
    213             // Error.
    214             return;
    215         }
    216         mParameters = mCameraDevice.getParameters();
    217     }
    218 
    219     // This Handler is used to post message back onto the main thread of the
    220     // application
    221     private class MainHandler extends Handler {
    222         @Override
    223         public void handleMessage(Message msg) {
    224             switch (msg.what) {
    225 
    226                 case ENABLE_SHUTTER_BUTTON:
    227                     mUI.enableShutter(true);
    228                     break;
    229 
    230                 case CLEAR_SCREEN_DELAY: {
    231                     mActivity.getWindow().clearFlags(
    232                             WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    233                     break;
    234                 }
    235 
    236                 case UPDATE_RECORD_TIME: {
    237                     updateRecordingTime();
    238                     break;
    239                 }
    240 
    241                 case CHECK_DISPLAY_ROTATION: {
    242                     // Restart the preview if display rotation has changed.
    243                     // Sometimes this happens when the device is held upside
    244                     // down and camera app is opened. Rotation animation will
    245                     // take some time and the rotation value we have got may be
    246                     // wrong. Framework does not have a callback for this now.
    247                     if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
    248                             && !mMediaRecorderRecording && !mSwitchingCamera) {
    249                         startPreview();
    250                     }
    251                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
    252                         mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
    253                     }
    254                     break;
    255                 }
    256 
    257                 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
    258                     showTapToSnapshotToast();
    259                     break;
    260                 }
    261 
    262                 case SWITCH_CAMERA: {
    263                     switchCamera();
    264                     break;
    265                 }
    266 
    267                 case SWITCH_CAMERA_START_ANIMATION: {
    268                     //TODO:
    269                     //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
    270 
    271                     // Enable all camera controls.
    272                     mSwitchingCamera = false;
    273                     break;
    274                 }
    275 
    276                 default:
    277                     Log.v(TAG, "Unhandled message: " + msg.what);
    278                     break;
    279             }
    280         }
    281     }
    282 
    283     private BroadcastReceiver mReceiver = null;
    284 
    285     private class MyBroadcastReceiver extends BroadcastReceiver {
    286         @Override
    287         public void onReceive(Context context, Intent intent) {
    288             String action = intent.getAction();
    289             if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
    290                 stopVideoRecording();
    291             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
    292                 Toast.makeText(mActivity,
    293                         mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
    294             }
    295         }
    296     }
    297 
    298     private String createName(long dateTaken) {
    299         Date date = new Date(dateTaken);
    300         SimpleDateFormat dateFormat = new SimpleDateFormat(
    301                 mActivity.getString(R.string.video_file_name_format));
    302 
    303         return dateFormat.format(date);
    304     }
    305 
    306     private int getPreferredCameraId(ComboPreferences preferences) {
    307         int intentCameraId = CameraUtil.getCameraFacingIntentExtras(mActivity);
    308         if (intentCameraId != -1) {
    309             // Testing purpose. Launch a specific camera through the intent
    310             // extras.
    311             return intentCameraId;
    312         } else {
    313             return CameraSettings.readPreferredCameraId(preferences);
    314         }
    315     }
    316 
    317     private void initializeSurfaceView() {
    318         if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {  // API level < 16
    319             mUI.initializeSurfaceView();
    320         }
    321     }
    322 
    323     @Override
    324     public void init(CameraActivity activity, View root) {
    325         mActivity = activity;
    326         mUI = new VideoUI(activity, this, root);
    327         mPreferences = new ComboPreferences(mActivity);
    328         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
    329         mCameraId = getPreferredCameraId(mPreferences);
    330 
    331         mPreferences.setLocalId(mActivity, mCameraId);
    332         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
    333 
    334         mOrientationManager = new OrientationManager(mActivity);
    335 
    336         /*
    337          * To reduce startup time, we start the preview in another thread.
    338          * We make sure the preview is started at the end of onCreate.
    339          */
    340         CameraOpenThread cameraOpenThread = new CameraOpenThread();
    341         cameraOpenThread.start();
    342 
    343         mContentResolver = mActivity.getContentResolver();
    344 
    345         // Surface texture is from camera screen nail and startPreview needs it.
    346         // This must be done before startPreview.
    347         mIsVideoCaptureIntent = isVideoCaptureIntent();
    348         initializeSurfaceView();
    349 
    350         // Make sure camera device is opened.
    351         try {
    352             cameraOpenThread.join();
    353             if (mCameraDevice == null) {
    354                 return;
    355             }
    356         } catch (InterruptedException ex) {
    357             // ignore
    358         }
    359 
    360         readVideoPreferences();
    361         mUI.setPrefChangedListener(this);
    362 
    363         mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
    364         mLocationManager = new LocationManager(mActivity, null);
    365 
    366         mUI.setOrientationIndicator(0, false);
    367         setDisplayOrientation();
    368 
    369         mUI.showTimeLapseUI(mCaptureTimeLapse);
    370         initializeVideoSnapshot();
    371         resizeForPreviewAspectRatio();
    372 
    373         initializeVideoControl();
    374         mPendingSwitchCameraId = -1;
    375     }
    376 
    377     // SingleTapListener
    378     // Preview area is touched. Take a picture.
    379     @Override
    380     public void onSingleTapUp(View view, int x, int y) {
    381         takeASnapshot();
    382     }
    383 
    384     private void takeASnapshot() {
    385         // Only take snapshots if video snapshot is supported by device
    386         if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
    387             if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress) {
    388                 return;
    389             }
    390             MediaSaveService s = mActivity.getMediaSaveService();
    391             if (s == null || s.isQueueFull()) {
    392                 return;
    393             }
    394 
    395             // Set rotation and gps data.
    396             int rotation = CameraUtil.getJpegRotation(mCameraId, mOrientation);
    397             mParameters.setRotation(rotation);
    398             Location loc = mLocationManager.getCurrentLocation();
    399             CameraUtil.setGpsParameters(mParameters, loc);
    400             mCameraDevice.setParameters(mParameters);
    401 
    402             Log.v(TAG, "Video snapshot start");
    403             mCameraDevice.takePicture(mHandler,
    404                     null, null, null, new JpegPictureCallback(loc));
    405             showVideoSnapshotUI(true);
    406             mSnapshotInProgress = true;
    407             UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
    408                     UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
    409         }
    410     }
    411 
    412     @Override
    413     public void onStop() {}
    414 
    415     private void loadCameraPreferences() {
    416         CameraSettings settings = new CameraSettings(mActivity, mParameters,
    417                 mCameraId, CameraHolder.instance().getCameraInfo());
    418         // Remove the video quality preference setting when the quality is given in the intent.
    419         mPreferenceGroup = filterPreferenceScreenByIntent(
    420                 settings.getPreferenceGroup(R.xml.video_preferences));
    421     }
    422 
    423     private void initializeVideoControl() {
    424         loadCameraPreferences();
    425         mUI.initializePopup(mPreferenceGroup);
    426     }
    427 
    428     @Override
    429     public void onOrientationChanged(int orientation) {
    430         // We keep the last known orientation. So if the user first orient
    431         // the camera then point the camera to floor or sky, we still have
    432         // the correct orientation.
    433         if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
    434         int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
    435 
    436         if (mOrientation != newOrientation) {
    437             mOrientation = newOrientation;
    438         }
    439 
    440         // Show the toast after getting the first orientation changed.
    441         if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
    442             mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
    443             showTapToSnapshotToast();
    444         }
    445     }
    446 
    447     private void startPlayVideoActivity() {
    448         Intent intent = new Intent(Intent.ACTION_VIEW);
    449         intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
    450         try {
    451             mActivity
    452                     .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
    453         } catch (ActivityNotFoundException ex) {
    454             Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
    455         }
    456     }
    457 
    458     @Override
    459     @OnClickAttr
    460     public void onReviewPlayClicked(View v) {
    461         startPlayVideoActivity();
    462     }
    463 
    464     @Override
    465     @OnClickAttr
    466     public void onReviewDoneClicked(View v) {
    467         mIsInReviewMode = false;
    468         doReturnToCaller(true);
    469     }
    470 
    471     @Override
    472     @OnClickAttr
    473     public void onReviewCancelClicked(View v) {
    474         // TODO: It should be better to not even insert the URI at all before we
    475         // confirm done in review, which means we need to handle temporary video
    476         // files in a quite different way than we currently had.
    477         // Make sure we don't delete the Uri sent from the video capture intent.
    478         if (mCurrentVideoUriFromMediaSaved) {
    479             mContentResolver.delete(mCurrentVideoUri, null, null);
    480         }
    481         mIsInReviewMode = false;
    482         doReturnToCaller(false);
    483     }
    484 
    485     @Override
    486     public boolean isInReviewMode() {
    487         return mIsInReviewMode;
    488     }
    489 
    490     private void onStopVideoRecording() {
    491         boolean recordFail = stopVideoRecording();
    492         if (mIsVideoCaptureIntent) {
    493             if (mQuickCapture) {
    494                 doReturnToCaller(!recordFail);
    495             } else if (!recordFail) {
    496                 showCaptureResult();
    497             }
    498         } else if (!recordFail){
    499             // Start capture animation.
    500             if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
    501                 // The capture animation is disabled on ICS because we use SurfaceView
    502                 // for preview during recording. When the recording is done, we switch
    503                 // back to use SurfaceTexture for preview and we need to stop then start
    504                 // the preview. This will cause the preview flicker since the preview
    505                 // will not be continuous for a short period of time.
    506 
    507                 mUI.animateFlash();
    508                 mUI.animateCapture();
    509             }
    510         }
    511     }
    512 
    513     public void onVideoSaved() {
    514         if (mIsVideoCaptureIntent) {
    515             showCaptureResult();
    516         }
    517     }
    518 
    519     public void onProtectiveCurtainClick(View v) {
    520         // Consume clicks
    521     }
    522 
    523     @Override
    524     public void onShutterButtonClick() {
    525         if (mUI.collapseCameraControls() || mSwitchingCamera) return;
    526 
    527         boolean stop = mMediaRecorderRecording;
    528 
    529         if (stop) {
    530             onStopVideoRecording();
    531         } else {
    532             startVideoRecording();
    533         }
    534         mUI.enableShutter(false);
    535 
    536         // Keep the shutter button disabled when in video capture intent
    537         // mode and recording is stopped. It'll be re-enabled when
    538         // re-take button is clicked.
    539         if (!(mIsVideoCaptureIntent && stop)) {
    540             mHandler.sendEmptyMessageDelayed(
    541                     ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
    542         }
    543     }
    544 
    545     @Override
    546     public void onShutterButtonFocus(boolean pressed) {
    547         mUI.setShutterPressed(pressed);
    548     }
    549 
    550     private void readVideoPreferences() {
    551         // The preference stores values from ListPreference and is thus string type for all values.
    552         // We need to convert it to int manually.
    553         String videoQuality = mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
    554                         null);
    555         if (videoQuality == null) {
    556             // check for highest quality before setting default value
    557             videoQuality = CameraSettings.getSupportedHighestVideoQuality(mCameraId,
    558                     mActivity.getResources().getString(R.string.pref_video_quality_default));
    559             mPreferences.edit().putString(CameraSettings.KEY_VIDEO_QUALITY, videoQuality);
    560         }
    561         int quality = Integer.valueOf(videoQuality);
    562 
    563         // Set video quality.
    564         Intent intent = mActivity.getIntent();
    565         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
    566             int extraVideoQuality =
    567                     intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
    568             if (extraVideoQuality > 0) {
    569                 quality = CamcorderProfile.QUALITY_HIGH;
    570             } else {  // 0 is mms.
    571                 quality = CamcorderProfile.QUALITY_LOW;
    572             }
    573         }
    574 
    575         // Set video duration limit. The limit is read from the preference,
    576         // unless it is specified in the intent.
    577         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
    578             int seconds =
    579                     intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
    580             mMaxVideoDurationInMs = 1000 * seconds;
    581         } else {
    582             mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
    583         }
    584 
    585         // Read time lapse recording interval.
    586         String frameIntervalStr = mPreferences.getString(
    587                 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
    588                 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
    589         mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
    590         mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
    591         // TODO: This should be checked instead directly +1000.
    592         if (mCaptureTimeLapse) quality += 1000;
    593         mProfile = CamcorderProfile.get(mCameraId, quality);
    594         getDesiredPreviewSize();
    595         mPreferenceRead = true;
    596     }
    597 
    598     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    599     private void getDesiredPreviewSize() {
    600         if (mCameraDevice == null) {
    601             return;
    602         }
    603         mParameters = mCameraDevice.getParameters();
    604         if (mParameters.getSupportedVideoSizes() == null) {
    605             mDesiredPreviewWidth = mProfile.videoFrameWidth;
    606             mDesiredPreviewHeight = mProfile.videoFrameHeight;
    607         } else { // Driver supports separates outputs for preview and video.
    608             List<Size> sizes = mParameters.getSupportedPreviewSizes();
    609             Size preferred = mParameters.getPreferredPreviewSizeForVideo();
    610             int product = preferred.width * preferred.height;
    611             Iterator<Size> it = sizes.iterator();
    612             // Remove the preview sizes that are not preferred.
    613             while (it.hasNext()) {
    614                 Size size = it.next();
    615                 if (size.width * size.height > product) {
    616                     it.remove();
    617                 }
    618             }
    619             Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
    620                     (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
    621             mDesiredPreviewWidth = optimalSize.width;
    622             mDesiredPreviewHeight = optimalSize.height;
    623         }
    624         mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
    625         Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
    626                 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
    627     }
    628 
    629     private void resizeForPreviewAspectRatio() {
    630         mUI.setAspectRatio(
    631                 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
    632     }
    633 
    634     @Override
    635     public void installIntentFilter() {
    636         // install an intent filter to receive SD card related events.
    637         IntentFilter intentFilter =
    638                 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
    639         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    640         intentFilter.addDataScheme("file");
    641         mReceiver = new MyBroadcastReceiver();
    642         mActivity.registerReceiver(mReceiver, intentFilter);
    643     }
    644 
    645     @Override
    646     public void onResumeBeforeSuper() {
    647         mPaused = false;
    648     }
    649 
    650     @Override
    651     public void onResumeAfterSuper() {
    652         mUI.enableShutter(false);
    653         mZoomValue = 0;
    654 
    655         showVideoSnapshotUI(false);
    656 
    657         if (!mPreviewing) {
    658             openCamera();
    659             if (mCameraDevice == null) {
    660                 return;
    661             }
    662             readVideoPreferences();
    663             resizeForPreviewAspectRatio();
    664             startPreview();
    665         } else {
    666             // preview already started
    667             mUI.enableShutter(true);
    668         }
    669 
    670         mUI.initDisplayChangeListener();
    671         // Initializing it here after the preview is started.
    672         mUI.initializeZoom(mParameters);
    673 
    674         keepScreenOnAwhile();
    675 
    676         mOrientationManager.resume();
    677         // Initialize location service.
    678         boolean recordLocation = RecordLocationPreference.get(mPreferences,
    679                 mContentResolver);
    680         mLocationManager.recordLocation(recordLocation);
    681 
    682         if (mPreviewing) {
    683             mOnResumeTime = SystemClock.uptimeMillis();
    684             mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
    685         }
    686 
    687         UsageStatistics.onContentViewChanged(
    688                 UsageStatistics.COMPONENT_CAMERA, "VideoModule");
    689     }
    690 
    691     private void setDisplayOrientation() {
    692         mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
    693         mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
    694         // Change the camera display orientation
    695         if (mCameraDevice != null) {
    696             mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
    697         }
    698     }
    699 
    700     @Override
    701     public void updateCameraOrientation() {
    702         if (mMediaRecorderRecording) return;
    703         if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
    704             setDisplayOrientation();
    705         }
    706     }
    707 
    708     @Override
    709     public int onZoomChanged(int index) {
    710         // Not useful to change zoom value when the activity is paused.
    711         if (mPaused) return index;
    712         mZoomValue = index;
    713         if (mParameters == null || mCameraDevice == null) return index;
    714         // Set zoom parameters asynchronously
    715         mParameters.setZoom(mZoomValue);
    716         mCameraDevice.setParameters(mParameters);
    717         Parameters p = mCameraDevice.getParameters();
    718         if (p != null) return p.getZoom();
    719         return index;
    720     }
    721 
    722     private void startPreview() {
    723         Log.v(TAG, "startPreview");
    724 
    725         SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
    726         if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
    727                 mCameraDevice == null) {
    728             return;
    729         }
    730 
    731         mCameraDevice.setErrorCallback(mErrorCallback);
    732         if (mPreviewing == true) {
    733             stopPreview();
    734         }
    735 
    736         setDisplayOrientation();
    737         mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
    738         setCameraParameters();
    739 
    740         try {
    741             mCameraDevice.setPreviewTexture(surfaceTexture);
    742             mCameraDevice.startPreview();
    743             mPreviewing = true;
    744             onPreviewStarted();
    745         } catch (Throwable ex) {
    746             closeCamera();
    747             throw new RuntimeException("startPreview failed", ex);
    748         }
    749     }
    750 
    751     private void onPreviewStarted() {
    752         mUI.enableShutter(true);
    753     }
    754 
    755     @Override
    756     public void stopPreview() {
    757         if (!mPreviewing) return;
    758         mCameraDevice.stopPreview();
    759         mPreviewing = false;
    760     }
    761 
    762     private void closeCamera() {
    763         Log.v(TAG, "closeCamera");
    764         if (mCameraDevice == null) {
    765             Log.d(TAG, "already stopped.");
    766             return;
    767         }
    768         mCameraDevice.setZoomChangeListener(null);
    769         mCameraDevice.setErrorCallback(null);
    770         CameraHolder.instance().release();
    771         mCameraDevice = null;
    772         mPreviewing = false;
    773         mSnapshotInProgress = false;
    774     }
    775 
    776     private void releasePreviewResources() {
    777         if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
    778             mUI.hideSurfaceView();
    779         }
    780     }
    781 
    782     @Override
    783     public void onPauseBeforeSuper() {
    784         mPaused = true;
    785 
    786         mUI.showPreviewCover();
    787         if (mMediaRecorderRecording) {
    788             // Camera will be released in onStopVideoRecording.
    789             onStopVideoRecording();
    790         } else {
    791             closeCamera();
    792             releaseMediaRecorder();
    793         }
    794 
    795         closeVideoFileDescriptor();
    796 
    797 
    798         releasePreviewResources();
    799 
    800         if (mReceiver != null) {
    801             mActivity.unregisterReceiver(mReceiver);
    802             mReceiver = null;
    803         }
    804         resetScreenOn();
    805 
    806         if (mLocationManager != null) mLocationManager.recordLocation(false);
    807         mOrientationManager.pause();
    808 
    809         mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
    810         mHandler.removeMessages(SWITCH_CAMERA);
    811         mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
    812         mPendingSwitchCameraId = -1;
    813         mSwitchingCamera = false;
    814         mPreferenceRead = false;
    815 
    816         mUI.collapseCameraControls();
    817         mUI.removeDisplayChangeListener();
    818     }
    819 
    820     @Override
    821     public void onPauseAfterSuper() {
    822     }
    823 
    824     @Override
    825     public void onUserInteraction() {
    826         if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
    827             keepScreenOnAwhile();
    828         }
    829     }
    830 
    831     @Override
    832     public boolean onBackPressed() {
    833         if (mPaused) return true;
    834         if (mMediaRecorderRecording) {
    835             onStopVideoRecording();
    836             return true;
    837         } else if (mUI.hidePieRenderer()) {
    838             return true;
    839         } else {
    840             return mUI.removeTopLevelPopup();
    841         }
    842     }
    843 
    844     @Override
    845     public boolean onKeyDown(int keyCode, KeyEvent event) {
    846         // Do not handle any key if the activity is paused.
    847         if (mPaused) {
    848             return true;
    849         }
    850 
    851         switch (keyCode) {
    852             case KeyEvent.KEYCODE_CAMERA:
    853                 if (event.getRepeatCount() == 0) {
    854                     mUI.clickShutter();
    855                     return true;
    856                 }
    857                 break;
    858             case KeyEvent.KEYCODE_DPAD_CENTER:
    859                 if (event.getRepeatCount() == 0) {
    860                     mUI.clickShutter();
    861                     return true;
    862                 }
    863                 break;
    864             case KeyEvent.KEYCODE_MENU:
    865                 if (mMediaRecorderRecording) return true;
    866                 break;
    867         }
    868         return false;
    869     }
    870 
    871     @Override
    872     public boolean onKeyUp(int keyCode, KeyEvent event) {
    873         switch (keyCode) {
    874             case KeyEvent.KEYCODE_CAMERA:
    875                 mUI.pressShutter(false);
    876                 return true;
    877         }
    878         return false;
    879     }
    880 
    881     @Override
    882     public boolean isVideoCaptureIntent() {
    883         String action = mActivity.getIntent().getAction();
    884         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
    885     }
    886 
    887     private void doReturnToCaller(boolean valid) {
    888         Intent resultIntent = new Intent();
    889         int resultCode;
    890         if (valid) {
    891             resultCode = Activity.RESULT_OK;
    892             resultIntent.setData(mCurrentVideoUri);
    893         } else {
    894             resultCode = Activity.RESULT_CANCELED;
    895         }
    896         mActivity.setResultEx(resultCode, resultIntent);
    897         mActivity.finish();
    898     }
    899 
    900     private void cleanupEmptyFile() {
    901         if (mVideoFilename != null) {
    902             File f = new File(mVideoFilename);
    903             if (f.length() == 0 && f.delete()) {
    904                 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
    905                 mVideoFilename = null;
    906             }
    907         }
    908     }
    909 
    910     private void setupMediaRecorderPreviewDisplay() {
    911         // Nothing to do here if using SurfaceTexture.
    912         if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
    913             // We stop the preview here before unlocking the device because we
    914             // need to change the SurfaceTexture to SurfaceView for preview.
    915             stopPreview();
    916             mCameraDevice.setPreviewDisplay(mUI.getSurfaceHolder());
    917             // The orientation for SurfaceTexture is different from that for
    918             // SurfaceView. For SurfaceTexture we don't need to consider the
    919             // display rotation. Just consider the sensor's orientation and we
    920             // will set the orientation correctly when showing the texture.
    921             // Gallery will handle the orientation for the preview. For
    922             // SurfaceView we will have to take everything into account so the
    923             // display rotation is considered.
    924             mCameraDevice.setDisplayOrientation(
    925                     CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId));
    926             mCameraDevice.startPreview();
    927             mPreviewing = true;
    928             mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
    929         }
    930     }
    931 
    932     // Prepares media recorder.
    933     private void initializeRecorder() {
    934         Log.v(TAG, "initializeRecorder");
    935         // If the mCameraDevice is null, then this activity is going to finish
    936         if (mCameraDevice == null) return;
    937 
    938         if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
    939             // Set the SurfaceView to visible so the surface gets created.
    940             // surfaceCreated() is called immediately when the visibility is
    941             // changed to visible. Thus, mSurfaceViewReady should become true
    942             // right after calling setVisibility().
    943             mUI.showSurfaceView();
    944         }
    945 
    946         Intent intent = mActivity.getIntent();
    947         Bundle myExtras = intent.getExtras();
    948 
    949         long requestedSizeLimit = 0;
    950         closeVideoFileDescriptor();
    951         mCurrentVideoUriFromMediaSaved = false;
    952         if (mIsVideoCaptureIntent && myExtras != null) {
    953             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
    954             if (saveUri != null) {
    955                 try {
    956                     mVideoFileDescriptor =
    957                             mContentResolver.openFileDescriptor(saveUri, "rw");
    958                     mCurrentVideoUri = saveUri;
    959                 } catch (java.io.FileNotFoundException ex) {
    960                     // invalid uri
    961                     Log.e(TAG, ex.toString());
    962                 }
    963             }
    964             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
    965         }
    966         mMediaRecorder = new MediaRecorder();
    967 
    968         // Unlock the camera object before passing it to media recorder.
    969         mCameraDevice.unlock();
    970         mMediaRecorder.setCamera(mCameraDevice.getCamera());
    971         if (!mCaptureTimeLapse) {
    972             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    973         }
    974         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    975         mMediaRecorder.setProfile(mProfile);
    976         mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
    977         mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
    978         if (mCaptureTimeLapse) {
    979             double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
    980             setCaptureRate(mMediaRecorder, fps);
    981         }
    982 
    983         setRecordLocation();
    984 
    985         // Set output file.
    986         // Try Uri in the intent first. If it doesn't exist, use our own
    987         // instead.
    988         if (mVideoFileDescriptor != null) {
    989             mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
    990         } else {
    991             generateVideoFilename(mProfile.fileFormat);
    992             mMediaRecorder.setOutputFile(mVideoFilename);
    993         }
    994 
    995         // Set maximum file size.
    996         long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
    997         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
    998             maxFileSize = requestedSizeLimit;
    999         }
   1000 
   1001         try {
   1002             mMediaRecorder.setMaxFileSize(maxFileSize);
   1003         } catch (RuntimeException exception) {
   1004             // We are going to ignore failure of setMaxFileSize here, as
   1005             // a) The composer selected may simply not support it, or
   1006             // b) The underlying media framework may not handle 64-bit range
   1007             // on the size restriction.
   1008         }
   1009 
   1010         // See android.hardware.Camera.Parameters.setRotation for
   1011         // documentation.
   1012         // Note that mOrientation here is the device orientation, which is the opposite of
   1013         // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
   1014         // which is the orientation the graphics need to rotate in order to render correctly.
   1015         int rotation = 0;
   1016         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
   1017             CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
   1018             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
   1019                 rotation = (info.orientation - mOrientation + 360) % 360;
   1020             } else {  // back-facing camera
   1021                 rotation = (info.orientation + mOrientation) % 360;
   1022             }
   1023         }
   1024         mMediaRecorder.setOrientationHint(rotation);
   1025         setupMediaRecorderPreviewDisplay();
   1026 
   1027         try {
   1028             mMediaRecorder.prepare();
   1029         } catch (IOException e) {
   1030             Log.e(TAG, "prepare failed for " + mVideoFilename, e);
   1031             releaseMediaRecorder();
   1032             throw new RuntimeException(e);
   1033         }
   1034 
   1035         mMediaRecorder.setOnErrorListener(this);
   1036         mMediaRecorder.setOnInfoListener(this);
   1037     }
   1038 
   1039     private static void setCaptureRate(MediaRecorder recorder, double fps) {
   1040         recorder.setCaptureRate(fps);
   1041     }
   1042 
   1043     private void setRecordLocation() {
   1044         Location loc = mLocationManager.getCurrentLocation();
   1045         if (loc != null) {
   1046             mMediaRecorder.setLocation((float) loc.getLatitude(),
   1047                     (float) loc.getLongitude());
   1048         }
   1049     }
   1050 
   1051     private void releaseMediaRecorder() {
   1052         Log.v(TAG, "Releasing media recorder.");
   1053         if (mMediaRecorder != null) {
   1054             cleanupEmptyFile();
   1055             mMediaRecorder.reset();
   1056             mMediaRecorder.release();
   1057             mMediaRecorder = null;
   1058         }
   1059         mVideoFilename = null;
   1060     }
   1061 
   1062     private void generateVideoFilename(int outputFileFormat) {
   1063         long dateTaken = System.currentTimeMillis();
   1064         String title = createName(dateTaken);
   1065         // Used when emailing.
   1066         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
   1067         String mime = convertOutputFormatToMimeType(outputFileFormat);
   1068         String path = Storage.DIRECTORY + '/' + filename;
   1069         String tmpPath = path + ".tmp";
   1070         mCurrentVideoValues = new ContentValues(9);
   1071         mCurrentVideoValues.put(Video.Media.TITLE, title);
   1072         mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
   1073         mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
   1074         mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
   1075         mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
   1076         mCurrentVideoValues.put(Video.Media.DATA, path);
   1077         mCurrentVideoValues.put(Video.Media.RESOLUTION,
   1078                 Integer.toString(mProfile.videoFrameWidth) + "x" +
   1079                 Integer.toString(mProfile.videoFrameHeight));
   1080         Location loc = mLocationManager.getCurrentLocation();
   1081         if (loc != null) {
   1082             mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
   1083             mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
   1084         }
   1085         mVideoFilename = tmpPath;
   1086         Log.v(TAG, "New video filename: " + mVideoFilename);
   1087     }
   1088 
   1089     private void saveVideo() {
   1090         if (mVideoFileDescriptor == null) {
   1091             long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
   1092             if (duration > 0) {
   1093                 if (mCaptureTimeLapse) {
   1094                     duration = getTimeLapseVideoLength(duration);
   1095                 }
   1096             } else {
   1097                 Log.w(TAG, "Video duration <= 0 : " + duration);
   1098             }
   1099             mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename,
   1100                     duration, mCurrentVideoValues,
   1101                     mOnVideoSavedListener, mContentResolver);
   1102         }
   1103         mCurrentVideoValues = null;
   1104     }
   1105 
   1106     private void deleteVideoFile(String fileName) {
   1107         Log.v(TAG, "Deleting video " + fileName);
   1108         File f = new File(fileName);
   1109         if (!f.delete()) {
   1110             Log.v(TAG, "Could not delete " + fileName);
   1111         }
   1112     }
   1113 
   1114     private PreferenceGroup filterPreferenceScreenByIntent(
   1115             PreferenceGroup screen) {
   1116         Intent intent = mActivity.getIntent();
   1117         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
   1118             CameraSettings.removePreferenceFromScreen(screen,
   1119                     CameraSettings.KEY_VIDEO_QUALITY);
   1120         }
   1121 
   1122         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
   1123             CameraSettings.removePreferenceFromScreen(screen,
   1124                     CameraSettings.KEY_VIDEO_QUALITY);
   1125         }
   1126         return screen;
   1127     }
   1128 
   1129     // from MediaRecorder.OnErrorListener
   1130     @Override
   1131     public void onError(MediaRecorder mr, int what, int extra) {
   1132         Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
   1133         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
   1134             // We may have run out of space on the sdcard.
   1135             stopVideoRecording();
   1136             mActivity.updateStorageSpaceAndHint();
   1137         }
   1138     }
   1139 
   1140     // from MediaRecorder.OnInfoListener
   1141     @Override
   1142     public void onInfo(MediaRecorder mr, int what, int extra) {
   1143         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
   1144             if (mMediaRecorderRecording) onStopVideoRecording();
   1145         } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
   1146             if (mMediaRecorderRecording) onStopVideoRecording();
   1147 
   1148             // Show the toast.
   1149             Toast.makeText(mActivity, R.string.video_reach_size_limit,
   1150                     Toast.LENGTH_LONG).show();
   1151         }
   1152     }
   1153 
   1154     /*
   1155      * Make sure we're not recording music playing in the background, ask the
   1156      * MediaPlaybackService to pause playback.
   1157      */
   1158     private void pauseAudioPlayback() {
   1159         // Shamelessly copied from MediaPlaybackService.java, which
   1160         // should be public, but isn't.
   1161         Intent i = new Intent("com.android.music.musicservicecommand");
   1162         i.putExtra("command", "pause");
   1163 
   1164         mActivity.sendBroadcast(i);
   1165     }
   1166 
   1167     // For testing.
   1168     public boolean isRecording() {
   1169         return mMediaRecorderRecording;
   1170     }
   1171 
   1172     private void startVideoRecording() {
   1173         Log.v(TAG, "startVideoRecording");
   1174         mUI.cancelAnimations();
   1175         mUI.setSwipingEnabled(false);
   1176 
   1177         mActivity.updateStorageSpaceAndHint();
   1178         if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
   1179             Log.v(TAG, "Storage issue, ignore the start request");
   1180             return;
   1181         }
   1182 
   1183         //??
   1184         //if (!mCameraDevice.waitDone()) return;
   1185         mCurrentVideoUri = null;
   1186 
   1187         initializeRecorder();
   1188         if (mMediaRecorder == null) {
   1189             Log.e(TAG, "Fail to initialize media recorder");
   1190             return;
   1191         }
   1192 
   1193         pauseAudioPlayback();
   1194 
   1195         try {
   1196             mMediaRecorder.start(); // Recording is now started
   1197         } catch (RuntimeException e) {
   1198             Log.e(TAG, "Could not start media recorder. ", e);
   1199             releaseMediaRecorder();
   1200             // If start fails, frameworks will not lock the camera for us.
   1201             mCameraDevice.lock();
   1202             return;
   1203         }
   1204 
   1205         // Make sure the video recording has started before announcing
   1206         // this in accessibility.
   1207         AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
   1208                 mActivity.getString(R.string.video_recording_started));
   1209 
   1210         // The parameters might have been altered by MediaRecorder already.
   1211         // We need to force mCameraDevice to refresh before getting it.
   1212         mCameraDevice.refreshParameters();
   1213         // The parameters may have been changed by MediaRecorder upon starting
   1214         // recording. We need to alter the parameters if we support camcorder
   1215         // zoom. To reduce latency when setting the parameters during zoom, we
   1216         // update mParameters here once.
   1217         mParameters = mCameraDevice.getParameters();
   1218 
   1219         mUI.enableCameraControls(false);
   1220 
   1221         mMediaRecorderRecording = true;
   1222         mOrientationManager.lockOrientation();
   1223         mRecordingStartTime = SystemClock.uptimeMillis();
   1224         mUI.showRecordingUI(true);
   1225 
   1226         updateRecordingTime();
   1227         keepScreenOn();
   1228         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
   1229                 UsageStatistics.ACTION_CAPTURE_START, "Video");
   1230     }
   1231 
   1232     private Bitmap getVideoThumbnail() {
   1233         Bitmap bitmap = null;
   1234         if (mVideoFileDescriptor != null) {
   1235             bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
   1236                     mDesiredPreviewWidth);
   1237         } else if (mCurrentVideoUri != null) {
   1238             try {
   1239                 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
   1240                 bitmap = Thumbnail.createVideoThumbnailBitmap(
   1241                         mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
   1242             } catch (java.io.FileNotFoundException ex) {
   1243                 // invalid uri
   1244                 Log.e(TAG, ex.toString());
   1245             }
   1246         }
   1247 
   1248         if (bitmap != null) {
   1249             // MetadataRetriever already rotates the thumbnail. We should rotate
   1250             // it to match the UI orientation (and mirror if it is front-facing camera).
   1251             CameraInfo[] info = CameraHolder.instance().getCameraInfo();
   1252             boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
   1253             bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
   1254         }
   1255         return bitmap;
   1256     }
   1257 
   1258     private void showCaptureResult() {
   1259         mIsInReviewMode = true;
   1260         Bitmap bitmap = getVideoThumbnail();
   1261         if (bitmap != null) {
   1262             mUI.showReviewImage(bitmap);
   1263         }
   1264         mUI.showReviewControls();
   1265         mUI.enableCameraControls(false);
   1266         mUI.showTimeLapseUI(false);
   1267     }
   1268 
   1269     private boolean stopVideoRecording() {
   1270         Log.v(TAG, "stopVideoRecording");
   1271         mUI.setSwipingEnabled(true);
   1272         if (!isVideoCaptureIntent()) {
   1273             mUI.showSwitcher();
   1274         }
   1275 
   1276         boolean fail = false;
   1277         if (mMediaRecorderRecording) {
   1278             boolean shouldAddToMediaStoreNow = false;
   1279 
   1280             try {
   1281                 mMediaRecorder.setOnErrorListener(null);
   1282                 mMediaRecorder.setOnInfoListener(null);
   1283                 mMediaRecorder.stop();
   1284                 shouldAddToMediaStoreNow = true;
   1285                 mCurrentVideoFilename = mVideoFilename;
   1286                 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
   1287                         + mCurrentVideoFilename);
   1288                 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
   1289                         mActivity.getString(R.string.video_recording_stopped));
   1290             } catch (RuntimeException e) {
   1291                 Log.e(TAG, "stop fail",  e);
   1292                 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
   1293                 fail = true;
   1294             }
   1295             mMediaRecorderRecording = false;
   1296             mOrientationManager.unlockOrientation();
   1297 
   1298             // If the activity is paused, this means activity is interrupted
   1299             // during recording. Release the camera as soon as possible because
   1300             // face unlock or other applications may need to use the camera.
   1301             if (mPaused) {
   1302                 closeCamera();
   1303             }
   1304 
   1305             mUI.showRecordingUI(false);
   1306             if (!mIsVideoCaptureIntent) {
   1307                 mUI.enableCameraControls(true);
   1308             }
   1309             // The orientation was fixed during video recording. Now make it
   1310             // reflect the device orientation as video recording is stopped.
   1311             mUI.setOrientationIndicator(0, true);
   1312             keepScreenOnAwhile();
   1313             if (shouldAddToMediaStoreNow && !fail) {
   1314                 if (mVideoFileDescriptor == null) {
   1315                     saveVideo();
   1316                 } else if (mIsVideoCaptureIntent) {
   1317                     // if no file save is needed, we can show the post capture UI now
   1318                     showCaptureResult();
   1319                 }
   1320             }
   1321         }
   1322         // release media recorder
   1323         releaseMediaRecorder();
   1324         if (!mPaused) {
   1325             mCameraDevice.lock();
   1326             if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
   1327                 stopPreview();
   1328                 mUI.hideSurfaceView();
   1329                 // Switch back to use SurfaceTexture for preview.
   1330                 startPreview();
   1331             }
   1332         }
   1333         // Update the parameters here because the parameters might have been altered
   1334         // by MediaRecorder.
   1335         if (!mPaused) mParameters = mCameraDevice.getParameters();
   1336         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
   1337                 fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
   1338                     UsageStatistics.ACTION_CAPTURE_DONE, "Video",
   1339                     SystemClock.uptimeMillis() - mRecordingStartTime);
   1340         return fail;
   1341     }
   1342 
   1343     private void resetScreenOn() {
   1344         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1345         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1346     }
   1347 
   1348     private void keepScreenOnAwhile() {
   1349         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1350         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1351         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
   1352     }
   1353 
   1354     private void keepScreenOn() {
   1355         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1356         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1357     }
   1358 
   1359     private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
   1360         long seconds = milliSeconds / 1000; // round down to compute seconds
   1361         long minutes = seconds / 60;
   1362         long hours = minutes / 60;
   1363         long remainderMinutes = minutes - (hours * 60);
   1364         long remainderSeconds = seconds - (minutes * 60);
   1365 
   1366         StringBuilder timeStringBuilder = new StringBuilder();
   1367 
   1368         // Hours
   1369         if (hours > 0) {
   1370             if (hours < 10) {
   1371                 timeStringBuilder.append('0');
   1372             }
   1373             timeStringBuilder.append(hours);
   1374 
   1375             timeStringBuilder.append(':');
   1376         }
   1377 
   1378         // Minutes
   1379         if (remainderMinutes < 10) {
   1380             timeStringBuilder.append('0');
   1381         }
   1382         timeStringBuilder.append(remainderMinutes);
   1383         timeStringBuilder.append(':');
   1384 
   1385         // Seconds
   1386         if (remainderSeconds < 10) {
   1387             timeStringBuilder.append('0');
   1388         }
   1389         timeStringBuilder.append(remainderSeconds);
   1390 
   1391         // Centi seconds
   1392         if (displayCentiSeconds) {
   1393             timeStringBuilder.append('.');
   1394             long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
   1395             if (remainderCentiSeconds < 10) {
   1396                 timeStringBuilder.append('0');
   1397             }
   1398             timeStringBuilder.append(remainderCentiSeconds);
   1399         }
   1400 
   1401         return timeStringBuilder.toString();
   1402     }
   1403 
   1404     private long getTimeLapseVideoLength(long deltaMs) {
   1405         // For better approximation calculate fractional number of frames captured.
   1406         // This will update the video time at a higher resolution.
   1407         double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
   1408         return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
   1409     }
   1410 
   1411     private void updateRecordingTime() {
   1412         if (!mMediaRecorderRecording) {
   1413             return;
   1414         }
   1415         long now = SystemClock.uptimeMillis();
   1416         long delta = now - mRecordingStartTime;
   1417 
   1418         // Starting a minute before reaching the max duration
   1419         // limit, we'll countdown the remaining time instead.
   1420         boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
   1421                 && delta >= mMaxVideoDurationInMs - 60000);
   1422 
   1423         long deltaAdjusted = delta;
   1424         if (countdownRemainingTime) {
   1425             deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
   1426         }
   1427         String text;
   1428 
   1429         long targetNextUpdateDelay;
   1430         if (!mCaptureTimeLapse) {
   1431             text = millisecondToTimeString(deltaAdjusted, false);
   1432             targetNextUpdateDelay = 1000;
   1433         } else {
   1434             // The length of time lapse video is different from the length
   1435             // of the actual wall clock time elapsed. Display the video length
   1436             // only in format hh:mm:ss.dd, where dd are the centi seconds.
   1437             text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
   1438             targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
   1439         }
   1440 
   1441         mUI.setRecordingTime(text);
   1442 
   1443         if (mRecordingTimeCountsDown != countdownRemainingTime) {
   1444             // Avoid setting the color on every update, do it only
   1445             // when it needs changing.
   1446             mRecordingTimeCountsDown = countdownRemainingTime;
   1447 
   1448             int color = mActivity.getResources().getColor(countdownRemainingTime
   1449                     ? R.color.recording_time_remaining_text
   1450                     : R.color.recording_time_elapsed_text);
   1451 
   1452             mUI.setRecordingTimeTextColor(color);
   1453         }
   1454 
   1455         long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
   1456         mHandler.sendEmptyMessageDelayed(
   1457                 UPDATE_RECORD_TIME, actualNextUpdateDelay);
   1458     }
   1459 
   1460     private static boolean isSupported(String value, List<String> supported) {
   1461         return supported == null ? false : supported.indexOf(value) >= 0;
   1462     }
   1463 
   1464     @SuppressWarnings("deprecation")
   1465     private void setCameraParameters() {
   1466         mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
   1467         mParameters.set("video-size", mProfile.videoFrameWidth+"x"+mProfile.videoFrameHeight);
   1468         int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
   1469         if (fpsRange.length > 0) {
   1470             mParameters.setPreviewFpsRange(
   1471                     fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
   1472                     fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
   1473         } else {
   1474             mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
   1475         }
   1476 
   1477         forceFlashOffIfSupported(!mUI.isVisible());
   1478 
   1479         // Set white balance parameter.
   1480         String whiteBalance = mPreferences.getString(
   1481                 CameraSettings.KEY_WHITE_BALANCE,
   1482                 mActivity.getString(R.string.pref_camera_whitebalance_default));
   1483         if (isSupported(whiteBalance,
   1484                 mParameters.getSupportedWhiteBalance())) {
   1485             mParameters.setWhiteBalance(whiteBalance);
   1486         } else {
   1487             whiteBalance = mParameters.getWhiteBalance();
   1488             if (whiteBalance == null) {
   1489                 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
   1490             }
   1491         }
   1492 
   1493         // Set zoom.
   1494         if (mParameters.isZoomSupported()) {
   1495             mParameters.setZoom(mZoomValue);
   1496         }
   1497 
   1498         // Set continuous autofocus.
   1499         List<String> supportedFocus = mParameters.getSupportedFocusModes();
   1500         if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
   1501             mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
   1502         }
   1503 
   1504         mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
   1505 
   1506         // Enable video stabilization. Convenience methods not available in API
   1507         // level <= 14
   1508         String vstabSupported = mParameters.get("video-stabilization-supported");
   1509         if ("true".equals(vstabSupported)) {
   1510             mParameters.set("video-stabilization", "true");
   1511         }
   1512 
   1513         // Set picture size.
   1514         // The logic here is different from the logic in still-mode camera.
   1515         // There we determine the preview size based on the picture size, but
   1516         // here we determine the picture size based on the preview size.
   1517         List<Size> supported = mParameters.getSupportedPictureSizes();
   1518         Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
   1519                 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
   1520         Size original = mParameters.getPictureSize();
   1521         if (!original.equals(optimalSize)) {
   1522             mParameters.setPictureSize(optimalSize.width, optimalSize.height);
   1523         }
   1524         Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
   1525                 optimalSize.height);
   1526 
   1527         // Set JPEG quality.
   1528         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
   1529                 CameraProfile.QUALITY_HIGH);
   1530         mParameters.setJpegQuality(jpegQuality);
   1531 
   1532         boolean flag = false;
   1533         if (mPreviewing) {
   1534             stopPreview();
   1535             flag = true;
   1536         }
   1537         mCameraDevice.setParameters(mParameters);
   1538         if (flag) {
   1539             startPreview();
   1540         }
   1541         // Keep preview size up to date.
   1542         mParameters = mCameraDevice.getParameters();
   1543 
   1544         // Update UI based on the new parameters.
   1545         mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1546     }
   1547 
   1548     @Override
   1549     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1550         // Do nothing.
   1551     }
   1552 
   1553     @Override
   1554     public void onConfigurationChanged(Configuration newConfig) {
   1555         Log.v(TAG, "onConfigurationChanged");
   1556         setDisplayOrientation();
   1557     }
   1558 
   1559     @Override
   1560     public void onOverriddenPreferencesClicked() {
   1561     }
   1562 
   1563     @Override
   1564     // TODO: Delete this after old camera code is removed
   1565     public void onRestorePreferencesClicked() {
   1566     }
   1567 
   1568     @Override
   1569     public void onSharedPreferenceChanged() {
   1570         // ignore the events after "onPause()" or preview has not started yet
   1571         if (mPaused) {
   1572             return;
   1573         }
   1574         synchronized (mPreferences) {
   1575             // If mCameraDevice is not ready then we can set the parameter in
   1576             // startPreview().
   1577             if (mCameraDevice == null) return;
   1578 
   1579             boolean recordLocation = RecordLocationPreference.get(
   1580                     mPreferences, mContentResolver);
   1581             mLocationManager.recordLocation(recordLocation);
   1582 
   1583             readVideoPreferences();
   1584             mUI.showTimeLapseUI(mCaptureTimeLapse);
   1585             // We need to restart the preview if preview size is changed.
   1586             Size size = mParameters.getPreviewSize();
   1587             if (size.width != mDesiredPreviewWidth
   1588                     || size.height != mDesiredPreviewHeight) {
   1589 
   1590                 stopPreview();
   1591                 resizeForPreviewAspectRatio();
   1592                 startPreview(); // Parameters will be set in startPreview().
   1593             } else {
   1594                 setCameraParameters();
   1595             }
   1596             mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1597         }
   1598     }
   1599 
   1600     protected void setCameraId(int cameraId) {
   1601         ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
   1602         pref.setValue("" + cameraId);
   1603     }
   1604 
   1605     private void switchCamera() {
   1606         if (mPaused)  {
   1607             return;
   1608         }
   1609 
   1610         Log.d(TAG, "Start to switch camera.");
   1611         mCameraId = mPendingSwitchCameraId;
   1612         mPendingSwitchCameraId = -1;
   1613         setCameraId(mCameraId);
   1614 
   1615         closeCamera();
   1616         mUI.collapseCameraControls();
   1617         // Restart the camera and initialize the UI. From onCreate.
   1618         mPreferences.setLocalId(mActivity, mCameraId);
   1619         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
   1620         openCamera();
   1621         readVideoPreferences();
   1622         startPreview();
   1623         initializeVideoSnapshot();
   1624         resizeForPreviewAspectRatio();
   1625         initializeVideoControl();
   1626 
   1627         // From onResume
   1628         mZoomValue = 0;
   1629         mUI.initializeZoom(mParameters);
   1630         mUI.setOrientationIndicator(0, false);
   1631 
   1632         // Start switch camera animation. Post a message because
   1633         // onFrameAvailable from the old camera may already exist.
   1634         mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
   1635         mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1636     }
   1637 
   1638     // Preview texture has been copied. Now camera can be released and the
   1639     // animation can be started.
   1640     @Override
   1641     public void onPreviewTextureCopied() {
   1642         mHandler.sendEmptyMessage(SWITCH_CAMERA);
   1643     }
   1644 
   1645     @Override
   1646     public void onCaptureTextureCopied() {
   1647     }
   1648 
   1649     private void initializeVideoSnapshot() {
   1650         if (mParameters == null) return;
   1651         if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
   1652             // Show the tap to focus toast if this is the first start.
   1653             if (mPreferences.getBoolean(
   1654                         CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
   1655                 // Delay the toast for one second to wait for orientation.
   1656                 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
   1657             }
   1658         }
   1659     }
   1660 
   1661     void showVideoSnapshotUI(boolean enabled) {
   1662         if (mParameters == null) return;
   1663         if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
   1664             if (enabled) {
   1665                 mUI.animateFlash();
   1666                 mUI.animateCapture();
   1667             } else {
   1668                 mUI.showPreviewBorder(enabled);
   1669             }
   1670             mUI.enableShutter(!enabled);
   1671         }
   1672     }
   1673 
   1674     private void forceFlashOffIfSupported(boolean forceOff) {
   1675         String flashMode;
   1676         if (!forceOff) {
   1677             flashMode = mPreferences.getString(
   1678                     CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
   1679                     mActivity.getString(R.string.pref_camera_video_flashmode_default));
   1680         } else {
   1681             flashMode = Parameters.FLASH_MODE_OFF;
   1682         }
   1683         List<String> supportedFlash = mParameters.getSupportedFlashModes();
   1684         if (isSupported(flashMode, supportedFlash)) {
   1685             mParameters.setFlashMode(flashMode);
   1686         } else {
   1687             flashMode = mParameters.getFlashMode();
   1688             if (flashMode == null) {
   1689                 flashMode = mActivity.getString(
   1690                         R.string.pref_camera_flashmode_no_flash);
   1691             }
   1692         }
   1693     }
   1694 
   1695     /**
   1696      * Used to update the flash mode. Video mode can turn on the flash as torch
   1697      * mode, which we would like to turn on and off when we switching in and
   1698      * out to the preview.
   1699      *
   1700      * @param forceOff whether we want to force the flash off.
   1701      */
   1702     private void forceFlashOff(boolean forceOff) {
   1703         if (!mPreviewing || mParameters.getFlashMode() == null) {
   1704             return;
   1705         }
   1706         forceFlashOffIfSupported(forceOff);
   1707         mCameraDevice.setParameters(mParameters);
   1708         mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1709     }
   1710 
   1711     @Override
   1712     public void onPreviewFocusChanged(boolean previewFocused) {
   1713         mUI.onPreviewFocusChanged(previewFocused);
   1714         forceFlashOff(!previewFocused);
   1715     }
   1716 
   1717     @Override
   1718     public boolean arePreviewControlsVisible() {
   1719         return mUI.arePreviewControlsVisible();
   1720     }
   1721 
   1722     private final class JpegPictureCallback implements CameraPictureCallback {
   1723         Location mLocation;
   1724 
   1725         public JpegPictureCallback(Location loc) {
   1726             mLocation = loc;
   1727         }
   1728 
   1729         @Override
   1730         public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
   1731             Log.v(TAG, "onPictureTaken");
   1732             mSnapshotInProgress = false;
   1733             showVideoSnapshotUI(false);
   1734             storeImage(jpegData, mLocation);
   1735         }
   1736     }
   1737 
   1738     private void storeImage(final byte[] data, Location loc) {
   1739         long dateTaken = System.currentTimeMillis();
   1740         String title = CameraUtil.createJpegName(dateTaken);
   1741         ExifInterface exif = Exif.getExif(data);
   1742         int orientation = Exif.getOrientation(exif);
   1743 
   1744         mActivity.getMediaSaveService().addImage(
   1745                 data, title, dateTaken, loc, orientation,
   1746                 exif, mOnPhotoSavedListener, mContentResolver);
   1747     }
   1748 
   1749     private String convertOutputFormatToMimeType(int outputFileFormat) {
   1750         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   1751             return "video/mp4";
   1752         }
   1753         return "video/3gpp";
   1754     }
   1755 
   1756     private String convertOutputFormatToFileExt(int outputFileFormat) {
   1757         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   1758             return ".mp4";
   1759         }
   1760         return ".3gp";
   1761     }
   1762 
   1763     private void closeVideoFileDescriptor() {
   1764         if (mVideoFileDescriptor != null) {
   1765             try {
   1766                 mVideoFileDescriptor.close();
   1767             } catch (IOException e) {
   1768                 Log.e(TAG, "Fail to close fd", e);
   1769             }
   1770             mVideoFileDescriptor = null;
   1771         }
   1772     }
   1773 
   1774     private void showTapToSnapshotToast() {
   1775         new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
   1776                 .show();
   1777         // Clear the preference.
   1778         Editor editor = mPreferences.edit();
   1779         editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
   1780         editor.apply();
   1781     }
   1782 
   1783     @Override
   1784     public boolean updateStorageHintOnResume() {
   1785         return true;
   1786     }
   1787 
   1788     // required by OnPreferenceChangedListener
   1789     @Override
   1790     public void onCameraPickerClicked(int cameraId) {
   1791         if (mPaused || mPendingSwitchCameraId != -1) return;
   1792 
   1793         mPendingSwitchCameraId = cameraId;
   1794         Log.d(TAG, "Start to copy texture.");
   1795         // We need to keep a preview frame for the animation before
   1796         // releasing the camera. This will trigger onPreviewTextureCopied.
   1797         // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
   1798         // Disable all camera controls.
   1799         mSwitchingCamera = true;
   1800         switchCamera();
   1801 
   1802     }
   1803 
   1804     @Override
   1805     public void onShowSwitcherPopup() {
   1806         mUI.onShowSwitcherPopup();
   1807     }
   1808 
   1809     @Override
   1810     public void onMediaSaveServiceConnected(MediaSaveService s) {
   1811         // do nothing.
   1812     }
   1813 
   1814     @Override
   1815     public void onPreviewUIReady() {
   1816         startPreview();
   1817     }
   1818 
   1819     @Override
   1820     public void onPreviewUIDestroyed() {
   1821         stopPreview();
   1822     }
   1823 }
   1824