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