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.graphics.Bitmap;
     29 import android.graphics.Point;
     30 import android.graphics.SurfaceTexture;
     31 import android.location.Location;
     32 import android.media.AudioManager;
     33 import android.media.CamcorderProfile;
     34 import android.media.CameraProfile;
     35 import android.media.MediaRecorder;
     36 import android.net.Uri;
     37 import android.os.Build;
     38 import android.os.Bundle;
     39 import android.os.Handler;
     40 import android.os.Looper;
     41 import android.os.Message;
     42 import android.os.ParcelFileDescriptor;
     43 import android.os.SystemClock;
     44 import android.provider.MediaStore;
     45 import android.provider.MediaStore.MediaColumns;
     46 import android.provider.MediaStore.Video;
     47 import android.view.KeyEvent;
     48 import android.view.OrientationEventListener;
     49 import android.view.View;
     50 import android.widget.Toast;
     51 
     52 import com.android.camera.app.AppController;
     53 import com.android.camera.app.CameraAppUI;
     54 import com.android.camera.app.LocationManager;
     55 import com.android.camera.app.MediaSaver;
     56 import com.android.camera.app.MemoryManager;
     57 import com.android.camera.app.MemoryManager.MemoryListener;
     58 import com.android.camera.debug.Log;
     59 import com.android.camera.exif.ExifInterface;
     60 import com.android.camera.hardware.HardwareSpec;
     61 import com.android.camera.hardware.HardwareSpecImpl;
     62 import com.android.camera.module.ModuleController;
     63 import com.android.camera.settings.Keys;
     64 import com.android.camera.settings.SettingsManager;
     65 import com.android.camera.settings.SettingsUtil;
     66 import com.android.camera.ui.TouchCoordinate;
     67 import com.android.camera.util.ApiHelper;
     68 import com.android.camera.util.CameraUtil;
     69 import com.android.camera.util.UsageStatistics;
     70 import com.android.camera2.R;
     71 import com.android.ex.camera2.portability.CameraAgent;
     72 import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
     73 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
     74 import com.android.ex.camera2.portability.CameraCapabilities;
     75 import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
     76 import com.android.ex.camera2.portability.CameraSettings;
     77 import com.android.ex.camera2.portability.Size;
     78 import com.google.common.logging.eventprotos;
     79 
     80 import java.io.File;
     81 import java.io.IOException;
     82 import java.text.SimpleDateFormat;
     83 import java.util.ArrayList;
     84 import java.util.Date;
     85 import java.util.Iterator;
     86 import java.util.List;
     87 import java.util.Set;
     88 
     89 public class VideoModule extends CameraModule
     90     implements ModuleController,
     91     VideoController,
     92     MemoryListener,
     93     MediaRecorder.OnErrorListener,
     94     MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
     95 
     96     private static final String VIDEO_MODULE_STRING_ID = "VideoModule";
     97 
     98     private static final Log.Tag TAG = new Log.Tag(VIDEO_MODULE_STRING_ID);
     99 
    100     // Messages defined for the UI thread handler.
    101     private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
    102     private static final int MSG_UPDATE_RECORD_TIME = 5;
    103     private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
    104     private static final int MSG_SWITCH_CAMERA = 8;
    105     private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
    106 
    107     private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
    108 
    109     /**
    110      * An unpublished intent flag requesting to start recording straight away
    111      * and return as soon as recording is stopped.
    112      * TODO: consider publishing by moving into MediaStore.
    113      */
    114     private static final String EXTRA_QUICK_CAPTURE =
    115             "android.intent.extra.quickCapture";
    116 
    117     // module fields
    118     private CameraActivity mActivity;
    119     private boolean mPaused;
    120 
    121     // if, during and intent capture, the activity is paused (e.g. when app switching or reviewing a
    122     // shot video), we don't want the bottom bar intent ui to reset to the capture button
    123     private boolean mDontResetIntentUiOnResume;
    124 
    125     private int mCameraId;
    126     private CameraSettings mCameraSettings;
    127     private CameraCapabilities mCameraCapabilities;
    128 
    129     private boolean mIsInReviewMode;
    130     private boolean mSnapshotInProgress = false;
    131 
    132     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
    133 
    134     // Preference must be read before starting preview. We check this before starting
    135     // preview.
    136     private boolean mPreferenceRead;
    137 
    138     private boolean mIsVideoCaptureIntent;
    139     private boolean mQuickCapture;
    140 
    141     private MediaRecorder mMediaRecorder;
    142 
    143     private boolean mSwitchingCamera;
    144     private boolean mMediaRecorderRecording = false;
    145     private long mRecordingStartTime;
    146     private boolean mRecordingTimeCountsDown = false;
    147     private long mOnResumeTime;
    148     // The video file that the hardware camera is about to record into
    149     // (or is recording into.
    150     private String mVideoFilename;
    151     private ParcelFileDescriptor mVideoFileDescriptor;
    152 
    153     // The video file that has already been recorded, and that is being
    154     // examined by the user.
    155     private String mCurrentVideoFilename;
    156     private Uri mCurrentVideoUri;
    157     private boolean mCurrentVideoUriFromMediaSaved;
    158     private ContentValues mCurrentVideoValues;
    159 
    160     private CamcorderProfile mProfile;
    161 
    162     // The video duration limit. 0 means no limit.
    163     private int mMaxVideoDurationInMs;
    164 
    165     boolean mPreviewing = false; // True if preview is started.
    166     // The display rotation in degrees. This is only valid when mPreviewing is
    167     // true.
    168     private int mDisplayRotation;
    169     private int mCameraDisplayOrientation;
    170     private AppController mAppController;
    171 
    172     private int mDesiredPreviewWidth;
    173     private int mDesiredPreviewHeight;
    174     private ContentResolver mContentResolver;
    175 
    176     private LocationManager mLocationManager;
    177 
    178     private int mPendingSwitchCameraId;
    179     private final Handler mHandler = new MainHandler();
    180     private VideoUI mUI;
    181     private CameraProxy mCameraDevice;
    182 
    183     // The degrees of the device rotated clockwise from its natural orientation.
    184     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
    185 
    186     private float mZoomValue;  // The current zoom ratio.
    187 
    188     private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
    189             new MediaSaver.OnMediaSavedListener() {
    190                 @Override
    191                 public void onMediaSaved(Uri uri) {
    192                     if (uri != null) {
    193                         mCurrentVideoUri = uri;
    194                         mCurrentVideoUriFromMediaSaved = true;
    195                         onVideoSaved();
    196                         mActivity.notifyNewMedia(uri);
    197                     }
    198                 }
    199             };
    200 
    201     private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
    202             new MediaSaver.OnMediaSavedListener() {
    203                 @Override
    204                 public void onMediaSaved(Uri uri) {
    205                     if (uri != null) {
    206                         mActivity.notifyNewMedia(uri);
    207                     }
    208                 }
    209             };
    210     private FocusOverlayManager mFocusManager;
    211     private boolean mMirror;
    212     private boolean mFocusAreaSupported;
    213     private boolean mMeteringAreaSupported;
    214 
    215     private final CameraAgent.CameraAFCallback mAutoFocusCallback =
    216             new CameraAgent.CameraAFCallback() {
    217         @Override
    218         public void onAutoFocus(boolean focused, CameraProxy camera) {
    219             if (mPaused) {
    220                 return;
    221             }
    222             mFocusManager.onAutoFocus(focused, false);
    223         }
    224     };
    225 
    226     private final Object mAutoFocusMoveCallback =
    227             ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
    228                     ? new CameraAgent.CameraAFMoveCallback() {
    229                 @Override
    230                 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
    231                     // mFocusManager.onAutoFocusMoving(moving) not called because UI
    232                     // not compatible with vertical video hint UI.
    233                 }
    234             } : null;
    235 
    236     /**
    237      * This Handler is used to post message back onto the main thread of the
    238      * application.
    239      */
    240     private class MainHandler extends Handler {
    241         @Override
    242         public void handleMessage(Message msg) {
    243             switch (msg.what) {
    244 
    245                 case MSG_ENABLE_SHUTTER_BUTTON:
    246                     mAppController.setShutterEnabled(true);
    247                     break;
    248 
    249                 case MSG_UPDATE_RECORD_TIME: {
    250                     updateRecordingTime();
    251                     break;
    252                 }
    253 
    254                 case MSG_CHECK_DISPLAY_ROTATION: {
    255                     // Restart the preview if display rotation has changed.
    256                     // Sometimes this happens when the device is held upside
    257                     // down and camera app is opened. Rotation animation will
    258                     // take some time and the rotation value we have got may be
    259                     // wrong. Framework does not have a callback for this now.
    260                     if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
    261                             && !mMediaRecorderRecording && !mSwitchingCamera) {
    262                         startPreview();
    263                     }
    264                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
    265                         mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
    266                     }
    267                     break;
    268                 }
    269 
    270                 case MSG_SWITCH_CAMERA: {
    271                     switchCamera();
    272                     break;
    273                 }
    274 
    275                 case MSG_SWITCH_CAMERA_START_ANIMATION: {
    276                     //TODO:
    277                     //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
    278 
    279                     // Enable all camera controls.
    280                     mSwitchingCamera = false;
    281                     break;
    282                 }
    283 
    284                 default:
    285                     Log.v(TAG, "Unhandled message: " + msg.what);
    286                     break;
    287             }
    288         }
    289     }
    290 
    291     private BroadcastReceiver mReceiver = null;
    292 
    293     private class MyBroadcastReceiver extends BroadcastReceiver {
    294         @Override
    295         public void onReceive(Context context, Intent intent) {
    296             String action = intent.getAction();
    297             if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
    298                 stopVideoRecording();
    299             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
    300                 Toast.makeText(mActivity,
    301                         mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
    302             }
    303         }
    304     }
    305 
    306     private int mShutterIconId;
    307 
    308 
    309     /**
    310      * Construct a new video module.
    311      */
    312     public VideoModule(AppController app) {
    313         super(app);
    314     }
    315 
    316     @Override
    317     public String getPeekAccessibilityString() {
    318         return mAppController.getAndroidContext()
    319             .getResources().getString(R.string.video_accessibility_peek);
    320     }
    321 
    322     private String createName(long dateTaken) {
    323         Date date = new Date(dateTaken);
    324         SimpleDateFormat dateFormat = new SimpleDateFormat(
    325                 mActivity.getString(R.string.video_file_name_format));
    326 
    327         return dateFormat.format(date);
    328     }
    329 
    330     @Override
    331     public String getModuleStringIdentifier() {
    332         return VIDEO_MODULE_STRING_ID;
    333     }
    334 
    335     @Override
    336     public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
    337         mActivity = activity;
    338         // TODO: Need to look at the controller interface to see if we can get
    339         // rid of passing in the activity directly.
    340         mAppController = mActivity;
    341 
    342         mActivity.updateStorageSpaceAndHint(null);
    343 
    344         mUI = new VideoUI(mActivity, this,  mActivity.getModuleLayoutRoot());
    345         mActivity.setPreviewStatusListener(mUI);
    346 
    347         SettingsManager settingsManager = mActivity.getSettingsManager();
    348         mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
    349                                                Keys.KEY_CAMERA_ID);
    350 
    351         /*
    352          * To reduce startup time, we start the preview in another thread.
    353          * We make sure the preview is started at the end of onCreate.
    354          */
    355         requestCamera(mCameraId);
    356 
    357         mContentResolver = mActivity.getContentResolver();
    358 
    359         // Surface texture is from camera screen nail and startPreview needs it.
    360         // This must be done before startPreview.
    361         mIsVideoCaptureIntent = isVideoCaptureIntent();
    362 
    363         mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
    364         mLocationManager = mActivity.getLocationManager();
    365 
    366         mUI.setOrientationIndicator(0, false);
    367         setDisplayOrientation();
    368 
    369         mPendingSwitchCameraId = -1;
    370 
    371         mShutterIconId = CameraUtil.getCameraShutterIconId(
    372                 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
    373     }
    374 
    375     @Override
    376     public boolean isUsingBottomBar() {
    377         return true;
    378     }
    379 
    380     private void initializeControlByIntent() {
    381         if (isVideoCaptureIntent()) {
    382             if (!mDontResetIntentUiOnResume) {
    383                 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
    384             }
    385             // reset the flag
    386             mDontResetIntentUiOnResume = false;
    387         }
    388     }
    389 
    390     @Override
    391     public void onSingleTapUp(View view, int x, int y) {
    392         if (mPaused || mCameraDevice == null) {
    393             return;
    394         }
    395         if (mMediaRecorderRecording) {
    396             if (!mSnapshotInProgress) {
    397                 takeASnapshot();
    398             }
    399             return;
    400         }
    401         // Check if metering area or focus area is supported.
    402         if (!mFocusAreaSupported && !mMeteringAreaSupported) {
    403             return;
    404         }
    405         // Tap to focus.
    406         mFocusManager.onSingleTapUp(x, y);
    407     }
    408 
    409     private void takeASnapshot() {
    410         // Only take snapshots if video snapshot is supported by device
    411         if(!mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_SNAPSHOT)) {
    412             Log.w(TAG, "Cannot take a video snapshot - not supported by hardware");
    413             return;
    414         }
    415         if (!mIsVideoCaptureIntent) {
    416             if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress
    417                     || !mAppController.isShutterEnabled() || mCameraDevice == null) {
    418                 return;
    419             }
    420 
    421             Location loc = mLocationManager.getCurrentLocation();
    422             CameraUtil.setGpsParameters(mCameraSettings, loc);
    423             mCameraDevice.applySettings(mCameraSettings);
    424 
    425             Log.i(TAG, "Video snapshot start");
    426             mCameraDevice.takePicture(mHandler,
    427                     null, null, null, new JpegPictureCallback(loc));
    428             showVideoSnapshotUI(true);
    429             mSnapshotInProgress = true;
    430         }
    431     }
    432 
    433     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    434      private void updateAutoFocusMoveCallback() {
    435         if (mPaused || mCameraDevice == null) {
    436             return;
    437         }
    438 
    439         if (mCameraSettings.getCurrentFocusMode() == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
    440             mCameraDevice.setAutoFocusMoveCallback(mHandler,
    441                     (CameraAgent.CameraAFMoveCallback) mAutoFocusMoveCallback);
    442         } else {
    443             mCameraDevice.setAutoFocusMoveCallback(null, null);
    444         }
    445     }
    446 
    447     /**
    448      * @return Whether the currently active camera is front-facing.
    449      */
    450     private boolean isCameraFrontFacing() {
    451         return mAppController.getCameraProvider().getCharacteristics(mCameraId)
    452                 .isFacingFront();
    453     }
    454 
    455     /**
    456      * @return Whether the currently active camera is back-facing.
    457      */
    458     private boolean isCameraBackFacing() {
    459         return mAppController.getCameraProvider().getCharacteristics(mCameraId)
    460                 .isFacingBack();
    461     }
    462 
    463     /**
    464      * The focus manager gets initialized after camera is available.
    465      */
    466     private void initializeFocusManager() {
    467         // Create FocusManager object. startPreview needs it.
    468         // if mFocusManager not null, reuse it
    469         // otherwise create a new instance
    470         if (mFocusManager != null) {
    471             mFocusManager.removeMessages();
    472         } else {
    473             mMirror = isCameraFrontFacing();
    474             String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
    475                     R.array.pref_camera_focusmode_default_array);
    476             CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
    477             ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
    478                     new ArrayList<CameraCapabilities.FocusMode>();
    479             for (String modeString : defaultFocusModesStrings) {
    480                 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
    481                 if (mode != null) {
    482                     defaultFocusModes.add(mode);
    483                 }
    484             }
    485             mFocusManager = new FocusOverlayManager(mAppController,
    486                     defaultFocusModes, mCameraCapabilities, this, mMirror,
    487                     mActivity.getMainLooper(), mUI.getFocusUI());
    488         }
    489         mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
    490     }
    491 
    492     @Override
    493     public void onOrientationChanged(int orientation) {
    494         // We keep the last known orientation. So if the user first orient
    495         // the camera then point the camera to floor or sky, we still have
    496         // the correct orientation.
    497         if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
    498             return;
    499         }
    500         int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
    501 
    502         if (mOrientation != newOrientation) {
    503             mOrientation = newOrientation;
    504         }
    505         mUI.onOrientationChanged(orientation);
    506 
    507     }
    508 
    509     private final ButtonManager.ButtonCallback mFlashCallback =
    510         new ButtonManager.ButtonCallback() {
    511             @Override
    512             public void onStateChanged(int state) {
    513                 // Update flash parameters.
    514                 enableTorchMode(true);
    515             }
    516         };
    517 
    518     private final ButtonManager.ButtonCallback mCameraCallback =
    519         new ButtonManager.ButtonCallback() {
    520             @Override
    521             public void onStateChanged(int state) {
    522                 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
    523                     return;
    524                 }
    525                 mPendingSwitchCameraId = state;
    526                 Log.d(TAG, "Start to copy texture.");
    527 
    528                 // Disable all camera controls.
    529                 mSwitchingCamera = true;
    530                 switchCamera();
    531             }
    532         };
    533 
    534     private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
    535         @Override
    536         public void onClick(View v) {
    537             onReviewCancelClicked(v);
    538         }
    539     };
    540 
    541     private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
    542         @Override
    543         public void onClick(View v) {
    544             onReviewDoneClicked(v);
    545         }
    546     };
    547     private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
    548         @Override
    549         public void onClick(View v) {
    550             onReviewPlayClicked(v);
    551         }
    552     };
    553 
    554     @Override
    555     public void hardResetSettings(SettingsManager settingsManager) {
    556         // VideoModule does not need to hard reset any settings.
    557     }
    558 
    559     @Override
    560     public HardwareSpec getHardwareSpec() {
    561         return (mCameraSettings != null ?
    562                 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
    563     }
    564 
    565     @Override
    566     public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
    567         CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
    568 
    569         bottomBarSpec.enableCamera = true;
    570         bottomBarSpec.cameraCallback = mCameraCallback;
    571         bottomBarSpec.enableTorchFlash = true;
    572         bottomBarSpec.flashCallback = mFlashCallback;
    573         bottomBarSpec.hideHdr = true;
    574         bottomBarSpec.enableGridLines = true;
    575 
    576         if (isVideoCaptureIntent()) {
    577             bottomBarSpec.showCancel = true;
    578             bottomBarSpec.cancelCallback = mCancelCallback;
    579             bottomBarSpec.showDone = true;
    580             bottomBarSpec.doneCallback = mDoneCallback;
    581             bottomBarSpec.showReview = true;
    582             bottomBarSpec.reviewCallback = mReviewCallback;
    583         }
    584 
    585         return bottomBarSpec;
    586     }
    587 
    588     @Override
    589     public void onCameraAvailable(CameraProxy cameraProxy) {
    590         if (cameraProxy == null) {
    591             Log.w(TAG, "onCameraAvailable returns a null CameraProxy object");
    592             return;
    593         }
    594         mCameraDevice = cameraProxy;
    595         mCameraCapabilities = mCameraDevice.getCapabilities();
    596         mCameraSettings = mCameraDevice.getSettings();
    597         mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
    598         mMeteringAreaSupported =
    599                 mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
    600         readVideoPreferences();
    601         resizeForPreviewAspectRatio();
    602         initializeFocusManager();
    603         // TODO: Having focus overlay manager caching the parameters is prone to error,
    604         // we should consider passing the parameters to focus overlay to ensure the
    605         // parameters are up to date.
    606         mFocusManager.updateCapabilities(mCameraCapabilities);
    607 
    608         startPreview();
    609         initializeVideoSnapshot();
    610         mUI.initializeZoom(mCameraSettings, mCameraCapabilities);
    611         initializeControlByIntent();
    612     }
    613 
    614     private void startPlayVideoActivity() {
    615         Intent intent = new Intent(Intent.ACTION_VIEW);
    616         intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
    617         try {
    618             mActivity.launchActivityByIntent(intent);
    619         } catch (ActivityNotFoundException ex) {
    620             Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
    621         }
    622     }
    623 
    624     @Override
    625     @OnClickAttr
    626     public void onReviewPlayClicked(View v) {
    627         startPlayVideoActivity();
    628     }
    629 
    630     @Override
    631     @OnClickAttr
    632     public void onReviewDoneClicked(View v) {
    633         mIsInReviewMode = false;
    634         doReturnToCaller(true);
    635     }
    636 
    637     @Override
    638     @OnClickAttr
    639     public void onReviewCancelClicked(View v) {
    640         // TODO: It should be better to not even insert the URI at all before we
    641         // confirm done in review, which means we need to handle temporary video
    642         // files in a quite different way than we currently had.
    643         // Make sure we don't delete the Uri sent from the video capture intent.
    644         if (mCurrentVideoUriFromMediaSaved) {
    645             mContentResolver.delete(mCurrentVideoUri, null, null);
    646         }
    647         mIsInReviewMode = false;
    648         doReturnToCaller(false);
    649     }
    650 
    651     @Override
    652     public boolean isInReviewMode() {
    653         return mIsInReviewMode;
    654     }
    655 
    656     private void onStopVideoRecording() {
    657         mAppController.getCameraAppUI().setSwipeEnabled(true);
    658         boolean recordFail = stopVideoRecording();
    659         if (mIsVideoCaptureIntent) {
    660             if (mQuickCapture) {
    661                 doReturnToCaller(!recordFail);
    662             } else if (!recordFail) {
    663                 showCaptureResult();
    664             }
    665         } else if (!recordFail){
    666             // Start capture animation.
    667             if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
    668                 // The capture animation is disabled on ICS because we use SurfaceView
    669                 // for preview during recording. When the recording is done, we switch
    670                 // back to use SurfaceTexture for preview and we need to stop then start
    671                 // the preview. This will cause the preview flicker since the preview
    672                 // will not be continuous for a short period of time.
    673 
    674                 mUI.animateFlash();
    675             }
    676         }
    677     }
    678 
    679     public void onVideoSaved() {
    680         if (mIsVideoCaptureIntent) {
    681             showCaptureResult();
    682         }
    683     }
    684 
    685     public void onProtectiveCurtainClick(View v) {
    686         // Consume clicks
    687     }
    688 
    689     @Override
    690     public void onShutterButtonClick() {
    691         if (mSwitchingCamera) {
    692             return;
    693         }
    694         boolean stop = mMediaRecorderRecording;
    695 
    696         if (stop) {
    697             onStopVideoRecording();
    698         } else {
    699             startVideoRecording();
    700         }
    701         mAppController.setShutterEnabled(false);
    702         if (mCameraSettings != null) {
    703             mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());
    704         }
    705 
    706         // Keep the shutter button disabled when in video capture intent
    707         // mode and recording is stopped. It'll be re-enabled when
    708         // re-take button is clicked.
    709         if (!(mIsVideoCaptureIntent && stop)) {
    710             mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
    711         }
    712     }
    713 
    714     @Override
    715     public void onShutterCoordinate(TouchCoordinate coord) {
    716         // Do nothing.
    717     }
    718 
    719     @Override
    720     public void onShutterButtonFocus(boolean pressed) {
    721         // TODO: Remove this when old camera controls are removed from the UI.
    722     }
    723 
    724     private void readVideoPreferences() {
    725         // The preference stores values from ListPreference and is thus string type for all values.
    726         // We need to convert it to int manually.
    727         SettingsManager settingsManager = mActivity.getSettingsManager();
    728         String videoQualityKey = isCameraFrontFacing() ? Keys.KEY_VIDEO_QUALITY_FRONT
    729             : Keys.KEY_VIDEO_QUALITY_BACK;
    730         String videoQuality = settingsManager
    731                 .getString(SettingsManager.SCOPE_GLOBAL, videoQualityKey);
    732         int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
    733         Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
    734 
    735         // Set video quality.
    736         Intent intent = mActivity.getIntent();
    737         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
    738             int extraVideoQuality =
    739                     intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
    740             if (extraVideoQuality > 0) {
    741                 quality = CamcorderProfile.QUALITY_HIGH;
    742             } else {  // 0 is mms.
    743                 quality = CamcorderProfile.QUALITY_LOW;
    744             }
    745         }
    746 
    747         // Set video duration limit. The limit is read from the preference,
    748         // unless it is specified in the intent.
    749         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
    750             int seconds =
    751                     intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
    752             mMaxVideoDurationInMs = 1000 * seconds;
    753         } else {
    754             mMaxVideoDurationInMs = SettingsUtil.getMaxVideoDuration(mActivity
    755                     .getAndroidContext());
    756         }
    757 
    758         // If quality is not supported, request QUALITY_HIGH which is always supported.
    759         if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
    760             quality = CamcorderProfile.QUALITY_HIGH;
    761         }
    762         mProfile = CamcorderProfile.get(mCameraId, quality);
    763         mPreferenceRead = true;
    764         if (mCameraDevice == null) {
    765             return;
    766         }
    767         mCameraSettings = mCameraDevice.getSettings();
    768         Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
    769                 mCameraSettings, mCameraCapabilities, mProfile, mUI.getPreviewScreenSize());
    770         mDesiredPreviewWidth = desiredPreviewSize.x;
    771         mDesiredPreviewHeight = desiredPreviewSize.y;
    772         mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
    773         Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
    774                 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
    775     }
    776 
    777     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    778     /**
    779      * Calculates the preview size and stores it in mDesiredPreviewWidth and
    780      * mDesiredPreviewHeight.
    781      *
    782      * <p>This function checks {@link
    783      * com.android.camera.cameradevice.CameraCapabilities#getPreferredPreviewSizeForVideo()}
    784      * but also considers the current preview area size on screen and make sure
    785      * the final preview size will not be smaller than 1/2 of the current
    786      * on screen preview area in terms of their short sides.</p>
    787      *
    788      * @return The preferred preview size or {@code null} if the camera is not
    789      *         opened yet.
    790      */
    791     private static Point getDesiredPreviewSize(Context context, CameraSettings settings,
    792             CameraCapabilities capabilities, CamcorderProfile profile, Point previewScreenSize) {
    793         if (capabilities.getSupportedVideoSizes() == null) {
    794             // Driver doesn't support separate outputs for preview and video.
    795             return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
    796         }
    797 
    798         final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
    799                 previewScreenSize.x : previewScreenSize.y);
    800         List<Size> sizes = capabilities.getSupportedPreviewSizes();
    801         Size preferred = capabilities.getPreferredPreviewSizeForVideo();
    802         final int preferredPreviewSizeShortSide = (preferred.width() < preferred.height() ?
    803                 preferred.width() : preferred.height());
    804         if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
    805             preferred = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
    806         }
    807         int product = preferred.width() * preferred.height();
    808         Iterator<Size> it = sizes.iterator();
    809         // Remove the preview sizes that are not preferred.
    810         while (it.hasNext()) {
    811             Size size = it.next();
    812             if (size.width() * size.height() > product) {
    813                 it.remove();
    814             }
    815         }
    816         Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
    817                 (double) profile.videoFrameWidth / profile.videoFrameHeight);
    818         return new Point(optimalSize.width(), optimalSize.height());
    819     }
    820 
    821     private void resizeForPreviewAspectRatio() {
    822         mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
    823     }
    824 
    825     private void installIntentFilter() {
    826         // install an intent filter to receive SD card related events.
    827         IntentFilter intentFilter =
    828                 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
    829         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    830         intentFilter.addDataScheme("file");
    831         mReceiver = new MyBroadcastReceiver();
    832         mActivity.registerReceiver(mReceiver, intentFilter);
    833     }
    834 
    835     private void setDisplayOrientation() {
    836         mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
    837         Characteristics info =
    838                 mActivity.getCameraProvider().getCharacteristics(mCameraId);
    839         mCameraDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
    840         // Change the camera display orientation
    841         if (mCameraDevice != null) {
    842             mCameraDevice.setDisplayOrientation(mDisplayRotation);
    843         }
    844         if (mFocusManager != null) {
    845             mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
    846         }
    847     }
    848 
    849     @Override
    850     public void updateCameraOrientation() {
    851         if (mMediaRecorderRecording) {
    852             return;
    853         }
    854         if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
    855             setDisplayOrientation();
    856         }
    857     }
    858 
    859     @Override
    860     public void updatePreviewAspectRatio(float aspectRatio) {
    861         mAppController.updatePreviewAspectRatio(aspectRatio);
    862     }
    863 
    864     /**
    865      * Returns current Zoom value, with 1.0 as the value for no zoom.
    866      */
    867     private float currentZoomValue() {
    868         return mCameraSettings.getCurrentZoomRatio();
    869     }
    870 
    871     @Override
    872     public void onZoomChanged(float ratio) {
    873         // Not useful to change zoom value when the activity is paused.
    874         if (mPaused) {
    875             return;
    876         }
    877         mZoomValue = ratio;
    878         if (mCameraSettings == null || mCameraDevice == null) {
    879             return;
    880         }
    881         // Set zoom parameters asynchronously
    882         mCameraSettings.setZoomRatio(mZoomValue);
    883         mCameraDevice.applySettings(mCameraSettings);
    884     }
    885 
    886     private void startPreview() {
    887         Log.i(TAG, "startPreview");
    888 
    889         SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
    890         if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
    891                 mCameraDevice == null) {
    892             return;
    893         }
    894 
    895         mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
    896         if (mPreviewing == true) {
    897             stopPreview();
    898         }
    899 
    900         setDisplayOrientation();
    901         mCameraDevice.setDisplayOrientation(mDisplayRotation);
    902         setCameraParameters();
    903 
    904         if (mFocusManager != null) {
    905             // If the focus mode is continuous autofocus, call cancelAutoFocus
    906             // to resume it because it may have been paused by autoFocus call.
    907             CameraCapabilities.FocusMode focusMode =
    908                     mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode());
    909             if (focusMode == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
    910                 mCameraDevice.cancelAutoFocus();
    911             }
    912         }
    913 
    914         // This is to notify app controller that preview will start next, so app
    915         // controller can set preview callbacks if needed. This has to happen before
    916         // preview is started as a workaround of the framework issue related to preview
    917         // callbacks that causes preview stretch and crash. (More details see b/12210027
    918         // and b/12591410. Don't apply this to L, see b/16649297.
    919         if (!ApiHelper.isLOrHigher()) {
    920             Log.v(TAG, "calling onPreviewReadyToStart to set one shot callback");
    921             mAppController.onPreviewReadyToStart();
    922         } else {
    923             Log.v(TAG, "on L, no one shot callback necessary");
    924         }
    925         try {
    926             mCameraDevice.setPreviewTexture(surfaceTexture);
    927             mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()),
    928                     new CameraAgent.CameraStartPreviewCallback() {
    929                 @Override
    930                 public void onPreviewStarted() {
    931                     VideoModule.this.onPreviewStarted();
    932                 }
    933             });
    934             mPreviewing = true;
    935         } catch (Throwable ex) {
    936             closeCamera();
    937             throw new RuntimeException("startPreview failed", ex);
    938         }
    939     }
    940 
    941     private void onPreviewStarted() {
    942         mAppController.setShutterEnabled(true);
    943         mAppController.onPreviewStarted();
    944         if (mFocusManager != null) {
    945             mFocusManager.onPreviewStarted();
    946         }
    947     }
    948 
    949     @Override
    950     public void onPreviewInitialDataReceived() {
    951     }
    952 
    953     @Override
    954     public void stopPreview() {
    955         if (!mPreviewing) {
    956             Log.v(TAG, "Skip stopPreview since it's not mPreviewing");
    957             return;
    958         }
    959         if (mCameraDevice == null) {
    960             Log.v(TAG, "Skip stopPreview since mCameraDevice is null");
    961             return;
    962         }
    963 
    964         Log.v(TAG, "stopPreview");
    965         mCameraDevice.stopPreview();
    966         if (mFocusManager != null) {
    967             mFocusManager.onPreviewStopped();
    968         }
    969         mPreviewing = false;
    970     }
    971 
    972     private void closeCamera() {
    973         Log.i(TAG, "closeCamera");
    974         if (mCameraDevice == null) {
    975             Log.d(TAG, "already stopped.");
    976             return;
    977         }
    978         mCameraDevice.setZoomChangeListener(null);
    979         mCameraDevice.setErrorCallback(null, null);
    980         mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
    981         mCameraDevice = null;
    982         mPreviewing = false;
    983         mSnapshotInProgress = false;
    984         if (mFocusManager != null) {
    985             mFocusManager.onCameraReleased();
    986         }
    987     }
    988 
    989     @Override
    990     public boolean onBackPressed() {
    991         if (mPaused) {
    992             return true;
    993         }
    994         if (mMediaRecorderRecording) {
    995             onStopVideoRecording();
    996             return true;
    997         } else {
    998             return false;
    999         }
   1000     }
   1001 
   1002     @Override
   1003     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1004         // Do not handle any key if the activity is paused.
   1005         if (mPaused) {
   1006             return true;
   1007         }
   1008 
   1009         switch (keyCode) {
   1010             case KeyEvent.KEYCODE_CAMERA:
   1011                 if (event.getRepeatCount() == 0) {
   1012                     onShutterButtonClick();
   1013                     return true;
   1014                 }
   1015             case KeyEvent.KEYCODE_DPAD_CENTER:
   1016                 if (event.getRepeatCount() == 0) {
   1017                     onShutterButtonClick();
   1018                     return true;
   1019                 }
   1020             case KeyEvent.KEYCODE_MENU:
   1021                 // Consume menu button presses during capture.
   1022                 return mMediaRecorderRecording;
   1023         }
   1024         return false;
   1025     }
   1026 
   1027     @Override
   1028     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1029         switch (keyCode) {
   1030             case KeyEvent.KEYCODE_CAMERA:
   1031                 onShutterButtonClick();
   1032                 return true;
   1033             case KeyEvent.KEYCODE_MENU:
   1034                 // Consume menu button presses during capture.
   1035                 return mMediaRecorderRecording;
   1036         }
   1037         return false;
   1038     }
   1039 
   1040     @Override
   1041     public boolean isVideoCaptureIntent() {
   1042         String action = mActivity.getIntent().getAction();
   1043         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
   1044     }
   1045 
   1046     private void doReturnToCaller(boolean valid) {
   1047         Intent resultIntent = new Intent();
   1048         int resultCode;
   1049         if (valid) {
   1050             resultCode = Activity.RESULT_OK;
   1051             resultIntent.setData(mCurrentVideoUri);
   1052             resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1053         } else {
   1054             resultCode = Activity.RESULT_CANCELED;
   1055         }
   1056         mActivity.setResultEx(resultCode, resultIntent);
   1057         mActivity.finish();
   1058     }
   1059 
   1060     private void cleanupEmptyFile() {
   1061         if (mVideoFilename != null) {
   1062             File f = new File(mVideoFilename);
   1063             if (f.length() == 0 && f.delete()) {
   1064                 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
   1065                 mVideoFilename = null;
   1066             }
   1067         }
   1068     }
   1069 
   1070     // Prepares media recorder.
   1071     private void initializeRecorder() {
   1072         Log.i(TAG, "initializeRecorder: " + Thread.currentThread());
   1073         // If the mCameraDevice is null, then this activity is going to finish
   1074         if (mCameraDevice == null) {
   1075             return;
   1076         }
   1077 
   1078         Intent intent = mActivity.getIntent();
   1079         Bundle myExtras = intent.getExtras();
   1080 
   1081         long requestedSizeLimit = 0;
   1082         closeVideoFileDescriptor();
   1083         mCurrentVideoUriFromMediaSaved = false;
   1084         if (mIsVideoCaptureIntent && myExtras != null) {
   1085             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
   1086             if (saveUri != null) {
   1087                 try {
   1088                     mVideoFileDescriptor =
   1089                             mContentResolver.openFileDescriptor(saveUri, "rw");
   1090                     mCurrentVideoUri = saveUri;
   1091                 } catch (java.io.FileNotFoundException ex) {
   1092                     // invalid uri
   1093                     Log.e(TAG, ex.toString());
   1094                 }
   1095             }
   1096             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
   1097         }
   1098         mMediaRecorder = new MediaRecorder();
   1099         // Unlock the camera object before passing it to media recorder.
   1100         mCameraDevice.unlock();
   1101         mMediaRecorder.setCamera(mCameraDevice.getCamera());
   1102         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
   1103         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
   1104         mMediaRecorder.setProfile(mProfile);
   1105         mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
   1106         mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
   1107 
   1108         setRecordLocation();
   1109 
   1110         // Set output file.
   1111         // Try Uri in the intent first. If it doesn't exist, use our own
   1112         // instead.
   1113         if (mVideoFileDescriptor != null) {
   1114             mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
   1115         } else {
   1116             generateVideoFilename(mProfile.fileFormat);
   1117             mMediaRecorder.setOutputFile(mVideoFilename);
   1118         }
   1119 
   1120         // Set maximum file size.
   1121         long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
   1122         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
   1123             maxFileSize = requestedSizeLimit;
   1124         }
   1125 
   1126         try {
   1127             mMediaRecorder.setMaxFileSize(maxFileSize);
   1128         } catch (RuntimeException exception) {
   1129             // We are going to ignore failure of setMaxFileSize here, as
   1130             // a) The composer selected may simply not support it, or
   1131             // b) The underlying media framework may not handle 64-bit range
   1132             // on the size restriction.
   1133         }
   1134 
   1135         // See com.android.camera.cameradevice.CameraSettings.setPhotoRotationDegrees
   1136         // for documentation.
   1137         // Note that mOrientation here is the device orientation, which is the opposite of
   1138         // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
   1139         // which is the orientation the graphics need to rotate in order to render correctly.
   1140         int rotation = 0;
   1141         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
   1142             Characteristics info =
   1143                     mActivity.getCameraProvider().getCharacteristics(mCameraId);
   1144             if (isCameraFrontFacing()) {
   1145                 rotation = (info.getSensorOrientation() - mOrientation + 360) % 360;
   1146             } else if (isCameraBackFacing()) {
   1147                 rotation = (info.getSensorOrientation() + mOrientation) % 360;
   1148             } else {
   1149                 Log.e(TAG, "Camera is facing unhandled direction");
   1150             }
   1151         }
   1152         mMediaRecorder.setOrientationHint(rotation);
   1153 
   1154         try {
   1155             mMediaRecorder.prepare();
   1156         } catch (IOException e) {
   1157             Log.e(TAG, "prepare failed for " + mVideoFilename, e);
   1158             releaseMediaRecorder();
   1159             throw new RuntimeException(e);
   1160         }
   1161 
   1162         mMediaRecorder.setOnErrorListener(this);
   1163         mMediaRecorder.setOnInfoListener(this);
   1164     }
   1165 
   1166     private static void setCaptureRate(MediaRecorder recorder, double fps) {
   1167         recorder.setCaptureRate(fps);
   1168     }
   1169 
   1170     private void setRecordLocation() {
   1171         Location loc = mLocationManager.getCurrentLocation();
   1172         if (loc != null) {
   1173             mMediaRecorder.setLocation((float) loc.getLatitude(),
   1174                     (float) loc.getLongitude());
   1175         }
   1176     }
   1177 
   1178     private void releaseMediaRecorder() {
   1179         Log.i(TAG, "Releasing media recorder.");
   1180         if (mMediaRecorder != null) {
   1181             cleanupEmptyFile();
   1182             mMediaRecorder.reset();
   1183             mMediaRecorder.release();
   1184             mMediaRecorder = null;
   1185         }
   1186         mVideoFilename = null;
   1187     }
   1188 
   1189     private void generateVideoFilename(int outputFileFormat) {
   1190         long dateTaken = System.currentTimeMillis();
   1191         String title = createName(dateTaken);
   1192         // Used when emailing.
   1193         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
   1194         String mime = convertOutputFormatToMimeType(outputFileFormat);
   1195         String path = Storage.DIRECTORY + '/' + filename;
   1196         String tmpPath = path + ".tmp";
   1197         mCurrentVideoValues = new ContentValues(9);
   1198         mCurrentVideoValues.put(Video.Media.TITLE, title);
   1199         mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
   1200         mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
   1201         mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
   1202         mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
   1203         mCurrentVideoValues.put(Video.Media.DATA, path);
   1204         mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
   1205         mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
   1206         mCurrentVideoValues.put(Video.Media.RESOLUTION,
   1207                 Integer.toString(mProfile.videoFrameWidth) + "x" +
   1208                 Integer.toString(mProfile.videoFrameHeight));
   1209         Location loc = mLocationManager.getCurrentLocation();
   1210         if (loc != null) {
   1211             mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
   1212             mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
   1213         }
   1214         mVideoFilename = tmpPath;
   1215         Log.v(TAG, "New video filename: " + mVideoFilename);
   1216     }
   1217 
   1218     private void logVideoCapture(long duration) {
   1219         String flashSetting = mActivity.getSettingsManager()
   1220                 .getString(mAppController.getCameraScope(),
   1221                            Keys.KEY_VIDEOCAMERA_FLASH_MODE);
   1222         boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
   1223         int width = (Integer) mCurrentVideoValues.get(Video.Media.WIDTH);
   1224         int height = (Integer) mCurrentVideoValues.get(Video.Media.HEIGHT);
   1225         long size = new File(mCurrentVideoFilename).length();
   1226         String name = new File(mCurrentVideoValues.getAsString(Video.Media.DATA)).getName();
   1227         UsageStatistics.instance().videoCaptureDoneEvent(name, duration, isCameraFrontFacing(),
   1228                 currentZoomValue(), width, height, size, flashSetting, gridLinesOn);
   1229     }
   1230 
   1231     private void saveVideo() {
   1232         if (mVideoFileDescriptor == null) {
   1233             long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
   1234             if (duration > 0) {
   1235                 //
   1236             } else {
   1237                 Log.w(TAG, "Video duration <= 0 : " + duration);
   1238             }
   1239             mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length());
   1240             mCurrentVideoValues.put(Video.Media.DURATION, duration);
   1241             getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
   1242                     mCurrentVideoValues, mOnVideoSavedListener, mContentResolver);
   1243             logVideoCapture(duration);
   1244         }
   1245         mCurrentVideoValues = null;
   1246     }
   1247 
   1248     private void deleteVideoFile(String fileName) {
   1249         Log.v(TAG, "Deleting video " + fileName);
   1250         File f = new File(fileName);
   1251         if (!f.delete()) {
   1252             Log.v(TAG, "Could not delete " + fileName);
   1253         }
   1254     }
   1255 
   1256     // from MediaRecorder.OnErrorListener
   1257     @Override
   1258     public void onError(MediaRecorder mr, int what, int extra) {
   1259         Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
   1260         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
   1261             // We may have run out of space on the sdcard.
   1262             stopVideoRecording();
   1263             mActivity.updateStorageSpaceAndHint(null);
   1264         }
   1265     }
   1266 
   1267     // from MediaRecorder.OnInfoListener
   1268     @Override
   1269     public void onInfo(MediaRecorder mr, int what, int extra) {
   1270         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
   1271             if (mMediaRecorderRecording) {
   1272                 onStopVideoRecording();
   1273             }
   1274         } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
   1275             if (mMediaRecorderRecording) {
   1276                 onStopVideoRecording();
   1277             }
   1278 
   1279             // Show the toast.
   1280             Toast.makeText(mActivity, R.string.video_reach_size_limit,
   1281                     Toast.LENGTH_LONG).show();
   1282         }
   1283     }
   1284 
   1285     /*
   1286      * Make sure we're not recording music playing in the background, ask the
   1287      * MediaPlaybackService to pause playback.
   1288      */
   1289     private void pauseAudioPlayback() {
   1290         AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
   1291         am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
   1292     }
   1293 
   1294     // For testing.
   1295     public boolean isRecording() {
   1296         return mMediaRecorderRecording;
   1297     }
   1298 
   1299     private void startVideoRecording() {
   1300         Log.i(TAG, "startVideoRecording: " + Thread.currentThread());
   1301         mUI.cancelAnimations();
   1302         mUI.setSwipingEnabled(false);
   1303         mUI.showFocusUI(false);
   1304         mUI.showVideoRecordingHints(false);
   1305 
   1306         mActivity.updateStorageSpaceAndHint(new CameraActivity.OnStorageUpdateDoneListener() {
   1307             @Override
   1308             public void onStorageUpdateDone(long bytes) {
   1309                 if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
   1310                     Log.w(TAG, "Storage issue, ignore the start request");
   1311                 } else {
   1312                     if (mCameraDevice == null) {
   1313                         Log.v(TAG, "in storage callback after camera closed");
   1314                         return;
   1315                     }
   1316                     if (mPaused == true) {
   1317                         Log.v(TAG, "in storage callback after module paused");
   1318                         return;
   1319                     }
   1320 
   1321                     // Monkey is so fast so it could trigger startVideoRecording twice. To prevent
   1322                     // app crash (b/17313985), do nothing here for the second storage-checking
   1323                     // callback because recording is already started.
   1324                     if (mMediaRecorderRecording) {
   1325                         Log.v(TAG, "in storage callback after recording started");
   1326                         return;
   1327                     }
   1328 
   1329                     mCurrentVideoUri = null;
   1330 
   1331                     initializeRecorder();
   1332                     if (mMediaRecorder == null) {
   1333                         Log.e(TAG, "Fail to initialize media recorder");
   1334                         return;
   1335                     }
   1336 
   1337                     pauseAudioPlayback();
   1338 
   1339                     try {
   1340                         mMediaRecorder.start(); // Recording is now started
   1341                     } catch (RuntimeException e) {
   1342                         Log.e(TAG, "Could not start media recorder. ", e);
   1343                         releaseMediaRecorder();
   1344                         // If start fails, frameworks will not lock the camera for us.
   1345                         mCameraDevice.lock();
   1346                         return;
   1347                     }
   1348                     mAppController.getCameraAppUI().setSwipeEnabled(false);
   1349 
   1350                     // The parameters might have been altered by MediaRecorder already.
   1351                     // We need to force mCameraDevice to refresh before getting it.
   1352                     mCameraDevice.refreshSettings();
   1353                     // The parameters may have been changed by MediaRecorder upon starting
   1354                     // recording. We need to alter the parameters if we support camcorder
   1355                     // zoom. To reduce latency when setting the parameters during zoom, we
   1356                     // update the settings here once.
   1357                     mCameraSettings = mCameraDevice.getSettings();
   1358 
   1359                     mMediaRecorderRecording = true;
   1360                     mActivity.lockOrientation();
   1361                     mRecordingStartTime = SystemClock.uptimeMillis();
   1362 
   1363                     // A special case of mode options closing: during capture it should
   1364                     // not be possible to change mode state.
   1365                     mAppController.getCameraAppUI().hideModeOptions();
   1366                     mAppController.getCameraAppUI().animateBottomBarToVideoStop(R.drawable.ic_stop);
   1367                     mUI.showRecordingUI(true);
   1368 
   1369                     setFocusParameters();
   1370 
   1371                     updateRecordingTime();
   1372                     mActivity.enableKeepScreenOn(true);
   1373                 }
   1374             }
   1375         });
   1376     }
   1377 
   1378     private Bitmap getVideoThumbnail() {
   1379         Bitmap bitmap = null;
   1380         if (mVideoFileDescriptor != null) {
   1381             bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
   1382                     mDesiredPreviewWidth);
   1383         } else if (mCurrentVideoUri != null) {
   1384             try {
   1385                 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
   1386                 bitmap = Thumbnail.createVideoThumbnailBitmap(
   1387                         mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
   1388             } catch (java.io.FileNotFoundException ex) {
   1389                 // invalid uri
   1390                 Log.e(TAG, ex.toString());
   1391             }
   1392         }
   1393 
   1394         if (bitmap != null) {
   1395             // MetadataRetriever already rotates the thumbnail. We should rotate
   1396             // it to match the UI orientation (and mirror if it is front-facing camera).
   1397             bitmap = CameraUtil.rotateAndMirror(bitmap, 0, isCameraFrontFacing());
   1398         }
   1399         return bitmap;
   1400     }
   1401 
   1402     private void showCaptureResult() {
   1403         mIsInReviewMode = true;
   1404         Bitmap bitmap = getVideoThumbnail();
   1405         if (bitmap != null) {
   1406             mUI.showReviewImage(bitmap);
   1407         }
   1408         mUI.showReviewControls();
   1409     }
   1410 
   1411     private boolean stopVideoRecording() {
   1412         // Do nothing if camera device is still capturing photo. Monkey test can trigger app crashes
   1413         // (b/17313985) without this check. Crash could also be reproduced by continuously tapping
   1414         // on shutter button and preview with two fingers.
   1415         if (mSnapshotInProgress) {
   1416             Log.v(TAG, "Skip stopVideoRecording since snapshot in progress");
   1417             return true;
   1418         }
   1419         Log.v(TAG, "stopVideoRecording");
   1420 
   1421         mUI.setSwipingEnabled(true);
   1422         mUI.showFocusUI(true);
   1423         mUI.showVideoRecordingHints(true);
   1424 
   1425         boolean fail = false;
   1426         if (mMediaRecorderRecording) {
   1427             boolean shouldAddToMediaStoreNow = false;
   1428 
   1429             try {
   1430                 mMediaRecorder.setOnErrorListener(null);
   1431                 mMediaRecorder.setOnInfoListener(null);
   1432                 mMediaRecorder.stop();
   1433                 shouldAddToMediaStoreNow = true;
   1434                 mCurrentVideoFilename = mVideoFilename;
   1435                 Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename);
   1436             } catch (RuntimeException e) {
   1437                 Log.e(TAG, "stop fail",  e);
   1438                 if (mVideoFilename != null) {
   1439                     deleteVideoFile(mVideoFilename);
   1440                 }
   1441                 fail = true;
   1442             }
   1443             mMediaRecorderRecording = false;
   1444             mActivity.unlockOrientation();
   1445 
   1446             // If the activity is paused, this means activity is interrupted
   1447             // during recording. Release the camera as soon as possible because
   1448             // face unlock or other applications may need to use the camera.
   1449             if (mPaused) {
   1450                 // b/16300704: Monkey is fast so it could pause the module while recording.
   1451                 // stopPreview should definitely be called before switching off.
   1452                 stopPreview();
   1453 
   1454                 closeCamera();
   1455             }
   1456 
   1457             mUI.showRecordingUI(false);
   1458             // The orientation was fixed during video recording. Now make it
   1459             // reflect the device orientation as video recording is stopped.
   1460             mUI.setOrientationIndicator(0, true);
   1461             mActivity.enableKeepScreenOn(false);
   1462             if (shouldAddToMediaStoreNow && !fail) {
   1463                 if (mVideoFileDescriptor == null) {
   1464                     saveVideo();
   1465                 } else if (mIsVideoCaptureIntent) {
   1466                     // if no file save is needed, we can show the post capture UI now
   1467                     showCaptureResult();
   1468                 }
   1469             }
   1470         }
   1471         // release media recorder
   1472         releaseMediaRecorder();
   1473 
   1474         mAppController.getCameraAppUI().showModeOptions();
   1475         mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
   1476         if (!mPaused && mCameraDevice != null) {
   1477             setFocusParameters();
   1478             mCameraDevice.lock();
   1479             if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
   1480                 stopPreview();
   1481                 // Switch back to use SurfaceTexture for preview.
   1482                 startPreview();
   1483             }
   1484             // Update the parameters here because the parameters might have been altered
   1485             // by MediaRecorder.
   1486             mCameraSettings = mCameraDevice.getSettings();
   1487         }
   1488 
   1489         // Check this in advance of each shot so we don't add to shutter
   1490         // latency. It's true that someone else could write to the SD card
   1491         // in the mean time and fill it, but that could have happened
   1492         // between the shutter press and saving the file too.
   1493         mActivity.updateStorageSpaceAndHint(null);
   1494 
   1495         return fail;
   1496     }
   1497 
   1498     private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
   1499         long seconds = milliSeconds / 1000; // round down to compute seconds
   1500         long minutes = seconds / 60;
   1501         long hours = minutes / 60;
   1502         long remainderMinutes = minutes - (hours * 60);
   1503         long remainderSeconds = seconds - (minutes * 60);
   1504 
   1505         StringBuilder timeStringBuilder = new StringBuilder();
   1506 
   1507         // Hours
   1508         if (hours > 0) {
   1509             if (hours < 10) {
   1510                 timeStringBuilder.append('0');
   1511             }
   1512             timeStringBuilder.append(hours);
   1513 
   1514             timeStringBuilder.append(':');
   1515         }
   1516 
   1517         // Minutes
   1518         if (remainderMinutes < 10) {
   1519             timeStringBuilder.append('0');
   1520         }
   1521         timeStringBuilder.append(remainderMinutes);
   1522         timeStringBuilder.append(':');
   1523 
   1524         // Seconds
   1525         if (remainderSeconds < 10) {
   1526             timeStringBuilder.append('0');
   1527         }
   1528         timeStringBuilder.append(remainderSeconds);
   1529 
   1530         // Centi seconds
   1531         if (displayCentiSeconds) {
   1532             timeStringBuilder.append('.');
   1533             long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
   1534             if (remainderCentiSeconds < 10) {
   1535                 timeStringBuilder.append('0');
   1536             }
   1537             timeStringBuilder.append(remainderCentiSeconds);
   1538         }
   1539 
   1540         return timeStringBuilder.toString();
   1541     }
   1542 
   1543     private void updateRecordingTime() {
   1544         if (!mMediaRecorderRecording) {
   1545             return;
   1546         }
   1547         long now = SystemClock.uptimeMillis();
   1548         long delta = now - mRecordingStartTime;
   1549 
   1550         // Starting a minute before reaching the max duration
   1551         // limit, we'll countdown the remaining time instead.
   1552         boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
   1553                 && delta >= mMaxVideoDurationInMs - 60000);
   1554 
   1555         long deltaAdjusted = delta;
   1556         if (countdownRemainingTime) {
   1557             deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
   1558         }
   1559         String text;
   1560 
   1561         long targetNextUpdateDelay;
   1562 
   1563         text = millisecondToTimeString(deltaAdjusted, false);
   1564         targetNextUpdateDelay = 1000;
   1565 
   1566         mUI.setRecordingTime(text);
   1567 
   1568         if (mRecordingTimeCountsDown != countdownRemainingTime) {
   1569             // Avoid setting the color on every update, do it only
   1570             // when it needs changing.
   1571             mRecordingTimeCountsDown = countdownRemainingTime;
   1572 
   1573             int color = mActivity.getResources().getColor(R.color.recording_time_remaining_text);
   1574 
   1575             mUI.setRecordingTimeTextColor(color);
   1576         }
   1577 
   1578         long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
   1579         mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
   1580     }
   1581 
   1582     private static boolean isSupported(String value, List<String> supported) {
   1583         return supported == null ? false : supported.indexOf(value) >= 0;
   1584     }
   1585 
   1586     @SuppressWarnings("deprecation")
   1587     private void setCameraParameters() {
   1588         SettingsManager settingsManager = mActivity.getSettingsManager();
   1589 
   1590         mCameraSettings.setPreviewSize(new Size(mDesiredPreviewWidth, mDesiredPreviewHeight));
   1591         // This is required for Samsung SGH-I337 and probably other Samsung S4 versions
   1592         if (Build.BRAND.toLowerCase().contains("samsung")) {
   1593             mCameraSettings.setSetting("video-size",
   1594                     mProfile.videoFrameWidth + "x" + mProfile.videoFrameHeight);
   1595         }
   1596         int[] fpsRange =
   1597                 CameraUtil.getMaxPreviewFpsRange(mCameraCapabilities.getSupportedPreviewFpsRange());
   1598         if (fpsRange.length > 0) {
   1599             mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
   1600         } else {
   1601             mCameraSettings.setPreviewFrameRate(mProfile.videoFrameRate);
   1602         }
   1603 
   1604         enableTorchMode(Keys.isCameraBackFacing(settingsManager, mAppController.getModuleScope()));
   1605 
   1606         // Set zoom.
   1607         if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
   1608             mCameraSettings.setZoomRatio(mZoomValue);
   1609         }
   1610         updateFocusParameters();
   1611 
   1612         mCameraSettings.setRecordingHintEnabled(true);
   1613 
   1614         if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
   1615             mCameraSettings.setVideoStabilization(true);
   1616         }
   1617 
   1618         // Set picture size.
   1619         // The logic here is different from the logic in still-mode camera.
   1620         // There we determine the preview size based on the picture size, but
   1621         // here we determine the picture size based on the preview size.
   1622         List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
   1623         Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
   1624                 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
   1625         Size original = new Size(mCameraSettings.getCurrentPhotoSize());
   1626         if (!original.equals(optimalSize)) {
   1627             mCameraSettings.setPhotoSize(optimalSize);
   1628         }
   1629         Log.d(TAG, "Video snapshot size is " + optimalSize);
   1630 
   1631         // Set JPEG quality.
   1632         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
   1633                 CameraProfile.QUALITY_HIGH);
   1634         mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
   1635 
   1636         if (mCameraDevice != null) {
   1637             mCameraDevice.applySettings(mCameraSettings);
   1638             // Nexus 5 through KitKat 4.4.2 requires a second call to
   1639             // .setParameters() for frame rate settings to take effect.
   1640             mCameraDevice.applySettings(mCameraSettings);
   1641         }
   1642 
   1643         // Update UI based on the new parameters.
   1644         mUI.updateOnScreenIndicators(mCameraSettings);
   1645     }
   1646 
   1647     private void updateFocusParameters() {
   1648         // Set continuous autofocus. During recording, we use "continuous-video"
   1649         // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
   1650         // before recording starts) we use "continuous-picture" auto focus mode
   1651         // for faster but slightly jittery focusing.
   1652         Set<CameraCapabilities.FocusMode> supportedFocus = mCameraCapabilities
   1653                 .getSupportedFocusModes();
   1654         if (mMediaRecorderRecording) {
   1655             if (mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO)) {
   1656                 mCameraSettings.setFocusMode(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO);
   1657                 mFocusManager.overrideFocusMode(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO);
   1658             } else {
   1659                 mFocusManager.overrideFocusMode(null);
   1660             }
   1661         } else {
   1662             // FIXME(b/16984793): This is broken. For some reasons, CONTINUOUS_PICTURE is not on
   1663             // when preview starts.
   1664             mFocusManager.overrideFocusMode(null);
   1665             if (mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE)) {
   1666                 mCameraSettings.setFocusMode(
   1667                         mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
   1668                 if (mFocusAreaSupported) {
   1669                     mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
   1670                 }
   1671             }
   1672         }
   1673         updateAutoFocusMoveCallback();
   1674     }
   1675 
   1676     @Override
   1677     public void resume() {
   1678         if (isVideoCaptureIntent()) {
   1679             mDontResetIntentUiOnResume = mPaused;
   1680         }
   1681 
   1682         mPaused = false;
   1683         installIntentFilter();
   1684         mAppController.setShutterEnabled(false);
   1685         mZoomValue = 1.0f;
   1686 
   1687         showVideoSnapshotUI(false);
   1688 
   1689         if (!mPreviewing) {
   1690             requestCamera(mCameraId);
   1691         } else {
   1692             // preview already started
   1693             mAppController.setShutterEnabled(true);
   1694         }
   1695 
   1696         if (mFocusManager != null) {
   1697             // If camera is not open when resume is called, focus manager will not
   1698             // be initialized yet, in which case it will start listening to
   1699             // preview area size change later in the initialization.
   1700             mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
   1701         }
   1702 
   1703         if (mPreviewing) {
   1704             mOnResumeTime = SystemClock.uptimeMillis();
   1705             mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
   1706         }
   1707         getServices().getMemoryManager().addListener(this);
   1708     }
   1709 
   1710     @Override
   1711     public void pause() {
   1712         mPaused = true;
   1713 
   1714         if (mFocusManager != null) {
   1715             // If camera is not open when resume is called, focus manager will not
   1716             // be initialized yet, in which case it will start listening to
   1717             // preview area size change later in the initialization.
   1718             mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
   1719             mFocusManager.removeMessages();
   1720         }
   1721         if (mMediaRecorderRecording) {
   1722             // Camera will be released in onStopVideoRecording.
   1723             onStopVideoRecording();
   1724         } else {
   1725             stopPreview();
   1726             closeCamera();
   1727             releaseMediaRecorder();
   1728         }
   1729 
   1730         closeVideoFileDescriptor();
   1731 
   1732         if (mReceiver != null) {
   1733             mActivity.unregisterReceiver(mReceiver);
   1734             mReceiver = null;
   1735         }
   1736 
   1737         mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
   1738         mHandler.removeMessages(MSG_SWITCH_CAMERA);
   1739         mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
   1740         mPendingSwitchCameraId = -1;
   1741         mSwitchingCamera = false;
   1742         mPreferenceRead = false;
   1743         getServices().getMemoryManager().removeListener(this);
   1744         mUI.onPause();
   1745     }
   1746 
   1747     @Override
   1748     public void destroy() {
   1749 
   1750     }
   1751 
   1752     @Override
   1753     public void onLayoutOrientationChanged(boolean isLandscape) {
   1754         setDisplayOrientation();
   1755     }
   1756 
   1757     // TODO: integrate this into the SettingsManager listeners.
   1758     public void onSharedPreferenceChanged() {
   1759 
   1760     }
   1761 
   1762     private void switchCamera() {
   1763         if (mPaused)  {
   1764             return;
   1765         }
   1766         SettingsManager settingsManager = mActivity.getSettingsManager();
   1767 
   1768         Log.d(TAG, "Start to switch camera.");
   1769         mCameraId = mPendingSwitchCameraId;
   1770         mPendingSwitchCameraId = -1;
   1771         settingsManager.set(mAppController.getModuleScope(),
   1772                             Keys.KEY_CAMERA_ID, mCameraId);
   1773 
   1774         if (mFocusManager != null) {
   1775             mFocusManager.removeMessages();
   1776         }
   1777         closeCamera();
   1778         requestCamera(mCameraId);
   1779 
   1780         mMirror = isCameraFrontFacing();
   1781         if (mFocusManager != null) {
   1782             mFocusManager.setMirror(mMirror);
   1783         }
   1784 
   1785         // From onResume
   1786         mZoomValue = 1.0f;
   1787         mUI.setOrientationIndicator(0, false);
   1788 
   1789         // Start switch camera animation. Post a message because
   1790         // onFrameAvailable from the old camera may already exist.
   1791         mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
   1792         mUI.updateOnScreenIndicators(mCameraSettings);
   1793     }
   1794 
   1795     private void initializeVideoSnapshot() {
   1796         if (mCameraSettings == null) {
   1797             return;
   1798         }
   1799     }
   1800 
   1801     void showVideoSnapshotUI(boolean enabled) {
   1802         if (mCameraSettings == null) {
   1803             return;
   1804         }
   1805         if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_SNAPSHOT) &&
   1806                 !mIsVideoCaptureIntent) {
   1807             if (enabled) {
   1808                 mUI.animateFlash();
   1809             } else {
   1810                 mUI.showPreviewBorder(enabled);
   1811             }
   1812             mAppController.setShutterEnabled(!enabled);
   1813         }
   1814     }
   1815 
   1816     /**
   1817      * Used to update the flash mode. Video mode can turn on the flash as torch
   1818      * mode, which we would like to turn on and off when we switching in and
   1819      * out to the preview.
   1820      *
   1821      * @param enable Whether torch mode can be enabled.
   1822      */
   1823     private void enableTorchMode(boolean enable) {
   1824         if (mCameraSettings.getCurrentFlashMode() == null) {
   1825             return;
   1826         }
   1827 
   1828         SettingsManager settingsManager = mActivity.getSettingsManager();
   1829 
   1830         CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
   1831         CameraCapabilities.FlashMode flashMode;
   1832         if (enable) {
   1833             flashMode = stringifier
   1834                 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
   1835                                                                Keys.KEY_VIDEOCAMERA_FLASH_MODE));
   1836         } else {
   1837             flashMode = CameraCapabilities.FlashMode.OFF;
   1838         }
   1839         if (mCameraCapabilities.supports(flashMode)) {
   1840             mCameraSettings.setFlashMode(flashMode);
   1841         }
   1842         /* TODO: Find out how to deal with the following code piece:
   1843         else {
   1844             flashMode = mCameraSettings.getCurrentFlashMode();
   1845             if (flashMode == null) {
   1846                 flashMode = mActivity.getString(
   1847                         R.string.pref_camera_flashmode_no_flash);
   1848                 mParameters.setFlashMode(flashMode);
   1849             }
   1850         }*/
   1851         if (mCameraDevice != null) {
   1852             mCameraDevice.applySettings(mCameraSettings);
   1853         }
   1854         mUI.updateOnScreenIndicators(mCameraSettings);
   1855     }
   1856 
   1857     @Override
   1858     public void onPreviewVisibilityChanged(int visibility) {
   1859         if (mPreviewing) {
   1860             enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
   1861         }
   1862     }
   1863 
   1864     private final class JpegPictureCallback implements CameraPictureCallback {
   1865         Location mLocation;
   1866 
   1867         public JpegPictureCallback(Location loc) {
   1868             mLocation = loc;
   1869         }
   1870 
   1871         @Override
   1872         public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
   1873             Log.i(TAG, "Video snapshot taken.");
   1874             mSnapshotInProgress = false;
   1875             showVideoSnapshotUI(false);
   1876             storeImage(jpegData, mLocation);
   1877         }
   1878     }
   1879 
   1880     private void storeImage(final byte[] data, Location loc) {
   1881         long dateTaken = System.currentTimeMillis();
   1882         String title = CameraUtil.createJpegName(dateTaken);
   1883         ExifInterface exif = Exif.getExif(data);
   1884         int orientation = Exif.getOrientation(exif);
   1885 
   1886         String flashSetting = mActivity.getSettingsManager()
   1887             .getString(mAppController.getCameraScope(), Keys.KEY_VIDEOCAMERA_FLASH_MODE);
   1888         Boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
   1889         UsageStatistics.instance().photoCaptureDoneEvent(
   1890                 eventprotos.NavigationChange.Mode.VIDEO_STILL, title + ".jpeg", exif,
   1891                 isCameraFrontFacing(), false, currentZoomValue(), flashSetting, gridLinesOn,
   1892                 null, null, null);
   1893 
   1894         getServices().getMediaSaver().addImage(
   1895                 data, title, dateTaken, loc, orientation,
   1896                 exif, mOnPhotoSavedListener, mContentResolver);
   1897     }
   1898 
   1899     private String convertOutputFormatToMimeType(int outputFileFormat) {
   1900         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   1901             return "video/mp4";
   1902         }
   1903         return "video/3gpp";
   1904     }
   1905 
   1906     private String convertOutputFormatToFileExt(int outputFileFormat) {
   1907         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   1908             return ".mp4";
   1909         }
   1910         return ".3gp";
   1911     }
   1912 
   1913     private void closeVideoFileDescriptor() {
   1914         if (mVideoFileDescriptor != null) {
   1915             try {
   1916                 mVideoFileDescriptor.close();
   1917             } catch (IOException e) {
   1918                 Log.e(TAG, "Fail to close fd", e);
   1919             }
   1920             mVideoFileDescriptor = null;
   1921         }
   1922     }
   1923 
   1924     @Override
   1925     public void onPreviewUIReady() {
   1926         startPreview();
   1927     }
   1928 
   1929     @Override
   1930     public void onPreviewUIDestroyed() {
   1931         stopPreview();
   1932     }
   1933 
   1934     @Override
   1935     public void startPreCaptureAnimation() {
   1936         mAppController.startPreCaptureAnimation();
   1937     }
   1938 
   1939     private void requestCamera(int id) {
   1940         mActivity.getCameraProvider().requestCamera(id);
   1941     }
   1942 
   1943     @Override
   1944     public void onMemoryStateChanged(int state) {
   1945         mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
   1946     }
   1947 
   1948     @Override
   1949     public void onLowMemory() {
   1950         // Not much we can do in the video module.
   1951     }
   1952 
   1953     /***********************FocusOverlayManager Listener****************************/
   1954     @Override
   1955     public void autoFocus() {
   1956         if (mCameraDevice != null) {
   1957             mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
   1958         }
   1959     }
   1960 
   1961     @Override
   1962     public void cancelAutoFocus() {
   1963         if (mCameraDevice != null) {
   1964             mCameraDevice.cancelAutoFocus();
   1965             setFocusParameters();
   1966         }
   1967     }
   1968 
   1969     @Override
   1970     public boolean capture() {
   1971         return false;
   1972     }
   1973 
   1974     @Override
   1975     public void startFaceDetection() {
   1976 
   1977     }
   1978 
   1979     @Override
   1980     public void stopFaceDetection() {
   1981 
   1982     }
   1983 
   1984     @Override
   1985     public void setFocusParameters() {
   1986         if (mCameraDevice != null) {
   1987             updateFocusParameters();
   1988             mCameraDevice.applySettings(mCameraSettings);
   1989         }
   1990     }
   1991 }
   1992