Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.camera;
     18 
     19 import android.content.ActivityNotFoundException;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ContentResolver;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.SharedPreferences.Editor;
     27 import android.content.pm.ActivityInfo;
     28 import android.content.res.Configuration;
     29 import android.filterpacks.videosink.MediaRecorderStopException;
     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.PictureCallback;
     35 import android.hardware.Camera.Size;
     36 import android.location.Location;
     37 import android.media.CamcorderProfile;
     38 import android.media.CameraProfile;
     39 import android.media.MediaRecorder;
     40 import android.net.Uri;
     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.Video;
     48 import android.util.Log;
     49 import android.view.KeyEvent;
     50 import android.view.LayoutInflater;
     51 import android.view.MotionEvent;
     52 import android.view.OrientationEventListener;
     53 import android.view.Surface;
     54 import android.view.View;
     55 import android.view.WindowManager;
     56 import android.widget.ImageView;
     57 import android.widget.LinearLayout;
     58 import android.widget.TextView;
     59 import android.widget.Toast;
     60 
     61 import com.android.camera.ActivityBase.CameraOpenThread;
     62 import com.android.camera.ui.CameraPicker;
     63 import com.android.camera.ui.IndicatorControlContainer;
     64 import com.android.camera.ui.IndicatorControlWheelContainer;
     65 import com.android.camera.ui.PopupManager;
     66 import com.android.camera.ui.Rotatable;
     67 import com.android.camera.ui.RotateImageView;
     68 import com.android.camera.ui.RotateLayout;
     69 import com.android.camera.ui.RotateTextToast;
     70 import com.android.camera.ui.TwoStateImageView;
     71 import com.android.camera.ui.ZoomControl;
     72 
     73 import java.io.File;
     74 import java.io.IOException;
     75 import java.text.SimpleDateFormat;
     76 import java.util.Date;
     77 import java.util.Iterator;
     78 import java.util.List;
     79 
     80 /**
     81  * The Camcorder activity.
     82  */
     83 public class VideoCamera extends ActivityBase
     84         implements CameraPreference.OnPreferenceChangedListener,
     85         ShutterButton.OnShutterButtonListener, MediaRecorder.OnErrorListener,
     86         MediaRecorder.OnInfoListener, ModePicker.OnModeChangeListener,
     87         EffectsRecorder.EffectsListener {
     88 
     89     private static final String TAG = "videocamera";
     90 
     91     // We number the request code from 1000 to avoid collision with Gallery.
     92     private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
     93 
     94     private static final int CHECK_DISPLAY_ROTATION = 3;
     95     private static final int CLEAR_SCREEN_DELAY = 4;
     96     private static final int UPDATE_RECORD_TIME = 5;
     97     private static final int ENABLE_SHUTTER_BUTTON = 6;
     98     private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
     99     private static final int SWITCH_CAMERA = 8;
    100     private static final int SWITCH_CAMERA_START_ANIMATION = 9;
    101 
    102     private static final int SCREEN_DELAY = 2 * 60 * 1000;
    103 
    104     private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
    105 
    106     /**
    107      * An unpublished intent flag requesting to start recording straight away
    108      * and return as soon as recording is stopped.
    109      * TODO: consider publishing by moving into MediaStore.
    110      */
    111     private static final String EXTRA_QUICK_CAPTURE =
    112             "android.intent.extra.quickCapture";
    113 
    114     private boolean mSnapshotInProgress = false;
    115 
    116     private static final String EFFECT_BG_FROM_GALLERY = "gallery";
    117 
    118     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
    119 
    120     private ComboPreferences mPreferences;
    121     private PreferenceGroup mPreferenceGroup;
    122 
    123     private PreviewFrameLayout mPreviewFrameLayout;
    124     private SurfaceTexture mSurfaceTexture;
    125     private Surface mSurface;
    126     private IndicatorControlContainer mIndicatorControlContainer;
    127     private int mSurfaceWidth;
    128     private int mSurfaceHeight;
    129     private View mReviewControl;
    130     private RotateDialogController mRotateDialog;
    131     private ImageView mCaptureAnimView;
    132 
    133     // An review image having same size as preview. It is displayed when
    134     // recording is stopped in capture intent.
    135     private ImageView mReviewImage;
    136     private Rotatable mReviewCancelButton;
    137     private Rotatable mReviewDoneButton;
    138     private RotateImageView mReviewPlayButton;
    139     private View mReviewRetakeButton;
    140     private ModePicker mModePicker;
    141     private ShutterButton mShutterButton;
    142     private TextView mRecordingTimeView;
    143     private RotateLayout mBgLearningMessageRotater;
    144     private View mBgLearningMessageFrame;
    145     private LinearLayout mLabelsLinearLayout;
    146 
    147     private boolean mIsVideoCaptureIntent;
    148     private boolean mQuickCapture;
    149 
    150     private long mStorageSpace;
    151 
    152     private MediaRecorder mMediaRecorder;
    153     private EffectsRecorder mEffectsRecorder;
    154     private boolean mEffectsDisplayResult;
    155 
    156     private int mEffectType = EffectsRecorder.EFFECT_NONE;
    157     private Object mEffectParameter = null;
    158     private String mEffectUriFromGallery = null;
    159     private String mPrefVideoEffectDefault;
    160     private boolean mResetEffect = true;
    161 
    162     private boolean mSwitchingCamera;
    163     private boolean mMediaRecorderRecording = false;
    164     private long mRecordingStartTime;
    165     private boolean mRecordingTimeCountsDown = false;
    166     private RotateLayout mRecordingTimeRect;
    167     private long mOnResumeTime;
    168     // The video file that the hardware camera is about to record into
    169     // (or is recording into.)
    170     private String mVideoFilename;
    171     private ParcelFileDescriptor mVideoFileDescriptor;
    172 
    173     // The video file that has already been recorded, and that is being
    174     // examined by the user.
    175     private String mCurrentVideoFilename;
    176     private Uri mCurrentVideoUri;
    177     private ContentValues mCurrentVideoValues;
    178 
    179     private CamcorderProfile mProfile;
    180 
    181     // The video duration limit. 0 menas no limit.
    182     private int mMaxVideoDurationInMs;
    183 
    184     // Time Lapse parameters.
    185     private boolean mCaptureTimeLapse = false;
    186     // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
    187     private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
    188     private View mTimeLapseLabel;
    189 
    190     private int mDesiredPreviewWidth;
    191     private int mDesiredPreviewHeight;
    192 
    193     boolean mPreviewing = false; // True if preview is started.
    194     // The display rotation in degrees. This is only valid when mPreviewing is
    195     // true.
    196     private int mDisplayRotation;
    197     private int mCameraDisplayOrientation;
    198 
    199     private ContentResolver mContentResolver;
    200 
    201     private LocationManager mLocationManager;
    202 
    203     private VideoNamer mVideoNamer;
    204 
    205     private final Handler mHandler = new MainHandler();
    206 
    207     private MyOrientationEventListener mOrientationListener;
    208     // The degrees of the device rotated clockwise from its natural orientation.
    209     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
    210     // The orientation compensation for icons and thumbnails. Ex: if the value
    211     // is 90, the UI components should be rotated 90 degrees counter-clockwise.
    212     private int mOrientationCompensation = 0;
    213     // The orientation compensation when we start recording.
    214     private int mOrientationCompensationAtRecordStart;
    215 
    216     private int mZoomValue;  // The current zoom value.
    217     private int mZoomMax;
    218     private ZoomControl mZoomControl;
    219     private boolean mRestoreFlash;  // This is used to check if we need to restore the flash
    220                                     // status when going back from gallery.
    221 
    222     // This Handler is used to post message back onto the main thread of the
    223     // application
    224     private class MainHandler extends Handler {
    225         @Override
    226         public void handleMessage(Message msg) {
    227             switch (msg.what) {
    228 
    229                 case ENABLE_SHUTTER_BUTTON:
    230                     mShutterButton.setEnabled(true);
    231                     break;
    232 
    233                 case CLEAR_SCREEN_DELAY: {
    234                     getWindow().clearFlags(
    235                             WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    236                     break;
    237                 }
    238 
    239                 case UPDATE_RECORD_TIME: {
    240                     updateRecordingTime();
    241                     break;
    242                 }
    243 
    244                 case CHECK_DISPLAY_ROTATION: {
    245                     // Restart the preview if display rotation has changed.
    246                     // Sometimes this happens when the device is held upside
    247                     // down and camera app is opened. Rotation animation will
    248                     // take some time and the rotation value we have got may be
    249                     // wrong. Framework does not have a callback for this now.
    250                     if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation)
    251                             && !mMediaRecorderRecording && !mSwitchingCamera) {
    252                         startPreview();
    253                     }
    254                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
    255                         mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
    256                     }
    257                     break;
    258                 }
    259 
    260                 case SHOW_TAP_TO_SNAPSHOT_TOAST: {
    261                     showTapToSnapshotToast();
    262                     break;
    263                 }
    264 
    265                 case SWITCH_CAMERA: {
    266                     switchCamera();
    267                     break;
    268                 }
    269 
    270                 case SWITCH_CAMERA_START_ANIMATION: {
    271                     mCameraScreenNail.animateSwitchCamera();
    272 
    273                     // Enable all camera controls.
    274                     mSwitchingCamera = false;
    275                     break;
    276                 }
    277 
    278                 default:
    279                     Log.v(TAG, "Unhandled message: " + msg.what);
    280                     break;
    281             }
    282         }
    283     }
    284 
    285     private BroadcastReceiver mReceiver = null;
    286 
    287     private class MyBroadcastReceiver extends BroadcastReceiver {
    288         @Override
    289         public void onReceive(Context context, Intent intent) {
    290             String action = intent.getAction();
    291             if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
    292                 updateAndShowStorageHint();
    293                 stopVideoRecording();
    294             } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
    295                 updateAndShowStorageHint();
    296                 getLastThumbnail();
    297             } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
    298                 // SD card unavailable
    299                 // handled in ACTION_MEDIA_EJECT
    300             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
    301                 Toast.makeText(VideoCamera.this,
    302                         getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
    303             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
    304                 updateAndShowStorageHint();
    305             }
    306         }
    307     }
    308 
    309     private String createName(long dateTaken) {
    310         Date date = new Date(dateTaken);
    311         SimpleDateFormat dateFormat = new SimpleDateFormat(
    312                 getString(R.string.video_file_name_format));
    313 
    314         return dateFormat.format(date);
    315     }
    316 
    317     private int getPreferredCameraId(ComboPreferences preferences) {
    318         int intentCameraId = Util.getCameraFacingIntentExtras(this);
    319         if (intentCameraId != -1) {
    320             // Testing purpose. Launch a specific camera through the intent
    321             // extras.
    322             return intentCameraId;
    323         } else {
    324             return CameraSettings.readPreferredCameraId(preferences);
    325         }
    326     }
    327 
    328     @Override
    329     public void onCreate(Bundle savedInstanceState) {
    330         super.onCreate(savedInstanceState);
    331         mPreferences = new ComboPreferences(this);
    332         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
    333         mCameraId = getPreferredCameraId(mPreferences);
    334 
    335         mPreferences.setLocalId(this, mCameraId);
    336         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
    337 
    338         mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
    339         mPrefVideoEffectDefault = getString(R.string.pref_video_effect_default);
    340         resetEffect();
    341 
    342         /*
    343          * To reduce startup time, we start the preview in another thread.
    344          * We make sure the preview is started at the end of onCreate.
    345          */
    346         CameraOpenThread cameraOpenThread = new CameraOpenThread();
    347         cameraOpenThread.start();
    348 
    349         mContentResolver = getContentResolver();
    350 
    351         setContentView(R.layout.video_camera);
    352         // Surface texture is from camera screen nail and startPreview needs it.
    353         // This must be done before startPreview.
    354         mIsVideoCaptureIntent = isVideoCaptureIntent();
    355         createCameraScreenNail(!mIsVideoCaptureIntent);
    356 
    357         // Make sure camera device is opened.
    358         try {
    359             cameraOpenThread.join();
    360             if (mOpenCameraFail) {
    361                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
    362                 return;
    363             } else if (mCameraDisabled) {
    364                 Util.showErrorAndFinish(this, R.string.camera_disabled);
    365                 return;
    366             }
    367         } catch (InterruptedException ex) {
    368             // ignore
    369         }
    370 
    371         Thread startPreviewThread = new Thread(new Runnable() {
    372             @Override
    373             public void run() {
    374                 readVideoPreferences();
    375                 startPreview();
    376             }
    377         });
    378         startPreviewThread.start();
    379 
    380         initializeControlByIntent();
    381         initializeMiscControls();
    382 
    383         mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
    384         mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
    385         mOrientationListener = new MyOrientationEventListener(this);
    386         mLocationManager = new LocationManager(this, null);
    387 
    388         // Make sure preview is started.
    389         try {
    390             startPreviewThread.join();
    391             if (mOpenCameraFail) {
    392                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
    393                 return;
    394             } else if (mCameraDisabled) {
    395                 Util.showErrorAndFinish(this, R.string.camera_disabled);
    396                 return;
    397             }
    398         } catch (InterruptedException ex) {
    399             // ignore
    400         }
    401 
    402         showTimeLapseUI(mCaptureTimeLapse);
    403         initializeVideoSnapshot();
    404         resizeForPreviewAspectRatio();
    405 
    406         initializeIndicatorControl();
    407     }
    408 
    409     private void loadCameraPreferences() {
    410         CameraSettings settings = new CameraSettings(this, mParameters,
    411                 mCameraId, CameraHolder.instance().getCameraInfo());
    412         // Remove the video quality preference setting when the quality is given in the intent.
    413         mPreferenceGroup = filterPreferenceScreenByIntent(
    414                 settings.getPreferenceGroup(R.xml.video_preferences));
    415     }
    416 
    417     private boolean collapseCameraControls() {
    418         if ((mIndicatorControlContainer != null)
    419                 && mIndicatorControlContainer.dismissSettingPopup()) {
    420             return true;
    421         }
    422         if (mModePicker != null && mModePicker.dismissModeSelection()) return true;
    423         return false;
    424     }
    425 
    426     private void enableCameraControls(boolean enable) {
    427         if (mIndicatorControlContainer != null) {
    428             mIndicatorControlContainer.setEnabled(enable);
    429         }
    430         if (mModePicker != null) mModePicker.setEnabled(enable);
    431     }
    432 
    433     private void initializeIndicatorControl() {
    434         mIndicatorControlContainer =
    435                 (IndicatorControlContainer) findViewById(R.id.indicator_control);
    436         if (mIndicatorControlContainer == null) return;
    437         loadCameraPreferences();
    438 
    439         final String[] SETTING_KEYS = {
    440                     CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
    441                     CameraSettings.KEY_WHITE_BALANCE,
    442                     CameraSettings.KEY_VIDEO_EFFECT,
    443                     CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
    444                     CameraSettings.KEY_VIDEO_QUALITY};
    445         final String[] OTHER_SETTING_KEYS = {
    446                     CameraSettings.KEY_RECORD_LOCATION};
    447 
    448         CameraPicker.setImageResourceId(R.drawable.ic_switch_video_facing_holo_light);
    449         mIndicatorControlContainer.initialize(this, mPreferenceGroup,
    450                 mParameters.isZoomSupported(), SETTING_KEYS, OTHER_SETTING_KEYS);
    451         mIndicatorControlContainer.setListener(this);
    452         mCameraPicker = (CameraPicker) mIndicatorControlContainer.findViewById(
    453                 R.id.camera_picker);
    454 
    455         if (effectsActive()) {
    456             mIndicatorControlContainer.overrideSettings(
    457                     CameraSettings.KEY_VIDEO_QUALITY,
    458                     Integer.toString(CamcorderProfile.QUALITY_480P));
    459         }
    460     }
    461 
    462 
    463     private class MyOrientationEventListener
    464             extends OrientationEventListener {
    465         public MyOrientationEventListener(Context context) {
    466             super(context);
    467         }
    468 
    469         @Override
    470         public void onOrientationChanged(int orientation) {
    471             // We keep the last known orientation. So if the user first orient
    472             // the camera then point the camera to floor or sky, we still have
    473             // the correct orientation.
    474             if (orientation == ORIENTATION_UNKNOWN) return;
    475             int newOrientation = Util.roundOrientation(orientation, mOrientation);
    476 
    477             if (mOrientation != newOrientation) {
    478                 mOrientation = newOrientation;
    479                 // The input of effects recorder is affected by
    480                 // android.hardware.Camera.setDisplayOrientation. Its value only
    481                 // compensates the camera orientation (no Display.getRotation).
    482                 // So the orientation hint here should only consider sensor
    483                 // orientation.
    484                 if (effectsActive()) {
    485                     mEffectsRecorder.setOrientationHint(mOrientation);
    486                 }
    487             }
    488 
    489             // When the screen is unlocked, display rotation may change. Always
    490             // calculate the up-to-date orientationCompensation.
    491             int orientationCompensation =
    492                     (mOrientation + Util.getDisplayRotation(VideoCamera.this)) % 360;
    493 
    494             if (mOrientationCompensation != orientationCompensation) {
    495                 mOrientationCompensation = orientationCompensation;
    496                 // Do not rotate the icons during recording because the video
    497                 // orientation is fixed after recording.
    498                 if (!mMediaRecorderRecording) {
    499                     setOrientationIndicator(mOrientationCompensation, true);
    500                 }
    501             }
    502 
    503             // Show the toast after getting the first orientation changed.
    504             if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
    505                 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
    506                 showTapToSnapshotToast();
    507             }
    508         }
    509     }
    510 
    511     private void setOrientationIndicator(int orientation, boolean animation) {
    512         Rotatable[] indicators = {mThumbnailView, mModePicker,
    513                 mBgLearningMessageRotater, mIndicatorControlContainer,
    514                 mReviewDoneButton, mReviewPlayButton, mRotateDialog};
    515         for (Rotatable indicator : indicators) {
    516             if (indicator != null) indicator.setOrientation(orientation, animation);
    517         }
    518 
    519         // We change the orientation of the review cancel button only for tablet
    520         // UI because there's a label along with the X icon. For phone UI, we
    521         // don't change the orientation because there's only a symmetrical X
    522         // icon.
    523         if (mReviewCancelButton instanceof RotateLayout) {
    524             mReviewCancelButton.setOrientation(orientation, animation);
    525         }
    526 
    527         // We change the orientation of the linearlayout only for phone UI because when in portrait
    528         // the width is not enough.
    529         if (mLabelsLinearLayout != null) {
    530             if (((orientation / 90) & 1) == 0) {
    531                 mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
    532             } else {
    533                 mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
    534             }
    535         }
    536         mRecordingTimeRect.setOrientation(mOrientationCompensation, animation);
    537     }
    538 
    539     private void startPlayVideoActivity() {
    540         Intent intent = new Intent(Intent.ACTION_VIEW);
    541         intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
    542         try {
    543             startActivity(intent);
    544         } catch (ActivityNotFoundException ex) {
    545             Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
    546         }
    547     }
    548 
    549     @OnClickAttr
    550     public void onThumbnailClicked(View v) {
    551         if (!mMediaRecorderRecording && mThumbnail != null
    552                 && !mSwitchingCamera) {
    553             gotoGallery();
    554         }
    555     }
    556 
    557     @OnClickAttr
    558     public void onReviewRetakeClicked(View v) {
    559         deleteCurrentVideo();
    560         hideAlert();
    561     }
    562 
    563     @OnClickAttr
    564     public void onReviewPlayClicked(View v) {
    565         startPlayVideoActivity();
    566     }
    567 
    568     @OnClickAttr
    569     public void onReviewDoneClicked(View v) {
    570         doReturnToCaller(true);
    571     }
    572 
    573     @OnClickAttr
    574     public void onReviewCancelClicked(View v) {
    575         stopVideoRecording();
    576         doReturnToCaller(false);
    577     }
    578 
    579     private void onStopVideoRecording() {
    580         mEffectsDisplayResult = true;
    581         boolean recordFail = stopVideoRecording();
    582         if (mIsVideoCaptureIntent) {
    583             if (!effectsActive()) {
    584                 if (mQuickCapture) {
    585                     doReturnToCaller(!recordFail);
    586                 } else if (!recordFail) {
    587                     showAlert();
    588                 }
    589             }
    590         } else if (!recordFail){
    591             // Start capture animation.
    592             if (!mPaused) mCameraScreenNail.animateCapture(getCameraRotation());
    593             if (!effectsActive()) getThumbnail();
    594         }
    595     }
    596 
    597     private int getCameraRotation() {
    598         return (mOrientationCompensation - mDisplayRotation + 360) % 360;
    599     }
    600 
    601     public void onProtectiveCurtainClick(View v) {
    602         // Consume clicks
    603     }
    604 
    605     @Override
    606     public void onShutterButtonClick() {
    607         if (collapseCameraControls() || mSwitchingCamera) return;
    608 
    609         boolean stop = mMediaRecorderRecording;
    610 
    611         if (stop) {
    612             onStopVideoRecording();
    613         } else {
    614             startVideoRecording();
    615         }
    616         mShutterButton.setEnabled(false);
    617 
    618         // Keep the shutter button disabled when in video capture intent
    619         // mode and recording is stopped. It'll be re-enabled when
    620         // re-take button is clicked.
    621         if (!(mIsVideoCaptureIntent && stop)) {
    622             mHandler.sendEmptyMessageDelayed(
    623                     ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
    624         }
    625     }
    626 
    627     @Override
    628     public void onShutterButtonFocus(boolean pressed) {
    629         // Do nothing (everything happens in onShutterButtonClick).
    630     }
    631 
    632     private void updateAndShowStorageHint() {
    633         mStorageSpace = Storage.getAvailableSpace();
    634         updateStorageHint(mStorageSpace);
    635     }
    636 
    637     private void readVideoPreferences() {
    638         // The preference stores values from ListPreference and is thus string type for all values.
    639         // We need to convert it to int manually.
    640         String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
    641                 getResources().getString(R.string.pref_video_quality_default));
    642         String videoQuality =
    643                 mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
    644                         defaultQuality);
    645         int quality = Integer.valueOf(videoQuality);
    646 
    647         // Set video quality.
    648         Intent intent = getIntent();
    649         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
    650             int extraVideoQuality =
    651                     intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
    652             if (extraVideoQuality > 0) {
    653                 quality = CamcorderProfile.QUALITY_HIGH;
    654             } else {  // 0 is mms.
    655                 quality = CamcorderProfile.QUALITY_LOW;
    656             }
    657         }
    658 
    659         // Set video duration limit. The limit is read from the preference,
    660         // unless it is specified in the intent.
    661         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
    662             int seconds =
    663                     intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
    664             mMaxVideoDurationInMs = 1000 * seconds;
    665         } else {
    666             mMaxVideoDurationInMs = CameraSettings.DEFAULT_VIDEO_DURATION;
    667         }
    668 
    669         // Set effect
    670         mEffectType = CameraSettings.readEffectType(mPreferences);
    671         if (mEffectType != EffectsRecorder.EFFECT_NONE) {
    672             mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
    673             // Set quality to be no higher than 480p.
    674             CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
    675             if (profile.videoFrameHeight > 480) {
    676                 quality = CamcorderProfile.QUALITY_480P;
    677             }
    678             // On initial startup, can get here before indicator control is
    679             // enabled. In that case, UI quality override handled in
    680             // initializeIndicatorControl.
    681             if (mIndicatorControlContainer != null) {
    682                 mIndicatorControlContainer.overrideSettings(
    683                         CameraSettings.KEY_VIDEO_QUALITY,
    684                         Integer.toString(CamcorderProfile.QUALITY_480P));
    685             }
    686         } else {
    687             mEffectParameter = null;
    688             if (mIndicatorControlContainer != null) {
    689                 mIndicatorControlContainer.overrideSettings(
    690                         CameraSettings.KEY_VIDEO_QUALITY,
    691                         null);
    692             }
    693         }
    694         // Read time lapse recording interval.
    695         String frameIntervalStr = mPreferences.getString(
    696                 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
    697                 getString(R.string.pref_video_time_lapse_frame_interval_default));
    698         mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
    699 
    700         mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
    701         // TODO: This should be checked instead directly +1000.
    702         if (mCaptureTimeLapse) quality += 1000;
    703         mProfile = CamcorderProfile.get(mCameraId, quality);
    704         getDesiredPreviewSize();
    705     }
    706 
    707     private void writeDefaultEffectToPrefs()  {
    708         ComboPreferences.Editor editor = mPreferences.edit();
    709         editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
    710                 getString(R.string.pref_video_effect_default));
    711         editor.apply();
    712     }
    713 
    714     private void getDesiredPreviewSize() {
    715         mParameters = mCameraDevice.getParameters();
    716         if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
    717             mDesiredPreviewWidth = mProfile.videoFrameWidth;
    718             mDesiredPreviewHeight = mProfile.videoFrameHeight;
    719         } else {  // Driver supports separates outputs for preview and video.
    720             List<Size> sizes = mParameters.getSupportedPreviewSizes();
    721             Size preferred = mParameters.getPreferredPreviewSizeForVideo();
    722             int product = preferred.width * preferred.height;
    723             Iterator<Size> it = sizes.iterator();
    724             // Remove the preview sizes that are not preferred.
    725             while (it.hasNext()) {
    726                 Size size = it.next();
    727                 if (size.width * size.height > product) {
    728                     it.remove();
    729                 }
    730             }
    731             Size optimalSize = Util.getOptimalPreviewSize(this, sizes,
    732                     (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
    733             mDesiredPreviewWidth = optimalSize.width;
    734             mDesiredPreviewHeight = optimalSize.height;
    735         }
    736         Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
    737                 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
    738     }
    739 
    740     private void resizeForPreviewAspectRatio() {
    741         mPreviewFrameLayout.setAspectRatio(
    742                 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
    743     }
    744 
    745     @Override
    746     protected void onResume() {
    747         mPaused = false;
    748         super.onResume();
    749         if (mOpenCameraFail || mCameraDisabled) return;
    750 
    751         mZoomValue = 0;
    752 
    753         showVideoSnapshotUI(false);
    754 
    755         // Start orientation listener as soon as possible because it takes
    756         // some time to get first orientation.
    757         mOrientationListener.enable();
    758         if (!mPreviewing) {
    759             if (resetEffect()) {
    760                 mBgLearningMessageFrame.setVisibility(View.GONE);
    761                 mIndicatorControlContainer.reloadPreferences();
    762             }
    763             CameraOpenThread cameraOpenThread = new CameraOpenThread();
    764             cameraOpenThread.start();
    765             try {
    766                 cameraOpenThread.join();
    767                 if (mOpenCameraFail) {
    768                     Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
    769                     return;
    770                 } else if (mCameraDisabled) {
    771                     Util.showErrorAndFinish(this, R.string.camera_disabled);
    772                     return;
    773                 }
    774             } catch (InterruptedException ex) {
    775                 // ignore
    776             }
    777 
    778             readVideoPreferences();
    779             resizeForPreviewAspectRatio();
    780             startPreview();
    781         }
    782 
    783         // Initializing it here after the preview is started.
    784         initializeZoom();
    785 
    786         keepScreenOnAwhile();
    787 
    788         // install an intent filter to receive SD card related events.
    789         IntentFilter intentFilter =
    790                 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
    791         intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
    792         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
    793         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    794         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    795         intentFilter.addDataScheme("file");
    796         mReceiver = new MyBroadcastReceiver();
    797         registerReceiver(mReceiver, intentFilter);
    798         mStorageSpace = Storage.getAvailableSpace();
    799 
    800         mHandler.postDelayed(new Runnable() {
    801             @Override
    802             public void run() {
    803                 updateStorageHint(mStorageSpace);
    804             }
    805         }, 200);
    806 
    807         // Initialize location service.
    808         boolean recordLocation = RecordLocationPreference.get(
    809                 mPreferences, mContentResolver);
    810         mLocationManager.recordLocation(recordLocation);
    811 
    812         if (!mIsVideoCaptureIntent) {
    813             getLastThumbnail();
    814             mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
    815         }
    816 
    817         if (mPreviewing) {
    818             mOnResumeTime = SystemClock.uptimeMillis();
    819             mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
    820         }
    821         // Dismiss open menu if exists.
    822         PopupManager.getInstance(this).notifyShowPopup(null);
    823 
    824         mVideoNamer = new VideoNamer();
    825     }
    826 
    827     private void setPreviewTexture() {
    828         try {
    829             if (effectsActive()) {
    830                 mEffectsRecorder.setPreviewSurfaceTexture(mSurfaceTexture, mSurfaceWidth,
    831                     mSurfaceHeight);
    832             } else {
    833                 mCameraDevice.setPreviewTextureAsync(mSurfaceTexture);
    834             }
    835         } catch (Throwable ex) {
    836             closeCamera();
    837             throw new RuntimeException("setPreviewTexture failed", ex);
    838         }
    839     }
    840 
    841     private void setDisplayOrientation() {
    842         mDisplayRotation = Util.getDisplayRotation(this);
    843         mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
    844     }
    845 
    846     private void startPreview() {
    847         Log.v(TAG, "startPreview");
    848 
    849         mCameraDevice.setErrorCallback(mErrorCallback);
    850         if (mPreviewing == true) {
    851             stopPreview();
    852             if (effectsActive() && mEffectsRecorder != null) {
    853                 mEffectsRecorder.release();
    854             }
    855         }
    856 
    857         setDisplayOrientation();
    858         mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
    859         setCameraParameters();
    860 
    861         try {
    862             if (!effectsActive()) {
    863                 mCameraDevice.setPreviewTextureAsync(mSurfaceTexture);
    864                 mCameraDevice.startPreviewAsync();
    865             } else {
    866                 mSurfaceWidth = mCameraScreenNail.getWidth();
    867                 mSurfaceHeight = mCameraScreenNail.getHeight();
    868                 initializeEffectsPreview();
    869                 mEffectsRecorder.startPreview();
    870             }
    871         } catch (Throwable ex) {
    872             closeCamera();
    873             throw new RuntimeException("startPreview or setPreviewSurfaceTexture failed", ex);
    874         }
    875 
    876         mPreviewing = true;
    877     }
    878 
    879     private void stopPreview() {
    880         mCameraDevice.stopPreview();
    881         mPreviewing = false;
    882     }
    883 
    884     // Closing the effects out. Will shut down the effects graph.
    885     private void closeEffects() {
    886         Log.v(TAG, "Closing effects");
    887         if (mEffectsRecorder == null) {
    888             Log.d(TAG, "Effects are already closed. Nothing to do");
    889         }
    890         // This call can handle the case where the camera is already released
    891         // after the recording has been stopped.
    892         mEffectsRecorder.release();
    893         mEffectType = EffectsRecorder.EFFECT_NONE;
    894     }
    895 
    896     // By default, we want to close the effects as well with the camera.
    897     private void closeCamera() {
    898         closeCamera(true);
    899     }
    900 
    901     // In certain cases, when the effects are active, we may want to shutdown
    902     // only the camera related parts, and handle closing the effects in the
    903     // effectsUpdate callback.
    904     // For example, in onPause, we want to make the camera available to
    905     // outside world immediately, however, want to wait till the effects
    906     // callback to shut down the effects. In such a case, we just disconnect
    907     // the effects from the camera by calling disconnectCamera. That way
    908     // the effects can handle that when shutting down.
    909     //
    910     // @param closeEffectsAlso - indicates whether we want to close the
    911     // effects also along with the camera.
    912     private void closeCamera(boolean closeEffectsAlso) {
    913         Log.v(TAG, "closeCamera");
    914         if (mCameraDevice == null) {
    915             Log.d(TAG, "already stopped.");
    916             return;
    917         }
    918 
    919         if (mEffectsRecorder != null) {
    920             // Disconnect the camera from effects so that camera is ready to
    921             // be released to the outside world.
    922             mEffectsRecorder.disconnectCamera();
    923             if (closeEffectsAlso) {
    924                 closeEffects();
    925             }
    926         }
    927         mCameraDevice.setZoomChangeListener(null);
    928         mCameraDevice.setErrorCallback(null);
    929         CameraHolder.instance().release();
    930         mCameraDevice = null;
    931         mPreviewing = false;
    932         mSnapshotInProgress = false;
    933     }
    934 
    935     @Override
    936     protected void onPause() {
    937         mPaused = true;
    938 
    939         if (mMediaRecorderRecording) {
    940             // Camera will be released in onStopVideoRecording.
    941             onStopVideoRecording();
    942         } else {
    943             closeCamera();
    944             if (!effectsActive()) releaseMediaRecorder();
    945         }
    946         if (mSurfaceTexture != null) {
    947             mCameraScreenNail.releaseSurfaceTexture();
    948             mSurfaceTexture = null;
    949         }
    950         if (effectsActive()) {
    951             // If the effects are active, make sure we tell the graph that the
    952             // surfacetexture is not valid anymore. Disconnect the graph from the
    953             // display.
    954             mEffectsRecorder.disconnectDisplay();
    955         } else {
    956             // Close the file descriptor and clear the video namer only if the
    957             // effects are not active. If effects are active, we need to wait
    958             // till we get the callback from the Effects that the graph is done
    959             // recording. That also needs a change in the stopVideoRecording()
    960             // call to not call closeCamera if the effects are active, because
    961             // that will close down the effects are well, thus making this if
    962             // condition invalid.
    963             closeVideoFileDescriptor();
    964             clearVideoNamer();
    965         }
    966 
    967         if (mReceiver != null) {
    968             unregisterReceiver(mReceiver);
    969             mReceiver = null;
    970         }
    971         resetScreenOn();
    972         if (mIndicatorControlContainer != null) {
    973             mIndicatorControlContainer.dismissSettingPopup();
    974         }
    975 
    976         if (mOrientationListener != null) mOrientationListener.disable();
    977         if (mLocationManager != null) mLocationManager.recordLocation(false);
    978 
    979         mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
    980         mHandler.removeMessages(SWITCH_CAMERA);
    981         mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
    982         mPendingSwitchCameraId = -1;
    983         mSwitchingCamera = false;
    984         // Call onPause after stopping video recording. So the camera can be
    985         // released as soon as possible.
    986         super.onPause();
    987     }
    988 
    989     @Override
    990     public void onUserInteraction() {
    991         super.onUserInteraction();
    992         if (!mMediaRecorderRecording) keepScreenOnAwhile();
    993     }
    994 
    995     @Override
    996     public void onBackPressed() {
    997         if (mPaused) return;
    998         if (mMediaRecorderRecording) {
    999             onStopVideoRecording();
   1000         } else if (!collapseCameraControls()) {
   1001             super.onBackPressed();
   1002         }
   1003     }
   1004 
   1005     @Override
   1006     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1007         // Do not handle any key if the activity is paused.
   1008         if (mPaused) {
   1009             return true;
   1010         }
   1011 
   1012         switch (keyCode) {
   1013             case KeyEvent.KEYCODE_CAMERA:
   1014                 if (event.getRepeatCount() == 0) {
   1015                     mShutterButton.performClick();
   1016                     return true;
   1017                 }
   1018                 break;
   1019             case KeyEvent.KEYCODE_DPAD_CENTER:
   1020                 if (event.getRepeatCount() == 0) {
   1021                     mShutterButton.performClick();
   1022                     return true;
   1023                 }
   1024                 break;
   1025             case KeyEvent.KEYCODE_MENU:
   1026                 if (mMediaRecorderRecording) return true;
   1027                 break;
   1028         }
   1029 
   1030         return super.onKeyDown(keyCode, event);
   1031     }
   1032 
   1033     @Override
   1034     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1035         switch (keyCode) {
   1036             case KeyEvent.KEYCODE_CAMERA:
   1037                 mShutterButton.setPressed(false);
   1038                 return true;
   1039         }
   1040         return super.onKeyUp(keyCode, event);
   1041     }
   1042 
   1043     private boolean isVideoCaptureIntent() {
   1044         String action = getIntent().getAction();
   1045         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
   1046     }
   1047 
   1048     private void doReturnToCaller(boolean valid) {
   1049         Intent resultIntent = new Intent();
   1050         int resultCode;
   1051         if (valid) {
   1052             resultCode = RESULT_OK;
   1053             resultIntent.setData(mCurrentVideoUri);
   1054         } else {
   1055             resultCode = RESULT_CANCELED;
   1056         }
   1057         setResultEx(resultCode, resultIntent);
   1058         finish();
   1059     }
   1060 
   1061     private void cleanupEmptyFile() {
   1062         if (mVideoFilename != null) {
   1063             File f = new File(mVideoFilename);
   1064             if (f.length() == 0 && f.delete()) {
   1065                 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
   1066                 mVideoFilename = null;
   1067             }
   1068         }
   1069     }
   1070 
   1071     // Prepares media recorder.
   1072     private void initializeRecorder() {
   1073         Log.v(TAG, "initializeRecorder");
   1074         // If the mCameraDevice is null, then this activity is going to finish
   1075         if (mCameraDevice == null) return;
   1076 
   1077         if (mSurfaceTexture == null) {
   1078             Log.v(TAG, "SurfaceTexture is null. Wait for surface changed.");
   1079             return;
   1080         }
   1081 
   1082         Intent intent = getIntent();
   1083         Bundle myExtras = intent.getExtras();
   1084 
   1085         long requestedSizeLimit = 0;
   1086         closeVideoFileDescriptor();
   1087         if (mIsVideoCaptureIntent && myExtras != null) {
   1088             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
   1089             if (saveUri != null) {
   1090                 try {
   1091                     mVideoFileDescriptor =
   1092                             mContentResolver.openFileDescriptor(saveUri, "rw");
   1093                     mCurrentVideoUri = saveUri;
   1094                 } catch (java.io.FileNotFoundException ex) {
   1095                     // invalid uri
   1096                     Log.e(TAG, ex.toString());
   1097                 }
   1098             }
   1099             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
   1100         }
   1101         mMediaRecorder = new MediaRecorder();
   1102 
   1103         // Unlock the camera object before passing it to media recorder.
   1104         mCameraDevice.unlock();
   1105         mMediaRecorder.setCamera(mCameraDevice.getCamera());
   1106         if (!mCaptureTimeLapse) {
   1107             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
   1108         }
   1109         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
   1110         mMediaRecorder.setProfile(mProfile);
   1111         mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
   1112         if (mCaptureTimeLapse) {
   1113             mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
   1114         }
   1115 
   1116         Location loc = mLocationManager.getCurrentLocation();
   1117         if (loc != null) {
   1118             mMediaRecorder.setLocation((float) loc.getLatitude(),
   1119                     (float) loc.getLongitude());
   1120         }
   1121 
   1122 
   1123         // Set output file.
   1124         // Try Uri in the intent first. If it doesn't exist, use our own
   1125         // instead.
   1126         if (mVideoFileDescriptor != null) {
   1127             mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
   1128         } else {
   1129             generateVideoFilename(mProfile.fileFormat);
   1130             mMediaRecorder.setOutputFile(mVideoFilename);
   1131         }
   1132 
   1133         // Set maximum file size.
   1134         long maxFileSize = mStorageSpace - Storage.LOW_STORAGE_THRESHOLD;
   1135         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
   1136             maxFileSize = requestedSizeLimit;
   1137         }
   1138 
   1139         try {
   1140             mMediaRecorder.setMaxFileSize(maxFileSize);
   1141         } catch (RuntimeException exception) {
   1142             // We are going to ignore failure of setMaxFileSize here, as
   1143             // a) The composer selected may simply not support it, or
   1144             // b) The underlying media framework may not handle 64-bit range
   1145             // on the size restriction.
   1146         }
   1147 
   1148         // See android.hardware.Camera.Parameters.setRotation for
   1149         // documentation.
   1150         // Note that mOrientation here is the device orientation, which is the opposite of
   1151         // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
   1152         // which is the orientation the graphics need to rotate in order to render correctly.
   1153         int rotation = 0;
   1154         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
   1155             CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
   1156             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
   1157                 rotation = (info.orientation - mOrientation + 360) % 360;
   1158             } else {  // back-facing camera
   1159                 rotation = (info.orientation + mOrientation) % 360;
   1160             }
   1161         }
   1162         mMediaRecorder.setOrientationHint(rotation);
   1163         mOrientationCompensationAtRecordStart = mOrientationCompensation;
   1164 
   1165         try {
   1166             mMediaRecorder.prepare();
   1167         } catch (IOException e) {
   1168             Log.e(TAG, "prepare failed for " + mVideoFilename, e);
   1169             releaseMediaRecorder();
   1170             throw new RuntimeException(e);
   1171         }
   1172 
   1173         mMediaRecorder.setOnErrorListener(this);
   1174         mMediaRecorder.setOnInfoListener(this);
   1175     }
   1176 
   1177     private void initializeEffectsPreview() {
   1178         Log.v(TAG, "initializeEffectsPreview");
   1179         // If the mCameraDevice is null, then this activity is going to finish
   1180         if (mCameraDevice == null) return;
   1181 
   1182         boolean inLandscape = (getResources().getConfiguration().orientation
   1183                 == Configuration.ORIENTATION_LANDSCAPE);
   1184 
   1185         CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
   1186 
   1187         mEffectsDisplayResult = false;
   1188         mEffectsRecorder = new EffectsRecorder(this);
   1189 
   1190         // TODO: Confirm none of the following need to go to initializeEffectsRecording()
   1191         // and none of these change even when the preview is not refreshed.
   1192         mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
   1193         mEffectsRecorder.setCamera(mCameraDevice.getCamera());
   1194         mEffectsRecorder.setCameraFacing(info.facing);
   1195         mEffectsRecorder.setProfile(mProfile);
   1196         mEffectsRecorder.setEffectsListener(this);
   1197         mEffectsRecorder.setOnInfoListener(this);
   1198         mEffectsRecorder.setOnErrorListener(this);
   1199 
   1200         // The input of effects recorder is affected by
   1201         // android.hardware.Camera.setDisplayOrientation. Its value only
   1202         // compensates the camera orientation (no Display.getRotation). So the
   1203         // orientation hint here should only consider sensor orientation.
   1204         int orientation = 0;
   1205         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
   1206             orientation = mOrientation;
   1207         }
   1208         mEffectsRecorder.setOrientationHint(orientation);
   1209 
   1210         mOrientationCompensationAtRecordStart = mOrientationCompensation;
   1211 
   1212         mEffectsRecorder.setPreviewSurfaceTexture(mSurfaceTexture, mSurfaceWidth, mSurfaceHeight);
   1213 
   1214         if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
   1215                 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
   1216             mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
   1217         } else {
   1218             mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
   1219         }
   1220     }
   1221 
   1222     private void initializeEffectsRecording() {
   1223         Log.v(TAG, "initializeEffectsRecording");
   1224 
   1225         Intent intent = getIntent();
   1226         Bundle myExtras = intent.getExtras();
   1227 
   1228         long requestedSizeLimit = 0;
   1229         closeVideoFileDescriptor();
   1230         if (mIsVideoCaptureIntent && myExtras != null) {
   1231             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
   1232             if (saveUri != null) {
   1233                 try {
   1234                     mVideoFileDescriptor =
   1235                             mContentResolver.openFileDescriptor(saveUri, "rw");
   1236                     mCurrentVideoUri = saveUri;
   1237                 } catch (java.io.FileNotFoundException ex) {
   1238                     // invalid uri
   1239                     Log.e(TAG, ex.toString());
   1240                 }
   1241             }
   1242             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
   1243         }
   1244 
   1245         mEffectsRecorder.setProfile(mProfile);
   1246         // important to set the capture rate to zero if not timelapsed, since the
   1247         // effectsrecorder object does not get created again for each recording
   1248         // session
   1249         if (mCaptureTimeLapse) {
   1250             mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
   1251         } else {
   1252             mEffectsRecorder.setCaptureRate(0);
   1253         }
   1254 
   1255         // Set output file
   1256         if (mVideoFileDescriptor != null) {
   1257             mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
   1258         } else {
   1259             generateVideoFilename(mProfile.fileFormat);
   1260             mEffectsRecorder.setOutputFile(mVideoFilename);
   1261         }
   1262 
   1263         // Set maximum file size.
   1264         long maxFileSize = mStorageSpace - Storage.LOW_STORAGE_THRESHOLD;
   1265         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
   1266             maxFileSize = requestedSizeLimit;
   1267         }
   1268         mEffectsRecorder.setMaxFileSize(maxFileSize);
   1269         mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
   1270     }
   1271 
   1272 
   1273     private void releaseMediaRecorder() {
   1274         Log.v(TAG, "Releasing media recorder.");
   1275         if (mMediaRecorder != null) {
   1276             cleanupEmptyFile();
   1277             mMediaRecorder.reset();
   1278             mMediaRecorder.release();
   1279             mMediaRecorder = null;
   1280         }
   1281         mVideoFilename = null;
   1282     }
   1283 
   1284     private void releaseEffectsRecorder() {
   1285         Log.v(TAG, "Releasing effects recorder.");
   1286         if (mEffectsRecorder != null) {
   1287             cleanupEmptyFile();
   1288             mEffectsRecorder.release();
   1289             mEffectsRecorder = null;
   1290             mEffectType = EffectsRecorder.EFFECT_NONE;
   1291         }
   1292         mVideoFilename = null;
   1293     }
   1294 
   1295     private void generateVideoFilename(int outputFileFormat) {
   1296         long dateTaken = System.currentTimeMillis();
   1297         String title = createName(dateTaken);
   1298         // Used when emailing.
   1299         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
   1300         String mime = convertOutputFormatToMimeType(outputFileFormat);
   1301         String path = Storage.DIRECTORY + '/' + filename;
   1302         String tmpPath = path + ".tmp";
   1303         mCurrentVideoValues = new ContentValues(7);
   1304         mCurrentVideoValues.put(Video.Media.TITLE, title);
   1305         mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
   1306         mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
   1307         mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
   1308         mCurrentVideoValues.put(Video.Media.DATA, path);
   1309         mCurrentVideoValues.put(Video.Media.RESOLUTION,
   1310                 Integer.toString(mProfile.videoFrameWidth) + "x" +
   1311                 Integer.toString(mProfile.videoFrameHeight));
   1312         Location loc = mLocationManager.getCurrentLocation();
   1313         if (loc != null) {
   1314             mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
   1315             mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
   1316         }
   1317         mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues);
   1318         mVideoFilename = tmpPath;
   1319         Log.v(TAG, "New video filename: " + mVideoFilename);
   1320     }
   1321 
   1322     private boolean addVideoToMediaStore() {
   1323         boolean fail = false;
   1324         if (mVideoFileDescriptor == null) {
   1325             mCurrentVideoValues.put(Video.Media.SIZE,
   1326                     new File(mCurrentVideoFilename).length());
   1327             long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
   1328             if (duration > 0) {
   1329                 if (mCaptureTimeLapse) {
   1330                     duration = getTimeLapseVideoLength(duration);
   1331                 }
   1332                 mCurrentVideoValues.put(Video.Media.DURATION, duration);
   1333             } else {
   1334                 Log.w(TAG, "Video duration <= 0 : " + duration);
   1335             }
   1336             try {
   1337                 mCurrentVideoUri = mVideoNamer.getUri();
   1338 
   1339                 // Rename the video file to the final name. This avoids other
   1340                 // apps reading incomplete data.  We need to do it after the
   1341                 // above mVideoNamer.getUri() call, so we are certain that the
   1342                 // previous insert to MediaProvider is completed.
   1343                 String finalName = mCurrentVideoValues.getAsString(
   1344                         Video.Media.DATA);
   1345                 if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) {
   1346                     mCurrentVideoFilename = finalName;
   1347                 }
   1348 
   1349                 mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues
   1350                         , null, null);
   1351                 sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_VIDEO,
   1352                         mCurrentVideoUri));
   1353             } catch (Exception e) {
   1354                 // We failed to insert into the database. This can happen if
   1355                 // the SD card is unmounted.
   1356                 Log.e(TAG, "failed to add video to media store", e);
   1357                 mCurrentVideoUri = null;
   1358                 mCurrentVideoFilename = null;
   1359                 fail = true;
   1360             } finally {
   1361                 Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
   1362             }
   1363         }
   1364         mCurrentVideoValues = null;
   1365         return fail;
   1366     }
   1367 
   1368     private void deleteCurrentVideo() {
   1369         // Remove the video and the uri if the uri is not passed in by intent.
   1370         if (mCurrentVideoFilename != null) {
   1371             deleteVideoFile(mCurrentVideoFilename);
   1372             mCurrentVideoFilename = null;
   1373             if (mCurrentVideoUri != null) {
   1374                 mContentResolver.delete(mCurrentVideoUri, null, null);
   1375                 mCurrentVideoUri = null;
   1376             }
   1377         }
   1378         updateAndShowStorageHint();
   1379     }
   1380 
   1381     private void deleteVideoFile(String fileName) {
   1382         Log.v(TAG, "Deleting video " + fileName);
   1383         File f = new File(fileName);
   1384         if (!f.delete()) {
   1385             Log.v(TAG, "Could not delete " + fileName);
   1386         }
   1387     }
   1388 
   1389     private PreferenceGroup filterPreferenceScreenByIntent(
   1390             PreferenceGroup screen) {
   1391         Intent intent = getIntent();
   1392         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
   1393             CameraSettings.removePreferenceFromScreen(screen,
   1394                     CameraSettings.KEY_VIDEO_QUALITY);
   1395         }
   1396 
   1397         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
   1398             CameraSettings.removePreferenceFromScreen(screen,
   1399                     CameraSettings.KEY_VIDEO_QUALITY);
   1400         }
   1401         return screen;
   1402     }
   1403 
   1404     // from MediaRecorder.OnErrorListener
   1405     @Override
   1406     public void onError(MediaRecorder mr, int what, int extra) {
   1407         Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
   1408         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
   1409             // We may have run out of space on the sdcard.
   1410             stopVideoRecording();
   1411             updateAndShowStorageHint();
   1412         }
   1413     }
   1414 
   1415     // from MediaRecorder.OnInfoListener
   1416     @Override
   1417     public void onInfo(MediaRecorder mr, int what, int extra) {
   1418         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
   1419             if (mMediaRecorderRecording) onStopVideoRecording();
   1420         } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
   1421             if (mMediaRecorderRecording) onStopVideoRecording();
   1422 
   1423             // Show the toast.
   1424             Toast.makeText(this, R.string.video_reach_size_limit,
   1425                     Toast.LENGTH_LONG).show();
   1426         }
   1427     }
   1428 
   1429     /*
   1430      * Make sure we're not recording music playing in the background, ask the
   1431      * MediaPlaybackService to pause playback.
   1432      */
   1433     private void pauseAudioPlayback() {
   1434         // Shamelessly copied from MediaPlaybackService.java, which
   1435         // should be public, but isn't.
   1436         Intent i = new Intent("com.android.music.musicservicecommand");
   1437         i.putExtra("command", "pause");
   1438 
   1439         sendBroadcast(i);
   1440     }
   1441 
   1442     // For testing.
   1443     public boolean isRecording() {
   1444         return mMediaRecorderRecording;
   1445     }
   1446 
   1447     private void startVideoRecording() {
   1448         Log.v(TAG, "startVideoRecording");
   1449         setSwipingEnabled(false);
   1450 
   1451         updateAndShowStorageHint();
   1452         if (mStorageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
   1453             Log.v(TAG, "Storage issue, ignore the start request");
   1454             return;
   1455         }
   1456 
   1457         mCurrentVideoUri = null;
   1458         if (effectsActive()) {
   1459             initializeEffectsRecording();
   1460             if (mEffectsRecorder == null) {
   1461                 Log.e(TAG, "Fail to initialize effect recorder");
   1462                 return;
   1463             }
   1464         } else {
   1465             initializeRecorder();
   1466             if (mMediaRecorder == null) {
   1467                 Log.e(TAG, "Fail to initialize media recorder");
   1468                 return;
   1469             }
   1470         }
   1471 
   1472         pauseAudioPlayback();
   1473 
   1474         if (effectsActive()) {
   1475             try {
   1476                 mEffectsRecorder.startRecording();
   1477             } catch (RuntimeException e) {
   1478                 Log.e(TAG, "Could not start effects recorder. ", e);
   1479                 releaseEffectsRecorder();
   1480                 return;
   1481             }
   1482         } else {
   1483             try {
   1484                 mMediaRecorder.start(); // Recording is now started
   1485             } catch (RuntimeException e) {
   1486                 Log.e(TAG, "Could not start media recorder. ", e);
   1487                 releaseMediaRecorder();
   1488                 // If start fails, frameworks will not lock the camera for us.
   1489                 mCameraDevice.lock();
   1490                 return;
   1491             }
   1492         }
   1493 
   1494         // Parameters may have been changed by media recorder when recording
   1495         // starts. To reduce latency, we do not update mParameters during zoom.
   1496         // Keep this up-to-date now. Otherwise, we may revert the video size
   1497         // unexpectedly.
   1498         mParameters = mCameraDevice.getParameters();
   1499 
   1500         enableCameraControls(false);
   1501 
   1502         mMediaRecorderRecording = true;
   1503         mRecordingStartTime = SystemClock.uptimeMillis();
   1504         showRecordingUI(true);
   1505 
   1506         updateRecordingTime();
   1507         keepScreenOn();
   1508     }
   1509 
   1510     private void showRecordingUI(boolean recording) {
   1511         if (recording) {
   1512             mIndicatorControlContainer.dismissSecondLevelIndicator();
   1513             if (mThumbnailView != null) mThumbnailView.setEnabled(false);
   1514             mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video_recording);
   1515             mRecordingTimeView.setText("");
   1516             mRecordingTimeView.setVisibility(View.VISIBLE);
   1517             if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
   1518             if (mCaptureTimeLapse) {
   1519                 mIndicatorControlContainer.startTimeLapseAnimation(
   1520                         mTimeBetweenTimeLapseFrameCaptureMs,
   1521                         mRecordingStartTime);
   1522             }
   1523         } else {
   1524             if (mThumbnailView != null) mThumbnailView.setEnabled(true);
   1525             mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
   1526             mRecordingTimeView.setVisibility(View.GONE);
   1527             if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
   1528             if (mCaptureTimeLapse) {
   1529                 mIndicatorControlContainer.stopTimeLapseAnimation();
   1530             }
   1531         }
   1532     }
   1533 
   1534     private void getThumbnail() {
   1535         if (mCurrentVideoUri != null) {
   1536             Bitmap videoFrame = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
   1537                     mThumbnailViewWidth);
   1538             if (videoFrame != null) {
   1539                 mThumbnail = Thumbnail.createThumbnail(mCurrentVideoUri, videoFrame, 0);
   1540                 mThumbnailView.setBitmap(mThumbnail.getBitmap());
   1541             }
   1542         }
   1543     }
   1544 
   1545     private void showAlert() {
   1546         Bitmap bitmap = null;
   1547         if (mVideoFileDescriptor != null) {
   1548             bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
   1549                     mPreviewFrameLayout.getWidth());
   1550         } else if (mCurrentVideoFilename != null) {
   1551             bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
   1552                     mPreviewFrameLayout.getWidth());
   1553         }
   1554         if (bitmap != null) {
   1555             // MetadataRetriever already rotates the thumbnail. We should rotate
   1556             // it to match the UI orientation (and mirror if it is front-facing camera).
   1557             CameraInfo[] info = CameraHolder.instance().getCameraInfo();
   1558             boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
   1559             bitmap = Util.rotateAndMirror(bitmap, -mOrientationCompensationAtRecordStart,
   1560                     mirror);
   1561             mReviewImage.setImageBitmap(bitmap);
   1562             mReviewImage.setVisibility(View.VISIBLE);
   1563         }
   1564 
   1565         Util.fadeOut(mShutterButton);
   1566         Util.fadeOut(mIndicatorControlContainer);
   1567 
   1568         Util.fadeIn(mReviewRetakeButton);
   1569         Util.fadeIn((View) mReviewDoneButton);
   1570         Util.fadeIn(mReviewPlayButton);
   1571 
   1572         showTimeLapseUI(false);
   1573     }
   1574 
   1575     private void hideAlert() {
   1576         mReviewImage.setVisibility(View.GONE);
   1577         mShutterButton.setEnabled(true);
   1578         enableCameraControls(true);
   1579 
   1580         Util.fadeOut((View) mReviewDoneButton);
   1581         Util.fadeOut(mReviewRetakeButton);
   1582         Util.fadeOut(mReviewPlayButton);
   1583 
   1584         Util.fadeIn(mShutterButton);
   1585         Util.fadeIn(mIndicatorControlContainer);
   1586 
   1587         if (mCaptureTimeLapse) {
   1588             showTimeLapseUI(true);
   1589         }
   1590     }
   1591 
   1592     private boolean stopVideoRecording() {
   1593         Log.v(TAG, "stopVideoRecording");
   1594         setSwipingEnabled(true);
   1595 
   1596         boolean fail = false;
   1597         if (mMediaRecorderRecording) {
   1598             boolean shouldAddToMediaStoreNow = false;
   1599 
   1600             try {
   1601                 if (effectsActive()) {
   1602                     // This is asynchronous, so we can't add to media store now because thumbnail
   1603                     // may not be ready. In such case addVideoToMediaStore is called later
   1604                     // through a callback from the MediaEncoderFilter to EffectsRecorder,
   1605                     // and then to the VideoCamera.
   1606                     mEffectsRecorder.stopRecording();
   1607                 } else {
   1608                     mMediaRecorder.setOnErrorListener(null);
   1609                     mMediaRecorder.setOnInfoListener(null);
   1610                     mMediaRecorder.stop();
   1611                     shouldAddToMediaStoreNow = true;
   1612                 }
   1613                 mCurrentVideoFilename = mVideoFilename;
   1614                 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
   1615                         + mCurrentVideoFilename);
   1616             } catch (RuntimeException e) {
   1617                 Log.e(TAG, "stop fail",  e);
   1618                 if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
   1619                 fail = true;
   1620             }
   1621             mMediaRecorderRecording = false;
   1622 
   1623             // If the activity is paused, this means activity is interrupted
   1624             // during recording. Release the camera as soon as possible because
   1625             // face unlock or other applications may need to use the camera.
   1626             // However, if the effects are active, then we can only release the
   1627             // camera and cannot release the effects recorder since that will
   1628             // stop the graph. It is possible to separate out the Camera release
   1629             // part and the effects release part. However, the effects recorder
   1630             // does hold on to the camera, hence, it needs to be "disconnected"
   1631             // from the camera in the closeCamera call.
   1632             if (mPaused) {
   1633                 // Closing only the camera part if effects active. Effects will
   1634                 // be closed in the callback from effects.
   1635                 boolean closeEffects = !effectsActive();
   1636                 closeCamera(closeEffects);
   1637             }
   1638 
   1639             showRecordingUI(false);
   1640             if (!mIsVideoCaptureIntent) {
   1641                 enableCameraControls(true);
   1642             }
   1643             // The orientation was fixed during video recording. Now make it
   1644             // reflect the device orientation as video recording is stopped.
   1645             setOrientationIndicator(mOrientationCompensation, true);
   1646             keepScreenOnAwhile();
   1647             if (shouldAddToMediaStoreNow) {
   1648                 if (addVideoToMediaStore()) fail = true;
   1649             }
   1650         }
   1651         // always release media recorder if no effects running
   1652         if (!effectsActive()) {
   1653             releaseMediaRecorder();
   1654         }
   1655         return fail;
   1656     }
   1657 
   1658     private void resetScreenOn() {
   1659         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1660         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1661     }
   1662 
   1663     private void keepScreenOnAwhile() {
   1664         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1665         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1666         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
   1667     }
   1668 
   1669     private void keepScreenOn() {
   1670         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
   1671         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1672     }
   1673 
   1674     private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
   1675         long seconds = milliSeconds / 1000; // round down to compute seconds
   1676         long minutes = seconds / 60;
   1677         long hours = minutes / 60;
   1678         long remainderMinutes = minutes - (hours * 60);
   1679         long remainderSeconds = seconds - (minutes * 60);
   1680 
   1681         StringBuilder timeStringBuilder = new StringBuilder();
   1682 
   1683         // Hours
   1684         if (hours > 0) {
   1685             if (hours < 10) {
   1686                 timeStringBuilder.append('0');
   1687             }
   1688             timeStringBuilder.append(hours);
   1689 
   1690             timeStringBuilder.append(':');
   1691         }
   1692 
   1693         // Minutes
   1694         if (remainderMinutes < 10) {
   1695             timeStringBuilder.append('0');
   1696         }
   1697         timeStringBuilder.append(remainderMinutes);
   1698         timeStringBuilder.append(':');
   1699 
   1700         // Seconds
   1701         if (remainderSeconds < 10) {
   1702             timeStringBuilder.append('0');
   1703         }
   1704         timeStringBuilder.append(remainderSeconds);
   1705 
   1706         // Centi seconds
   1707         if (displayCentiSeconds) {
   1708             timeStringBuilder.append('.');
   1709             long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
   1710             if (remainderCentiSeconds < 10) {
   1711                 timeStringBuilder.append('0');
   1712             }
   1713             timeStringBuilder.append(remainderCentiSeconds);
   1714         }
   1715 
   1716         return timeStringBuilder.toString();
   1717     }
   1718 
   1719     private long getTimeLapseVideoLength(long deltaMs) {
   1720         // For better approximation calculate fractional number of frames captured.
   1721         // This will update the video time at a higher resolution.
   1722         double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
   1723         return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
   1724     }
   1725 
   1726     private void updateRecordingTime() {
   1727         if (!mMediaRecorderRecording) {
   1728             return;
   1729         }
   1730         long now = SystemClock.uptimeMillis();
   1731         long delta = now - mRecordingStartTime;
   1732 
   1733         // Starting a minute before reaching the max duration
   1734         // limit, we'll countdown the remaining time instead.
   1735         boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
   1736                 && delta >= mMaxVideoDurationInMs - 60000);
   1737 
   1738         long deltaAdjusted = delta;
   1739         if (countdownRemainingTime) {
   1740             deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
   1741         }
   1742         String text;
   1743 
   1744         long targetNextUpdateDelay;
   1745         if (!mCaptureTimeLapse) {
   1746             text = millisecondToTimeString(deltaAdjusted, false);
   1747             targetNextUpdateDelay = 1000;
   1748         } else {
   1749             // The length of time lapse video is different from the length
   1750             // of the actual wall clock time elapsed. Display the video length
   1751             // only in format hh:mm:ss.dd, where dd are the centi seconds.
   1752             text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
   1753             targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
   1754         }
   1755 
   1756         mRecordingTimeView.setText(text);
   1757 
   1758         if (mRecordingTimeCountsDown != countdownRemainingTime) {
   1759             // Avoid setting the color on every update, do it only
   1760             // when it needs changing.
   1761             mRecordingTimeCountsDown = countdownRemainingTime;
   1762 
   1763             int color = getResources().getColor(countdownRemainingTime
   1764                     ? R.color.recording_time_remaining_text
   1765                     : R.color.recording_time_elapsed_text);
   1766 
   1767             mRecordingTimeView.setTextColor(color);
   1768         }
   1769 
   1770         long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
   1771         mHandler.sendEmptyMessageDelayed(
   1772                 UPDATE_RECORD_TIME, actualNextUpdateDelay);
   1773     }
   1774 
   1775     private static boolean isSupported(String value, List<String> supported) {
   1776         return supported == null ? false : supported.indexOf(value) >= 0;
   1777     }
   1778 
   1779     @SuppressWarnings("deprecation")
   1780     private void setCameraParameters() {
   1781         mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
   1782         mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
   1783 
   1784         // Set flash mode.
   1785         String flashMode;
   1786         if (mShowCameraAppView) {
   1787             flashMode = mPreferences.getString(
   1788                     CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
   1789                     getString(R.string.pref_camera_video_flashmode_default));
   1790         } else {
   1791             flashMode = Parameters.FLASH_MODE_OFF;
   1792         }
   1793         List<String> supportedFlash = mParameters.getSupportedFlashModes();
   1794         if (isSupported(flashMode, supportedFlash)) {
   1795             mParameters.setFlashMode(flashMode);
   1796         } else {
   1797             flashMode = mParameters.getFlashMode();
   1798             if (flashMode == null) {
   1799                 flashMode = getString(
   1800                         R.string.pref_camera_flashmode_no_flash);
   1801             }
   1802         }
   1803 
   1804         // Set white balance parameter.
   1805         String whiteBalance = mPreferences.getString(
   1806                 CameraSettings.KEY_WHITE_BALANCE,
   1807                 getString(R.string.pref_camera_whitebalance_default));
   1808         if (isSupported(whiteBalance,
   1809                 mParameters.getSupportedWhiteBalance())) {
   1810             mParameters.setWhiteBalance(whiteBalance);
   1811         } else {
   1812             whiteBalance = mParameters.getWhiteBalance();
   1813             if (whiteBalance == null) {
   1814                 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
   1815             }
   1816         }
   1817 
   1818         // Set zoom.
   1819         if (mParameters.isZoomSupported()) {
   1820             mParameters.setZoom(mZoomValue);
   1821         }
   1822 
   1823         // Set continuous autofocus.
   1824         List<String> supportedFocus = mParameters.getSupportedFocusModes();
   1825         if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
   1826             mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
   1827         }
   1828 
   1829         mParameters.setRecordingHint(true);
   1830 
   1831         // Enable video stabilization. Convenience methods not available in API
   1832         // level <= 14
   1833         String vstabSupported = mParameters.get("video-stabilization-supported");
   1834         if ("true".equals(vstabSupported)) {
   1835             mParameters.set("video-stabilization", "true");
   1836         }
   1837 
   1838         // Set picture size.
   1839         // The logic here is different from the logic in still-mode camera.
   1840         // There we determine the preview size based on the picture size, but
   1841         // here we determine the picture size based on the preview size.
   1842         List<Size> supported = mParameters.getSupportedPictureSizes();
   1843         Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
   1844                 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
   1845         Size original = mParameters.getPictureSize();
   1846         if (!original.equals(optimalSize)) {
   1847             mParameters.setPictureSize(optimalSize.width, optimalSize.height);
   1848         }
   1849         Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
   1850                 optimalSize.height);
   1851 
   1852         // Set JPEG quality.
   1853         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
   1854                 CameraProfile.QUALITY_HIGH);
   1855         mParameters.setJpegQuality(jpegQuality);
   1856 
   1857         mCameraDevice.setParameters(mParameters);
   1858         // Keep preview size up to date.
   1859         mParameters = mCameraDevice.getParameters();
   1860 
   1861         updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
   1862     }
   1863 
   1864     private void updateCameraScreenNailSize(int width, int height) {
   1865         if (mCameraDisplayOrientation % 180 != 0) {
   1866             int tmp = width;
   1867             width = height;
   1868             height = tmp;
   1869         }
   1870 
   1871         int oldWidth = mCameraScreenNail.getWidth();
   1872         int oldHeight = mCameraScreenNail.getHeight();
   1873 
   1874         if (oldWidth != width || oldHeight != height) {
   1875             mCameraScreenNail.setSize(width, height);
   1876             notifyScreenNailChanged();
   1877         }
   1878 
   1879         if (mSurfaceTexture == null) {
   1880             mCameraScreenNail.acquireSurfaceTexture();
   1881             mSurfaceTexture = mCameraScreenNail.getSurfaceTexture();
   1882             mSurface = new Surface(mSurfaceTexture);
   1883         }
   1884     }
   1885 
   1886     private void switchToOtherMode(int mode) {
   1887         if (isFinishing()) return;
   1888         if (mThumbnail != null) ThumbnailHolder.keep(mThumbnail);
   1889         MenuHelper.gotoMode(mode, this);
   1890         finish();
   1891     }
   1892 
   1893     @Override
   1894     public void onModeChanged(int mode) {
   1895         if (mode != ModePicker.MODE_VIDEO) switchToOtherMode(mode);
   1896     }
   1897 
   1898     @Override
   1899     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1900         super.onActivityResult(requestCode, resultCode, data);
   1901         switch (requestCode) {
   1902             case REQUEST_EFFECT_BACKDROPPER:
   1903                 if (resultCode == RESULT_OK) {
   1904                     // onActivityResult() runs before onResume(), so this parameter will be
   1905                     // seen by startPreview from onResume()
   1906                     mEffectUriFromGallery = data.getData().toString();
   1907                     Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
   1908                     mResetEffect = false;
   1909                 } else {
   1910                     mEffectUriFromGallery = null;
   1911                     Log.w(TAG, "No URI from gallery");
   1912                     mResetEffect = true;
   1913                 }
   1914                 break;
   1915         }
   1916     }
   1917 
   1918     @Override
   1919     public void onEffectsUpdate(int effectId, int effectMsg) {
   1920         Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
   1921         if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
   1922             // Effects have shut down. Hide learning message if any,
   1923             // and restart regular preview.
   1924             mBgLearningMessageFrame.setVisibility(View.GONE);
   1925             checkQualityAndStartPreview();
   1926         } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
   1927             // This follows the codepath from onStopVideoRecording.
   1928             if (mEffectsDisplayResult && !addVideoToMediaStore()) {
   1929                 if (mIsVideoCaptureIntent) {
   1930                     if (mQuickCapture) {
   1931                         doReturnToCaller(true);
   1932                     } else {
   1933                         showAlert();
   1934                     }
   1935                 } else {
   1936                     getThumbnail();
   1937                 }
   1938             }
   1939             mEffectsDisplayResult = false;
   1940             // In onPause, these were not called if the effects were active. We
   1941             // had to wait till the effects recording is complete to do this.
   1942             if (mPaused) {
   1943                 closeVideoFileDescriptor();
   1944                 clearVideoNamer();
   1945             }
   1946         } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
   1947             // Enable the shutter button once the preview is complete.
   1948             mShutterButton.setEnabled(true);
   1949         } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) {
   1950             switch (effectMsg) {
   1951                 case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING:
   1952                     mBgLearningMessageFrame.setVisibility(View.VISIBLE);
   1953                     break;
   1954                 case EffectsRecorder.EFFECT_MSG_DONE_LEARNING:
   1955                 case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT:
   1956                     mBgLearningMessageFrame.setVisibility(View.GONE);
   1957                     break;
   1958             }
   1959         }
   1960         // In onPause, this was not called if the effects were active. We had to
   1961         // wait till the effects completed to do this.
   1962         if (mPaused) {
   1963             Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
   1964             closeEffects();
   1965         }
   1966     }
   1967 
   1968     public void onCancelBgTraining(View v) {
   1969         // Remove training message
   1970         mBgLearningMessageFrame.setVisibility(View.GONE);
   1971         // Write default effect out to shared prefs
   1972         writeDefaultEffectToPrefs();
   1973         // Tell the indicator controller to redraw based on new shared pref values
   1974         mIndicatorControlContainer.reloadPreferences();
   1975         // Tell VideoCamer to re-init based on new shared pref values.
   1976         onSharedPreferenceChanged();
   1977     }
   1978 
   1979     @Override
   1980     public synchronized void onEffectsError(Exception exception, String fileName) {
   1981         // TODO: Eventually we may want to show the user an error dialog, and then restart the
   1982         // camera and encoder gracefully. For now, we just delete the file and bail out.
   1983         if (fileName != null && new File(fileName).exists()) {
   1984             deleteVideoFile(fileName);
   1985         }
   1986         if (exception instanceof MediaRecorderStopException) {
   1987             Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
   1988             return;
   1989         }
   1990         throw new RuntimeException("Error during recording!", exception);
   1991     }
   1992 
   1993     private void initializeControlByIntent() {
   1994         if (mIsVideoCaptureIntent) {
   1995             // Cannot use RotateImageView for "done" and "cancel" button because
   1996             // the tablet layout uses RotateLayout, which cannot be cast to
   1997             // RotateImageView.
   1998             mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
   1999             mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
   2000             mReviewPlayButton = (RotateImageView) findViewById(R.id.btn_play);
   2001             mReviewRetakeButton = findViewById(R.id.btn_retake);
   2002             findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
   2003 
   2004             // Not grayed out upon disabled, to make the follow-up fade-out
   2005             // effect look smooth. Note that the review done button in tablet
   2006             // layout is not a TwoStateImageView.
   2007             if (mReviewDoneButton instanceof TwoStateImageView) {
   2008                 ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
   2009             }
   2010         } else {
   2011             mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
   2012             mThumbnailView.enableFilter(false);
   2013             mThumbnailView.setVisibility(View.VISIBLE);
   2014             mThumbnailViewWidth = mThumbnailView.getLayoutParams().width;
   2015             mModePicker = (ModePicker) findViewById(R.id.mode_picker);
   2016             mModePicker.setVisibility(View.VISIBLE);
   2017             mModePicker.setOnModeChangeListener(this);
   2018         }
   2019     }
   2020 
   2021     private void initializeMiscControls() {
   2022         mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
   2023         mPreviewFrameLayout.addOnLayoutChangeListener(this);
   2024         mReviewImage = (ImageView) findViewById(R.id.review_image);
   2025 
   2026         mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
   2027         mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
   2028         mShutterButton.setOnShutterButtonListener(this);
   2029         mShutterButton.requestFocus();
   2030 
   2031         // Disable the shutter button if effects are ON since it might take
   2032         // a little more time for the effects preview to be ready. We do not
   2033         // want to allow recording before that happens. The shutter button
   2034         // will be enabled when we get the message from effectsrecorder that
   2035         // the preview is running. This becomes critical when the camera is
   2036         // swapped.
   2037         if (effectsActive()) {
   2038             mShutterButton.setEnabled(false);
   2039         }
   2040 
   2041         mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
   2042         mRecordingTimeRect = (RotateLayout) findViewById(R.id.recording_time_rect);
   2043         mTimeLapseLabel = findViewById(R.id.time_lapse_label);
   2044         // The R.id.labels can only be found in phone layout.
   2045         // That is, mLabelsLinearLayout should be null in tablet layout.
   2046         mLabelsLinearLayout = (LinearLayout) findViewById(R.id.labels);
   2047 
   2048         mBgLearningMessageRotater = (RotateLayout) findViewById(R.id.bg_replace_message);
   2049         mBgLearningMessageFrame = findViewById(R.id.bg_replace_message_frame);
   2050         mCaptureAnimView = (ImageView) findViewById(R.id.capture_anim_view);
   2051     }
   2052 
   2053     @Override
   2054     public void onConfigurationChanged(Configuration newConfig) {
   2055         super.onConfigurationChanged(newConfig);
   2056         setDisplayOrientation();
   2057 
   2058         // Change layout in response to configuration change
   2059         LayoutInflater inflater = getLayoutInflater();
   2060         LinearLayout appRoot = (LinearLayout) findViewById(R.id.camera_app_root);
   2061         appRoot.setOrientation(
   2062                 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
   2063                 ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
   2064         appRoot.removeAllViews();
   2065         inflater.inflate(R.layout.preview_frame_video, appRoot);
   2066         inflater.inflate(R.layout.camera_control, appRoot);
   2067 
   2068         // from onCreate()
   2069         initializeControlByIntent();
   2070         initializeMiscControls();
   2071         showTimeLapseUI(mCaptureTimeLapse);
   2072         initializeVideoSnapshot();
   2073         resizeForPreviewAspectRatio();
   2074         initializeIndicatorControl();
   2075 
   2076         // from onResume()
   2077         showVideoSnapshotUI(false);
   2078         initializeZoom();
   2079         if (!mIsVideoCaptureIntent) {
   2080             updateThumbnailView();
   2081             mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
   2082         }
   2083     }
   2084 
   2085     @Override
   2086     public void onOverriddenPreferencesClicked() {
   2087     }
   2088 
   2089     @Override
   2090     public void onRestorePreferencesClicked() {
   2091         Runnable runnable = new Runnable() {
   2092             @Override
   2093             public void run() {
   2094                 restorePreferences();
   2095             }
   2096         };
   2097         mRotateDialog.showAlertDialog(
   2098                 null,
   2099                 getString(R.string.confirm_restore_message),
   2100                 getString(android.R.string.ok), runnable,
   2101                 getString(android.R.string.cancel), null);
   2102     }
   2103 
   2104     private void restorePreferences() {
   2105         // Reset the zoom. Zoom value is not stored in preference.
   2106         if (mParameters.isZoomSupported()) {
   2107             mZoomValue = 0;
   2108             setCameraParameters();
   2109             mZoomControl.setZoomIndex(0);
   2110         }
   2111 
   2112         if (mIndicatorControlContainer != null) {
   2113             mIndicatorControlContainer.dismissSettingPopup();
   2114             CameraSettings.restorePreferences(this, mPreferences,
   2115                     mParameters);
   2116             mIndicatorControlContainer.reloadPreferences();
   2117             onSharedPreferenceChanged();
   2118         }
   2119     }
   2120 
   2121     private boolean effectsActive() {
   2122         return (mEffectType != EffectsRecorder.EFFECT_NONE);
   2123     }
   2124 
   2125     @Override
   2126     public void onSharedPreferenceChanged() {
   2127         // ignore the events after "onPause()" or preview has not started yet
   2128         if (mPaused) return;
   2129         synchronized (mPreferences) {
   2130             // If mCameraDevice is not ready then we can set the parameter in
   2131             // startPreview().
   2132             if (mCameraDevice == null) return;
   2133 
   2134             boolean recordLocation = RecordLocationPreference.get(
   2135                     mPreferences, mContentResolver);
   2136             mLocationManager.recordLocation(recordLocation);
   2137 
   2138             // Check if the current effects selection has changed
   2139             if (updateEffectSelection()) return;
   2140 
   2141             readVideoPreferences();
   2142             showTimeLapseUI(mCaptureTimeLapse);
   2143             // We need to restart the preview if preview size is changed.
   2144             Size size = mParameters.getPreviewSize();
   2145             if (size.width != mDesiredPreviewWidth
   2146                     || size.height != mDesiredPreviewHeight) {
   2147                 if (!effectsActive()) {
   2148                     stopPreview();
   2149                 } else {
   2150                     mEffectsRecorder.release();
   2151                 }
   2152                 resizeForPreviewAspectRatio();
   2153                 startPreview(); // Parameters will be set in startPreview().
   2154             } else {
   2155                 setCameraParameters();
   2156             }
   2157         }
   2158     }
   2159 
   2160     @Override
   2161     public void onCameraPickerClicked(int cameraId) {
   2162         if (mPaused || mPendingSwitchCameraId != -1) return;
   2163 
   2164         Log.d(TAG, "Start to copy texture.");
   2165         // We need to keep a preview frame for the animation before
   2166         // releasing the camera. This will trigger onPreviewTextureCopied.
   2167         mCameraScreenNail.copyTexture();
   2168         mPendingSwitchCameraId = cameraId;
   2169         // Disable all camera controls.
   2170         mSwitchingCamera = true;
   2171     }
   2172 
   2173     private void switchCamera() {
   2174         if (mPaused) return;
   2175 
   2176         Log.d(TAG, "Start to switch camera.");
   2177         mCameraId = mPendingSwitchCameraId;
   2178         mPendingSwitchCameraId = -1;
   2179         mCameraPicker.setCameraId(mCameraId);
   2180 
   2181         closeCamera();
   2182 
   2183         // Restart the camera and initialize the UI. From onCreate.
   2184         mPreferences.setLocalId(this, mCameraId);
   2185         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
   2186         CameraOpenThread cameraOpenThread = new CameraOpenThread();
   2187         cameraOpenThread.start();
   2188         try {
   2189             cameraOpenThread.join();
   2190         } catch (InterruptedException ex) {
   2191             // ignore
   2192         }
   2193         readVideoPreferences();
   2194         startPreview();
   2195         initializeVideoSnapshot();
   2196         resizeForPreviewAspectRatio();
   2197         initializeIndicatorControl();
   2198 
   2199         // From onResume
   2200         initializeZoom();
   2201         setOrientationIndicator(mOrientationCompensation, false);
   2202 
   2203         // Start switch camera animation. Post a message because
   2204         // onFrameAvailable from the old camera may already exist.
   2205         mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
   2206     }
   2207 
   2208     // Preview texture has been copied. Now camera can be released and the
   2209     // animation can be started.
   2210     @Override
   2211     protected void onPreviewTextureCopied() {
   2212         mHandler.sendEmptyMessage(SWITCH_CAMERA);
   2213     }
   2214 
   2215     private boolean updateEffectSelection() {
   2216         int previousEffectType = mEffectType;
   2217         Object previousEffectParameter = mEffectParameter;
   2218         mEffectType = CameraSettings.readEffectType(mPreferences);
   2219         mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
   2220 
   2221         if (mEffectType == previousEffectType) {
   2222             if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
   2223             if (mEffectParameter.equals(previousEffectParameter)) return false;
   2224         }
   2225         Log.v(TAG, "New effect selection: " + mPreferences.getString(
   2226                 CameraSettings.KEY_VIDEO_EFFECT, "none"));
   2227 
   2228         if (mEffectType == EffectsRecorder.EFFECT_NONE) {
   2229             // Stop effects and return to normal preview
   2230             mEffectsRecorder.stopPreview();
   2231             mPreviewing = false;
   2232             return true;
   2233         }
   2234         if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
   2235             ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
   2236             // Request video from gallery to use for background
   2237             Intent i = new Intent(Intent.ACTION_PICK);
   2238             i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
   2239                              "video/*");
   2240             i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
   2241             startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
   2242             return true;
   2243         }
   2244         if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
   2245             // Stop regular preview and start effects.
   2246             stopPreview();
   2247             checkQualityAndStartPreview();
   2248         } else {
   2249             // Switch currently running effect
   2250             mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
   2251         }
   2252         return true;
   2253     }
   2254 
   2255     // Verifies that the current preview view size is correct before starting
   2256     // preview. If not, resets the surface texture and resizes the view.
   2257     private void checkQualityAndStartPreview() {
   2258         readVideoPreferences();
   2259         showTimeLapseUI(mCaptureTimeLapse);
   2260         Size size = mParameters.getPreviewSize();
   2261         if (size.width != mDesiredPreviewWidth
   2262                 || size.height != mDesiredPreviewHeight) {
   2263             resizeForPreviewAspectRatio();
   2264         }
   2265         // Start up preview again
   2266         startPreview();
   2267     }
   2268 
   2269     private void showTimeLapseUI(boolean enable) {
   2270         if (mTimeLapseLabel != null) {
   2271             mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
   2272         }
   2273     }
   2274 
   2275     @Override
   2276     public boolean dispatchTouchEvent(MotionEvent m) {
   2277         if (mSwitchingCamera) return true;
   2278 
   2279         // Check if the popup window should be dismissed first.
   2280         if (m.getAction() == MotionEvent.ACTION_DOWN) {
   2281             float x = m.getX();
   2282             float y = m.getY();
   2283             // Dismiss the mode selection window if the ACTION_DOWN event is out
   2284             // of its view area.
   2285             if ((mModePicker != null) && !Util.pointInView(x, y, mModePicker)) {
   2286                 mModePicker.dismissModeSelection();
   2287             }
   2288             // Check if the popup window is visible.
   2289             View popup = mIndicatorControlContainer.getActiveSettingPopup();
   2290             if (popup != null) {
   2291                 // Let popup window, indicator control or preview frame handle the
   2292                 // event by themselves. Dismiss the popup window if users touch on
   2293                 // other areas.
   2294                 if (!Util.pointInView(x, y, popup)
   2295                         && !Util.pointInView(x, y, mIndicatorControlContainer)) {
   2296                     mIndicatorControlContainer.dismissSettingPopup();
   2297                 }
   2298             }
   2299         }
   2300 
   2301         return super.dispatchTouchEvent(m);
   2302     }
   2303 
   2304     private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
   2305         @Override
   2306         public void onZoomValueChanged(int index) {
   2307             // Not useful to change zoom value when the activity is paused.
   2308             if (mPaused) return;
   2309 
   2310             mZoomValue = index;
   2311 
   2312             // Set zoom parameters asynchronously
   2313             mParameters.setZoom(mZoomValue);
   2314             mCameraDevice.setParametersAsync(mParameters);
   2315         }
   2316     }
   2317 
   2318     private void initializeZoom() {
   2319         mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
   2320         if (!mParameters.isZoomSupported()) return;
   2321 
   2322         mZoomMax = mParameters.getMaxZoom();
   2323         // Currently we use immediate zoom for fast zooming to get better UX and
   2324         // there is no plan to take advantage of the smooth zoom.
   2325         mZoomControl.setZoomMax(mZoomMax);
   2326         mZoomControl.setZoomIndex(mParameters.getZoom());
   2327         mZoomControl.setOnZoomChangeListener(new ZoomChangeListener());
   2328     }
   2329 
   2330     private void initializeVideoSnapshot() {
   2331         if (mParameters.isVideoSnapshotSupported() && !mIsVideoCaptureIntent) {
   2332             setSingleTapUpListener(mPreviewFrameLayout);
   2333             // Show the tap to focus toast if this is the first start.
   2334             if (mPreferences.getBoolean(
   2335                         CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
   2336                 // Delay the toast for one second to wait for orientation.
   2337                 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
   2338             }
   2339         } else {
   2340             setSingleTapUpListener(null);
   2341         }
   2342     }
   2343 
   2344     void showVideoSnapshotUI(boolean enabled) {
   2345         if (mParameters.isVideoSnapshotSupported() && !mIsVideoCaptureIntent) {
   2346             mPreviewFrameLayout.showBorder(enabled);
   2347             mIndicatorControlContainer.enableZoom(!enabled);
   2348             mShutterButton.setEnabled(!enabled);
   2349         }
   2350     }
   2351 
   2352     // Preview area is touched. Take a picture.
   2353     @Override
   2354     protected void onSingleTapUp(View view, int x, int y) {
   2355         if (mMediaRecorderRecording && effectsActive()) {
   2356             new RotateTextToast(this, R.string.disable_video_snapshot_hint,
   2357                     mOrientation).show();
   2358             return;
   2359         }
   2360 
   2361         if (mPaused || mSnapshotInProgress
   2362                 || !mMediaRecorderRecording || effectsActive()) {
   2363             return;
   2364         }
   2365 
   2366         // Set rotation and gps data.
   2367         int rotation = Util.getJpegRotation(mCameraId, mOrientation);
   2368         mParameters.setRotation(rotation);
   2369         Location loc = mLocationManager.getCurrentLocation();
   2370         Util.setGpsParameters(mParameters, loc);
   2371         mCameraDevice.setParameters(mParameters);
   2372 
   2373         Log.v(TAG, "Video snapshot start");
   2374         mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
   2375         showVideoSnapshotUI(true);
   2376         mSnapshotInProgress = true;
   2377     }
   2378 
   2379     @Override
   2380     protected void updateCameraAppView() {
   2381         super.updateCameraAppView();
   2382 
   2383         if (!mPreviewing || mParameters.getFlashMode() == null) return;
   2384 
   2385         // When going to and back from gallery, we need to turn off/on the flash.
   2386         if (!mShowCameraAppView) {
   2387             if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
   2388                 mRestoreFlash = false;
   2389                 return;
   2390             }
   2391             mRestoreFlash = true;
   2392             setCameraParameters();
   2393         } else if (mRestoreFlash) {
   2394             mRestoreFlash = false;
   2395             setCameraParameters();
   2396         }
   2397     }
   2398 
   2399     private final class JpegPictureCallback implements PictureCallback {
   2400         Location mLocation;
   2401 
   2402         public JpegPictureCallback(Location loc) {
   2403             mLocation = loc;
   2404         }
   2405 
   2406         @Override
   2407         public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
   2408             Log.v(TAG, "onPictureTaken");
   2409             mSnapshotInProgress = false;
   2410             showVideoSnapshotUI(false);
   2411             storeImage(jpegData, mLocation);
   2412         }
   2413     }
   2414 
   2415     private void storeImage(final byte[] data, Location loc) {
   2416         long dateTaken = System.currentTimeMillis();
   2417         String title = Util.createJpegName(dateTaken);
   2418         int orientation = Exif.getOrientation(data);
   2419         Size s = mParameters.getPictureSize();
   2420         Uri uri = Storage.addImage(mContentResolver, title, dateTaken, loc, orientation, data,
   2421                 s.width, s.height);
   2422         if (uri != null) {
   2423             // Create a thumbnail whose width is equal or bigger than that of the preview.
   2424             int ratio = (int) Math.ceil((double) mParameters.getPictureSize().width
   2425                     / mPreviewFrameLayout.getWidth());
   2426             int inSampleSize = Integer.highestOneBit(ratio);
   2427             mThumbnail = Thumbnail.createThumbnail(data, orientation, inSampleSize, uri);
   2428             if (mThumbnail != null) {
   2429                 mThumbnailView.setBitmap(mThumbnail.getBitmap());
   2430             }
   2431             Util.broadcastNewPicture(this, uri);
   2432         }
   2433     }
   2434 
   2435     private boolean resetEffect() {
   2436         if (mResetEffect) {
   2437             String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
   2438                     mPrefVideoEffectDefault);
   2439             if (!mPrefVideoEffectDefault.equals(value)) {
   2440                 writeDefaultEffectToPrefs();
   2441                 return true;
   2442             }
   2443         }
   2444         mResetEffect = true;
   2445         return false;
   2446     }
   2447 
   2448     private String convertOutputFormatToMimeType(int outputFileFormat) {
   2449         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   2450             return "video/mp4";
   2451         }
   2452         return "video/3gpp";
   2453     }
   2454 
   2455     private String convertOutputFormatToFileExt(int outputFileFormat) {
   2456         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
   2457             return ".mp4";
   2458         }
   2459         return ".3gp";
   2460     }
   2461 
   2462     private void closeVideoFileDescriptor() {
   2463         if (mVideoFileDescriptor != null) {
   2464             try {
   2465                 mVideoFileDescriptor.close();
   2466             } catch (IOException e) {
   2467                 Log.e(TAG, "Fail to close fd", e);
   2468             }
   2469             mVideoFileDescriptor = null;
   2470         }
   2471     }
   2472 
   2473     private void showTapToSnapshotToast() {
   2474         new RotateTextToast(this, R.string.video_snapshot_hint, mOrientationCompensation)
   2475                 .show();
   2476         // Clear the preference.
   2477         Editor editor = mPreferences.edit();
   2478         editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
   2479         editor.apply();
   2480     }
   2481 
   2482     private void clearVideoNamer() {
   2483         if (mVideoNamer != null) {
   2484             mVideoNamer.finish();
   2485             mVideoNamer = null;
   2486         }
   2487     }
   2488 
   2489     private static class VideoNamer extends Thread {
   2490         private boolean mRequestPending;
   2491         private ContentResolver mResolver;
   2492         private ContentValues mValues;
   2493         private boolean mStop;
   2494         private Uri mUri;
   2495 
   2496         // Runs in main thread
   2497         public VideoNamer() {
   2498             start();
   2499         }
   2500 
   2501         // Runs in main thread
   2502         public synchronized void prepareUri(
   2503                 ContentResolver resolver, ContentValues values) {
   2504             mRequestPending = true;
   2505             mResolver = resolver;
   2506             mValues = new ContentValues(values);
   2507             notifyAll();
   2508         }
   2509 
   2510         // Runs in main thread
   2511         public synchronized Uri getUri() {
   2512             // wait until the request is done.
   2513             while (mRequestPending) {
   2514                 try {
   2515                     wait();
   2516                 } catch (InterruptedException ex) {
   2517                     // ignore.
   2518                 }
   2519             }
   2520             Uri uri = mUri;
   2521             mUri = null;
   2522             return uri;
   2523         }
   2524 
   2525         // Runs in namer thread
   2526         @Override
   2527         public synchronized void run() {
   2528             while (true) {
   2529                 if (mStop) break;
   2530                 if (!mRequestPending) {
   2531                     try {
   2532                         wait();
   2533                     } catch (InterruptedException ex) {
   2534                         // ignore.
   2535                     }
   2536                     continue;
   2537                 }
   2538                 cleanOldUri();
   2539                 generateUri();
   2540                 mRequestPending = false;
   2541                 notifyAll();
   2542             }
   2543             cleanOldUri();
   2544         }
   2545 
   2546         // Runs in main thread
   2547         public synchronized void finish() {
   2548             mStop = true;
   2549             notifyAll();
   2550         }
   2551 
   2552         // Runs in namer thread
   2553         private void generateUri() {
   2554             Uri videoTable = Uri.parse("content://media/external/video/media");
   2555             mUri = mResolver.insert(videoTable, mValues);
   2556         }
   2557 
   2558         // Runs in namer thread
   2559         private void cleanOldUri() {
   2560             if (mUri == null) return;
   2561             mResolver.delete(mUri, null, null);
   2562             mUri = null;
   2563         }
   2564     }
   2565 }
   2566