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         setupMediaRecorderPreviewDisplay();
    969         // Unlock the camera object before passing it to media recorder.
    970         mCameraDevice.unlock();
    971         mMediaRecorder.setCamera(mCameraDevice.getCamera());
    972         if (!mCaptureTimeLapse) {
    973             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    974         }
    975         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    976         mMediaRecorder.setProfile(mProfile);
    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 
   1026         try {
   1027             mMediaRecorder.prepare();
   1028         } catch (IOException e) {
   1029             Log.e(TAG, "prepare failed for " + mVideoFilename, e);
   1030             releaseMediaRecorder();
   1031             throw new RuntimeException(e);
   1032         }
   1033 
   1034         mMediaRecorder.setOnErrorListener(this);
   1035         mMediaRecorder.setOnInfoListener(this);
   1036     }
   1037 
   1038     private static void setCaptureRate(MediaRecorder recorder, double fps) {
   1039         recorder.setCaptureRate(fps);
   1040     }
   1041 
   1042     private void setRecordLocation() {
   1043         Location loc = mLocationManager.getCurrentLocation();
   1044         if (loc != null) {
   1045             mMediaRecorder.setLocation((float) loc.getLatitude(),
   1046                     (float) loc.getLongitude());
   1047         }
   1048     }
   1049 
   1050     private void releaseMediaRecorder() {
   1051         Log.v(TAG, "Releasing media recorder.");
   1052         if (mMediaRecorder != null) {
   1053             cleanupEmptyFile();
   1054             mMediaRecorder.reset();
   1055             mMediaRecorder.release();
   1056             mMediaRecorder = null;
   1057         }
   1058         mVideoFilename = null;
   1059     }
   1060 
   1061     private void generateVideoFilename(int outputFileFormat) {
   1062         long dateTaken = System.currentTimeMillis();
   1063         String title = createName(dateTaken);
   1064         // Used when emailing.
   1065         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
   1066         String mime = convertOutputFormatToMimeType(outputFileFormat);
   1067         String path = Storage.DIRECTORY + '/' + filename;
   1068         String tmpPath = path + ".tmp";
   1069         mCurrentVideoValues = new ContentValues(9);
   1070         mCurrentVideoValues.put(Video.Media.TITLE, title);
   1071         mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
   1072         mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
   1073         mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
   1074         mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
   1075         mCurrentVideoValues.put(Video.Media.DATA, path);
   1076         mCurrentVideoValues.put(Video.Media.RESOLUTION,
   1077                 Integer.toString(mProfile.videoFrameWidth) + "x" +
   1078                 Integer.toString(mProfile.videoFrameHeight));
   1079         Location loc = mLocationManager.getCurrentLocation();
   1080         if (loc != null) {
   1081             mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
   1082             mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
   1083         }
   1084         mVideoFilename = tmpPath;
   1085         Log.v(TAG, "New video filename: " + mVideoFilename);
   1086     }
   1087 
   1088     private void saveVideo() {
   1089         if (mVideoFileDescriptor == null) {
   1090             long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
   1091             if (duration > 0) {
   1092                 if (mCaptureTimeLapse) {
   1093                     duration = getTimeLapseVideoLength(duration);
   1094                 }
   1095             } else {
   1096                 Log.w(TAG, "Video duration <= 0 : " + duration);
   1097             }
   1098             mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename,
   1099                     duration, mCurrentVideoValues,
   1100                     mOnVideoSavedListener, mContentResolver);
   1101         }
   1102         mCurrentVideoValues = null;
   1103     }
   1104 
   1105     private void deleteVideoFile(String fileName) {
   1106         Log.v(TAG, "Deleting video " + fileName);
   1107         File f = new File(fileName);
   1108         if (!f.delete()) {
   1109             Log.v(TAG, "Could not delete " + fileName);
   1110         }
   1111     }
   1112 
   1113     private PreferenceGroup filterPreferenceScreenByIntent(
   1114             PreferenceGroup screen) {
   1115         Intent intent = mActivity.getIntent();
   1116         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
   1117             CameraSettings.removePreferenceFromScreen(screen,
   1118                     CameraSettings.KEY_VIDEO_QUALITY);
   1119         }
   1120 
   1121         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
   1122             CameraSettings.removePreferenceFromScreen(screen,
   1123                     CameraSettings.KEY_VIDEO_QUALITY);
   1124         }
   1125         return screen;
   1126     }
   1127 
   1128     // from MediaRecorder.OnErrorListener
   1129     @Override
   1130     public void onError(MediaRecorder mr, int what, int extra) {
   1131         Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
   1132         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
   1133             // We may have run out of space on the sdcard.
   1134             stopVideoRecording();
   1135             mActivity.updateStorageSpaceAndHint();
   1136         }
   1137     }
   1138 
   1139     // from MediaRecorder.OnInfoListener
   1140     @Override
   1141     public void onInfo(MediaRecorder mr, int what, int extra) {
   1142         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
   1143             if (mMediaRecorderRecording) onStopVideoRecording();
   1144         } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
   1145             if (mMediaRecorderRecording) onStopVideoRecording();
   1146 
   1147             // Show the toast.
   1148             Toast.makeText(mActivity, R.string.video_reach_size_limit,
   1149                     Toast.LENGTH_LONG).show();
   1150         }
   1151     }
   1152 
   1153     /*
   1154      * Make sure we're not recording music playing in the background, ask the
   1155      * MediaPlaybackService to pause playback.
   1156      */
   1157     private void pauseAudioPlayback() {
   1158         // Shamelessly copied from MediaPlaybackService.java, which
   1159         // should be public, but isn't.
   1160         Intent i = new Intent("com.android.music.musicservicecommand");
   1161         i.putExtra("command", "pause");
   1162 
   1163         mActivity.sendBroadcast(i);
   1164     }
   1165 
   1166     // For testing.
   1167     public boolean isRecording() {
   1168         return mMediaRecorderRecording;
   1169     }
   1170 
   1171     private void startVideoRecording() {
   1172         Log.v(TAG, "startVideoRecording");
   1173         mUI.cancelAnimations();
   1174         mUI.setSwipingEnabled(false);
   1175 
   1176         mActivity.updateStorageSpaceAndHint();
   1177         if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
   1178             Log.v(TAG, "Storage issue, ignore the start request");
   1179             return;
   1180         }
   1181 
   1182         //??
   1183         //if (!mCameraDevice.waitDone()) return;
   1184         mCurrentVideoUri = null;
   1185 
   1186         initializeRecorder();
   1187         if (mMediaRecorder == null) {
   1188             Log.e(TAG, "Fail to initialize media recorder");
   1189             return;
   1190         }
   1191 
   1192         pauseAudioPlayback();
   1193 
   1194         try {
   1195             mMediaRecorder.start(); // Recording is now started
   1196         } catch (RuntimeException e) {
   1197             Log.e(TAG, "Could not start media recorder. ", e);
   1198             releaseMediaRecorder();
   1199             // If start fails, frameworks will not lock the camera for us.
   1200             mCameraDevice.lock();
   1201             return;
   1202         }
   1203 
   1204         // Make sure the video recording has started before announcing
   1205         // this in accessibility.
   1206         AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
   1207                 mActivity.getString(R.string.video_recording_started));
   1208 
   1209         // The parameters might have been altered by MediaRecorder already.
   1210         // We need to force mCameraDevice to refresh before getting it.
   1211         mCameraDevice.refreshParameters();
   1212         // The parameters may have been changed by MediaRecorder upon starting
   1213         // recording. We need to alter the parameters if we support camcorder
   1214         // zoom. To reduce latency when setting the parameters during zoom, we
   1215         // update mParameters here once.
   1216         mParameters = mCameraDevice.getParameters();
   1217 
   1218         mUI.enableCameraControls(false);
   1219 
   1220         mMediaRecorderRecording = true;
   1221         mOrientationManager.lockOrientation();
   1222         mRecordingStartTime = SystemClock.uptimeMillis();
   1223         mUI.showRecordingUI(true);
   1224 
   1225         updateRecordingTime();
   1226         keepScreenOn();
   1227         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
   1228                 UsageStatistics.ACTION_CAPTURE_START, "Video");
   1229     }
   1230 
   1231     private Bitmap getVideoThumbnail() {
   1232         Bitmap bitmap = null;
   1233         if (mVideoFileDescriptor != null) {
   1234             bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
   1235                     mDesiredPreviewWidth);
   1236         } else if (mCurrentVideoUri != null) {
   1237             try {
   1238                 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
   1239                 bitmap = Thumbnail.createVideoThumbnailBitmap(
   1240                         mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
   1241             } catch (java.io.FileNotFoundException ex) {
   1242                 // invalid uri
   1243                 Log.e(TAG, ex.toString());
   1244             }
   1245         }
   1246 
   1247         if (bitmap != null) {
   1248             // MetadataRetriever already rotates the thumbnail. We should rotate
   1249             // it to match the UI orientation (and mirror if it is front-facing camera).
   1250             CameraInfo[] info = CameraHolder.instance().getCameraInfo();
   1251             boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
   1252             bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
   1253         }
   1254         return bitmap;
   1255     }
   1256 
   1257     private void showCaptureResult() {
   1258         mIsInReviewMode = true;
   1259         Bitmap bitmap = getVideoThumbnail();
   1260         if (bitmap != null) {
   1261             mUI.showReviewImage(bitmap);
   1262         }
   1263         mUI.showReviewControls();
   1264         mUI.enableCameraControls(false);
   1265         mUI.showTimeLapseUI(false);
   1266     }
   1267 
   1268     private boolean stopVideoRecording() {
   1269         Log.v(TAG, "stopVideoRecording");
   1270         mUI.setSwipingEnabled(true);
   1271         if (!isVideoCaptureIntent()) {
   1272             mUI.showSwitcher();
   1273         }
   1274 
   1275         boolean fail = false;
   1276         if (mMediaRecorderRecording) {
   1277             boolean shouldAddToMediaStoreNow = false;
   1278 
   1279             try {
   1280                 mMediaRecorder.setOnErrorListener(null);
   1281                 mMediaRecorder.setOnInfoListener(null);
   1282                 mMediaRecorder.stop();
   1283                 shouldAddToMediaStoreNow = true;
   1284                 mCurrentVideoFilename = mVideoFilename;
   1285                 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
   1286                         + mCurrentVideoFilename);
   1287                 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
   1288                         mActivity.getString(R.string.video_recording_stopped));
   1289             } catch (RuntimeException e) {
   1290                 Log.e(TAG, "stop fail",  e);
   1291                 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
   1292                 fail = true;
   1293             }
   1294             mMediaRecorderRecording = false;
   1295             mOrientationManager.unlockOrientation();
   1296 
   1297             // If the activity is paused, this means activity is interrupted
   1298             // during recording. Release the camera as soon as possible because
   1299             // face unlock or other applications may need to use the camera.
   1300             if (mPaused) {
   1301                 closeCamera();
   1302             }
   1303 
   1304             mUI.showRecordingUI(false);
   1305             if (!mIsVideoCaptureIntent) {
   1306                 mUI.enableCameraControls(true);
   1307             }
   1308             // The orientation was fixed during video recording. Now make it
   1309             // reflect the device orientation as video recording is stopped.
   1310             mUI.setOrientationIndicator(0, true);
   1311             keepScreenOnAwhile();
   1312             if (shouldAddToMediaStoreNow && !fail) {
   1313                 if (mVideoFileDescriptor == null) {
   1314                     saveVideo();
   1315                 } else if (mIsVideoCaptureIntent) {
   1316                     // if no file save is needed, we can show the post capture UI now
   1317                     showCaptureResult();
   1318                 }
   1319             }
   1320         }
   1321         // release media recorder
   1322         releaseMediaRecorder();
   1323         if (!mPaused) {
   1324             mCameraDevice.lock();
   1325             if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
   1326                 stopPreview();
   1327                 mUI.hideSurfaceView();
   1328                 // Switch back to use SurfaceTexture for preview.
   1329                 startPreview();
   1330             }
   1331         }
   1332         // Update the parameters here because the parameters might have been altered
   1333         // by MediaRecorder.
   1334         if (!mPaused) mParameters = mCameraDevice.getParameters();
   1335         UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
   1336                 fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
   1337                     UsageStatistics.ACTION_CAPTURE_DONE, "Video",
   1338                     SystemClock.uptimeMillis() - mRecordingStartTime);
   1339         return fail;
   1340     }
   1341 
   1342     private void resetScreenOn() {
   1343         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1344         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1345     }
   1346 
   1347     private void keepScreenOnAwhile() {
   1348         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1349         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1350         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
   1351     }
   1352 
   1353     private void keepScreenOn() {
   1354         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1355         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1356     }
   1357 
   1358     private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
   1359         long seconds = milliSeconds / 1000; // round down to compute seconds
   1360         long minutes = seconds / 60;
   1361         long hours = minutes / 60;
   1362         long remainderMinutes = minutes - (hours * 60);
   1363         long remainderSeconds = seconds - (minutes * 60);
   1364 
   1365         StringBuilder timeStringBuilder = new StringBuilder();
   1366 
   1367         // Hours
   1368         if (hours > 0) {
   1369             if (hours < 10) {
   1370                 timeStringBuilder.append('0');
   1371             }
   1372             timeStringBuilder.append(hours);
   1373 
   1374             timeStringBuilder.append(':');
   1375         }
   1376 
   1377         // Minutes
   1378         if (remainderMinutes < 10) {
   1379             timeStringBuilder.append('0');
   1380         }
   1381         timeStringBuilder.append(remainderMinutes);
   1382         timeStringBuilder.append(':');
   1383 
   1384         // Seconds
   1385         if (remainderSeconds < 10) {
   1386             timeStringBuilder.append('0');
   1387         }
   1388         timeStringBuilder.append(remainderSeconds);
   1389 
   1390         // Centi seconds
   1391         if (displayCentiSeconds) {
   1392             timeStringBuilder.append('.');
   1393             long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
   1394             if (remainderCentiSeconds < 10) {
   1395                 timeStringBuilder.append('0');
   1396             }
   1397             timeStringBuilder.append(remainderCentiSeconds);
   1398         }
   1399 
   1400         return timeStringBuilder.toString();
   1401     }
   1402 
   1403     private long getTimeLapseVideoLength(long deltaMs) {
   1404         // For better approximation calculate fractional number of frames captured.
   1405         // This will update the video time at a higher resolution.
   1406         double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
   1407         return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
   1408     }
   1409 
   1410     private void updateRecordingTime() {
   1411         if (!mMediaRecorderRecording) {
   1412             return;
   1413         }
   1414         long now = SystemClock.uptimeMillis();
   1415         long delta = now - mRecordingStartTime;
   1416 
   1417         // Starting a minute before reaching the max duration
   1418         // limit, we'll countdown the remaining time instead.
   1419         boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
   1420                 && delta >= mMaxVideoDurationInMs - 60000);
   1421 
   1422         long deltaAdjusted = delta;
   1423         if (countdownRemainingTime) {
   1424             deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
   1425         }
   1426         String text;
   1427 
   1428         long targetNextUpdateDelay;
   1429         if (!mCaptureTimeLapse) {
   1430             text = millisecondToTimeString(deltaAdjusted, false);
   1431             targetNextUpdateDelay = 1000;
   1432         } else {
   1433             // The length of time lapse video is different from the length
   1434             // of the actual wall clock time elapsed. Display the video length
   1435             // only in format hh:mm:ss.dd, where dd are the centi seconds.
   1436             text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
   1437             targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
   1438         }
   1439 
   1440         mUI.setRecordingTime(text);
   1441 
   1442         if (mRecordingTimeCountsDown != countdownRemainingTime) {
   1443             // Avoid setting the color on every update, do it only
   1444             // when it needs changing.
   1445             mRecordingTimeCountsDown = countdownRemainingTime;
   1446 
   1447             int color = mActivity.getResources().getColor(countdownRemainingTime
   1448                     ? R.color.recording_time_remaining_text
   1449                     : R.color.recording_time_elapsed_text);
   1450 
   1451             mUI.setRecordingTimeTextColor(color);
   1452         }
   1453 
   1454         long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
   1455         mHandler.sendEmptyMessageDelayed(
   1456                 UPDATE_RECORD_TIME, actualNextUpdateDelay);
   1457     }
   1458 
   1459     private static boolean isSupported(String value, List<String> supported) {
   1460         return supported == null ? false : supported.indexOf(value) >= 0;
   1461     }
   1462 
   1463     @SuppressWarnings("deprecation")
   1464     private void setCameraParameters() {
   1465         mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
   1466         int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
   1467         if (fpsRange.length > 0) {
   1468             mParameters.setPreviewFpsRange(
   1469                     fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
   1470                     fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
   1471         } else {
   1472             mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
   1473         }
   1474 
   1475         forceFlashOffIfSupported(!mUI.isVisible());
   1476 
   1477         // Set white balance parameter.
   1478         String whiteBalance = mPreferences.getString(
   1479                 CameraSettings.KEY_WHITE_BALANCE,
   1480                 mActivity.getString(R.string.pref_camera_whitebalance_default));
   1481         if (isSupported(whiteBalance,
   1482                 mParameters.getSupportedWhiteBalance())) {
   1483             mParameters.setWhiteBalance(whiteBalance);
   1484         } else {
   1485             whiteBalance = mParameters.getWhiteBalance();
   1486             if (whiteBalance == null) {
   1487                 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
   1488             }
   1489         }
   1490 
   1491         // Set zoom.
   1492         if (mParameters.isZoomSupported()) {
   1493             mParameters.setZoom(mZoomValue);
   1494         }
   1495 
   1496         // Set continuous autofocus.
   1497         List<String> supportedFocus = mParameters.getSupportedFocusModes();
   1498         if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
   1499             mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
   1500         }
   1501 
   1502         mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
   1503 
   1504         // Enable video stabilization. Convenience methods not available in API
   1505         // level <= 14
   1506         String vstabSupported = mParameters.get("video-stabilization-supported");
   1507         if ("true".equals(vstabSupported)) {
   1508             mParameters.set("video-stabilization", "true");
   1509         }
   1510 
   1511         // Set picture size.
   1512         // The logic here is different from the logic in still-mode camera.
   1513         // There we determine the preview size based on the picture size, but
   1514         // here we determine the picture size based on the preview size.
   1515         List<Size> supported = mParameters.getSupportedPictureSizes();
   1516         Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
   1517                 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
   1518         Size original = mParameters.getPictureSize();
   1519         if (!original.equals(optimalSize)) {
   1520             mParameters.setPictureSize(optimalSize.width, optimalSize.height);
   1521         }
   1522         Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
   1523                 optimalSize.height);
   1524 
   1525         // Set JPEG quality.
   1526         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
   1527                 CameraProfile.QUALITY_HIGH);
   1528         mParameters.setJpegQuality(jpegQuality);
   1529 
   1530         mCameraDevice.setParameters(mParameters);
   1531         // Keep preview size up to date.
   1532         mParameters = mCameraDevice.getParameters();
   1533 
   1534         // Update UI based on the new parameters.
   1535         mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1536     }
   1537 
   1538     @Override
   1539     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1540         // Do nothing.
   1541     }
   1542 
   1543     @Override
   1544     public void onConfigurationChanged(Configuration newConfig) {
   1545         Log.v(TAG, "onConfigurationChanged");
   1546         setDisplayOrientation();
   1547     }
   1548 
   1549     @Override
   1550     public void onOverriddenPreferencesClicked() {
   1551     }
   1552 
   1553     @Override
   1554     // TODO: Delete this after old camera code is removed
   1555     public void onRestorePreferencesClicked() {
   1556     }
   1557 
   1558     @Override
   1559     public void onSharedPreferenceChanged() {
   1560         // ignore the events after "onPause()" or preview has not started yet
   1561         if (mPaused) {
   1562             return;
   1563         }
   1564         synchronized (mPreferences) {
   1565             // If mCameraDevice is not ready then we can set the parameter in
   1566             // startPreview().
   1567             if (mCameraDevice == null) return;
   1568 
   1569             boolean recordLocation = RecordLocationPreference.get(
   1570                     mPreferences, mContentResolver);
   1571             mLocationManager.recordLocation(recordLocation);
   1572 
   1573             readVideoPreferences();
   1574             mUI.showTimeLapseUI(mCaptureTimeLapse);
   1575             // We need to restart the preview if preview size is changed.
   1576             Size size = mParameters.getPreviewSize();
   1577             if (size.width != mDesiredPreviewWidth
   1578                     || size.height != mDesiredPreviewHeight) {
   1579 
   1580                 stopPreview();
   1581                 resizeForPreviewAspectRatio();
   1582                 startPreview(); // Parameters will be set in startPreview().
   1583             } else {
   1584                 setCameraParameters();
   1585             }
   1586             mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1587         }
   1588     }
   1589 
   1590     protected void setCameraId(int cameraId) {
   1591         ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
   1592         pref.setValue("" + cameraId);
   1593     }
   1594 
   1595     private void switchCamera() {
   1596         if (mPaused)  {
   1597             return;
   1598         }
   1599 
   1600         Log.d(TAG, "Start to switch camera.");
   1601         mCameraId = mPendingSwitchCameraId;
   1602         mPendingSwitchCameraId = -1;
   1603         setCameraId(mCameraId);
   1604 
   1605         closeCamera();
   1606         mUI.collapseCameraControls();
   1607         // Restart the camera and initialize the UI. From onCreate.
   1608         mPreferences.setLocalId(mActivity, mCameraId);
   1609         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
   1610         openCamera();
   1611         readVideoPreferences();
   1612         startPreview();
   1613         initializeVideoSnapshot();
   1614         resizeForPreviewAspectRatio();
   1615         initializeVideoControl();
   1616 
   1617         // From onResume
   1618         mZoomValue = 0;
   1619         mUI.initializeZoom(mParameters);
   1620         mUI.setOrientationIndicator(0, false);
   1621 
   1622         // Start switch camera animation. Post a message because
   1623         // onFrameAvailable from the old camera may already exist.
   1624         mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
   1625         mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1626     }
   1627 
   1628     // Preview texture has been copied. Now camera can be released and the
   1629     // animation can be started.
   1630     @Override
   1631     public void onPreviewTextureCopied() {
   1632         mHandler.sendEmptyMessage(SWITCH_CAMERA);
   1633     }
   1634 
   1635     @Override
   1636     public void onCaptureTextureCopied() {
   1637     }
   1638 
   1639     private void initializeVideoSnapshot() {
   1640         if (mParameters == null) return;
   1641         if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
   1642             // Show the tap to focus toast if this is the first start.
   1643             if (mPreferences.getBoolean(
   1644                         CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
   1645                 // Delay the toast for one second to wait for orientation.
   1646                 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
   1647             }
   1648         }
   1649     }
   1650 
   1651     void showVideoSnapshotUI(boolean enabled) {
   1652         if (mParameters == null) return;
   1653         if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
   1654             if (enabled) {
   1655                 mUI.animateFlash();
   1656                 mUI.animateCapture();
   1657             } else {
   1658                 mUI.showPreviewBorder(enabled);
   1659             }
   1660             mUI.enableShutter(!enabled);
   1661         }
   1662     }
   1663 
   1664     private void forceFlashOffIfSupported(boolean forceOff) {
   1665         String flashMode;
   1666         if (!forceOff) {
   1667             flashMode = mPreferences.getString(
   1668                     CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
   1669                     mActivity.getString(R.string.pref_camera_video_flashmode_default));
   1670         } else {
   1671             flashMode = Parameters.FLASH_MODE_OFF;
   1672         }
   1673         List<String> supportedFlash = mParameters.getSupportedFlashModes();
   1674         if (isSupported(flashMode, supportedFlash)) {
   1675             mParameters.setFlashMode(flashMode);
   1676         } else {
   1677             flashMode = mParameters.getFlashMode();
   1678             if (flashMode == null) {
   1679                 flashMode = mActivity.getString(
   1680                         R.string.pref_camera_flashmode_no_flash);
   1681             }
   1682         }
   1683     }
   1684 
   1685     /**
   1686      * Used to update the flash mode. Video mode can turn on the flash as torch
   1687      * mode, which we would like to turn on and off when we switching in and
   1688      * out to the preview.
   1689      *
   1690      * @param forceOff whether we want to force the flash off.
   1691      */
   1692     private void forceFlashOff(boolean forceOff) {
   1693         if (!mPreviewing || mParameters.getFlashMode() == null) {
   1694             return;
   1695         }
   1696         forceFlashOffIfSupported(forceOff);
   1697         mCameraDevice.setParameters(mParameters);
   1698         mUI.updateOnScreenIndicators(mParameters, mPreferences);
   1699     }
   1700 
   1701     @Override
   1702     public void onPreviewFocusChanged(boolean previewFocused) {
   1703         mUI.onPreviewFocusChanged(previewFocused);
   1704         forceFlashOff(!previewFocused);
   1705     }
   1706 
   1707     @Override
   1708     public boolean arePreviewControlsVisible() {
   1709         return mUI.arePreviewControlsVisible();
   1710     }
   1711 
   1712     private final class JpegPictureCallback implements CameraPictureCallback {
   1713         Location mLocation;
   1714 
   1715         public JpegPictureCallback(Location loc) {
   1716             mLocation = loc;
   1717         }
   1718 
   1719         @Override
   1720         public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
   1721             Log.v(TAG, "onPictureTaken");
   1722             mSnapshotInProgress = false;
   1723             showVideoSnapshotUI(false);
   1724             storeImage(jpegData, mLocation);
   1725         }
   1726     }
   1727 
   1728     private void storeImage(final byte[] data, Location loc) {
   1729         long dateTaken = System.currentTimeMillis();
   1730         String title = CameraUtil.createJpegName(dateTaken);
   1731         ExifInterface exif = Exif.getExif(data);
   1732         int orientation = Exif.getOrientation(exif);
   1733 
   1734         mActivity.getMediaSaveService().addImage(
   1735                 data, title, dateTaken, loc, orientation,
   1736                 exif, mOnPhotoSavedListener, mContentResolver);
   1737     }
   1738 
   1739     private String convertOutputFormatToMimeType(int outputFileFormat) {
   1740         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   1741             return "video/mp4";
   1742         }
   1743         return "video/3gpp";
   1744     }
   1745 
   1746     private String convertOutputFormatToFileExt(int outputFileFormat) {
   1747         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   1748             return ".mp4";
   1749         }
   1750         return ".3gp";
   1751     }
   1752 
   1753     private void closeVideoFileDescriptor() {
   1754         if (mVideoFileDescriptor != null) {
   1755             try {
   1756                 mVideoFileDescriptor.close();
   1757             } catch (IOException e) {
   1758                 Log.e(TAG, "Fail to close fd", e);
   1759             }
   1760             mVideoFileDescriptor = null;
   1761         }
   1762     }
   1763 
   1764     private void showTapToSnapshotToast() {
   1765         new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
   1766                 .show();
   1767         // Clear the preference.
   1768         Editor editor = mPreferences.edit();
   1769         editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
   1770         editor.apply();
   1771     }
   1772 
   1773     @Override
   1774     public boolean updateStorageHintOnResume() {
   1775         return true;
   1776     }
   1777 
   1778     // required by OnPreferenceChangedListener
   1779     @Override
   1780     public void onCameraPickerClicked(int cameraId) {
   1781         if (mPaused || mPendingSwitchCameraId != -1) return;
   1782 
   1783         mPendingSwitchCameraId = cameraId;
   1784         Log.d(TAG, "Start to copy texture.");
   1785         // We need to keep a preview frame for the animation before
   1786         // releasing the camera. This will trigger onPreviewTextureCopied.
   1787         // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
   1788         // Disable all camera controls.
   1789         mSwitchingCamera = true;
   1790         switchCamera();
   1791 
   1792     }
   1793 
   1794     @Override
   1795     public void onShowSwitcherPopup() {
   1796         mUI.onShowSwitcherPopup();
   1797     }
   1798 
   1799     @Override
   1800     public void onMediaSaveServiceConnected(MediaSaveService s) {
   1801         // do nothing.
   1802     }
   1803 
   1804     @Override
   1805     public void onPreviewUIReady() {
   1806         startPreview();
   1807     }
   1808 
   1809     @Override
   1810     public void onPreviewUIDestroyed() {
   1811         stopPreview();
   1812     }
   1813 }
   1814