• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
handleMessage(Message msg)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
onReceive(Context context, Intent intent)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 
createName(long dateTaken)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
onCreate(Bundle icicle)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 
loadCameraPreferences()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 
collapseCameraControls()466     private boolean collapseCameraControls() {
467         if ((mIndicatorControlContainer != null)
468                 && mIndicatorControlContainer.dismissSettingPopup()) {
469             return true;
470         }
471         return false;
472     }
473 
enableCameraControls(boolean enable)474     private void enableCameraControls(boolean enable) {
475         if (mIndicatorControlContainer != null) {
476             mIndicatorControlContainer.setEnabled(enable);
477         }
478         if (mModePicker != null) mModePicker.setEnabled(enable);
479     }
480 
initializeIndicatorControl()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 {
MyOrientationEventListener(Context context)513         public MyOrientationEventListener(Context context) {
514             super(context);
515         }
516 
517         @Override
onOrientationChanged(int orientation)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 
setOrientationIndicator(int degree)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 
startPlayVideoActivity()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
onThumbnailClicked(View v)576     public void onThumbnailClicked(View v) {
577         if (!mMediaRecorderRecording && mThumbnail != null) {
578             showSharePopup();
579         }
580     }
581 
582     @OnClickAttr
onReviewRetakeClicked(View v)583     public void onReviewRetakeClicked(View v) {
584         deleteCurrentVideo();
585         hideAlert();
586     }
587 
588     @OnClickAttr
onReviewPlayClicked(View v)589     public void onReviewPlayClicked(View v) {
590         startPlayVideoActivity();
591     }
592 
593     @OnClickAttr
onReviewDoneClicked(View v)594     public void onReviewDoneClicked(View v) {
595         doReturnToCaller(true);
596     }
597 
598     @OnClickAttr
onReviewCancelClicked(View v)599     public void onReviewCancelClicked(View v) {
600         stopVideoRecording();
601         doReturnToCaller(false);
602     }
603 
onStopVideoRecording(boolean valid)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 
onProtectiveCurtainClick(View v)617     public void onProtectiveCurtainClick(View v) {
618         // Consume clicks
619     }
620 
621     @Override
onShutterButtonClick()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
onShutterButtonFocus(boolean pressed)643     public void onShutterButtonFocus(boolean pressed) {
644         // Do nothing (everything happens in onShutterButtonClick).
645     }
646 
647     private OnScreenHint mStorageHint;
648 
updateAndShowStorageHint()649     private void updateAndShowStorageHint() {
650         mStorageSpace = Storage.getAvailableSpace();
651         showStorageHint();
652     }
653 
showStorageHint()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 
readVideoPreferences()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 
writeDefaultEffectToPrefs()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 
getDesiredPreviewSize()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 
resizeForPreviewAspectRatio()781     private void resizeForPreviewAspectRatio() {
782         mPreviewFrameLayout.setAspectRatio(
783                 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
784     }
785 
786     @Override
doOnResume()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 
setPreviewDisplay(SurfaceHolder holder)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 
startPreview()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 
closeCamera()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 
finishRecorderAndCloseCamera()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
onPause()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
onUserInteraction()978     public void onUserInteraction() {
979         super.onUserInteraction();
980         if (!mMediaRecorderRecording) keepScreenOnAwhile();
981     }
982 
983     @Override
onBackPressed()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
onKeyDown(int keyCode, KeyEvent event)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
onKeyUp(int keyCode, KeyEvent event)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 
surfaceChanged(SurfaceHolder holder, int format, int w, int h)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 
surfaceCreated(SurfaceHolder holder)1073     public void surfaceCreated(SurfaceHolder holder) {
1074     }
1075 
surfaceDestroyed(SurfaceHolder holder)1076     public void surfaceDestroyed(SurfaceHolder holder) {
1077         mSurfaceHolder = null;
1078     }
1079 
gotoGallery()1080     private void gotoGallery() {
1081         MenuHelper.gotoCameraVideoGallery(this);
1082     }
1083 
1084     @Override
onCreateOptionsMenu(Menu menu)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 
isVideoCaptureIntent()1097     private boolean isVideoCaptureIntent() {
1098         String action = getIntent().getAction();
1099         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1100     }
1101 
doReturnToCaller(boolean valid)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 
cleanupEmptyFile()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.
initializeRecorder()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 
initializeEffectsPreview()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 
initializeEffectsRecording()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 
releaseMediaRecorder()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 
releaseEffectsRecorder()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 
generateVideoFilename(int outputFileFormat)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 
addVideoToMediaStore()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 
deleteCurrentVideo()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 
deleteVideoFile(String fileName)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 
addBaseMenuItems(Menu menu)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 
filterPreferenceScreenByIntent( PreferenceGroup screen)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
onError(MediaRecorder mr, int what, int extra)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
onInfo(MediaRecorder mr, int what, int extra)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      */
pauseAudioPlayback()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.
isRecording()1495     public boolean isRecording() {
1496         return mMediaRecorderRecording;
1497     }
1498 
startVideoRecording()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 
showRecordingUI(boolean recording)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 
getThumbnail()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 
showAlert()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 
hideAlert()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 
stopVideoRecording()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 
resetScreenOn()1689     private void resetScreenOn() {
1690         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1691         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1692     }
1693 
keepScreenOnAwhile()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 
keepScreenOn()1700     private void keepScreenOn() {
1701         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1702         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1703     }
1704 
initThumbnailButton()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 
updateThumbnailButton()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 
millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds)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 
getTimeLapseVideoLength(long deltaMs)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 
updateRecordingTime()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 
isSupported(String value, List<String> supported)1825     private static boolean isSupported(String value, List<String> supported) {
1826         return supported == null ? false : supported.indexOf(value) >= 0;
1827     }
1828 
setCameraParameters()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 
switchToOtherMode(int mode)1905     private boolean switchToOtherMode(int mode) {
1906         if (isFinishing()) return false;
1907         MenuHelper.gotoMode(mode, this);
1908         finish();
1909         return true;
1910     }
1911 
onModeChanged(int mode)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
onActivityResult(int requestCode, int resultCode, Intent data)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
onEffectsUpdate(int effectId, int effectMsg)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 
onCancelBgTraining(View v)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
onEffectsError(Exception exception, String fileName)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
onConfigurationChanged(Configuration config)1991     public void onConfigurationChanged(Configuration config) {
1992         super.onConfigurationChanged(config);
1993     }
1994 
onOverriddenPreferencesClicked()1995     public void onOverriddenPreferencesClicked() {
1996     }
1997 
onRestorePreferencesClicked()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 
restorePreferences()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 
effectsActive()2027     private boolean effectsActive() {
2028         return (mEffectType != EffectsRecorder.EFFECT_NONE);
2029     }
2030 
onSharedPreferenceChanged()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 
updateEffectSelection()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.
checkQualityAndStartPreview()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 
showTimeLapseUI(boolean enable)2136     private void showTimeLapseUI(boolean enable) {
2137         if (mTimeLapseLabel != null) {
2138             mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
2139         }
2140     }
2141 
showSharePopup()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
dispatchTouchEvent(MotionEvent m)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
onDown(MotionEvent e)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
onZoomValueChanged(int index)2184         public void onZoomValueChanged(int index) {
2185             VideoCamera.this.onZoomValueChanged(index);
2186         }
2187 
2188         // only for smooth zoom
2189         @Override
onZoomStateChanged(int state)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 
initializeZoom()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
onZoomChange(int value, boolean stopped, android.hardware.Camera camera)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 
onZoomValueChanged(int index)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 
initializeVideoSnapshot()2271     private void initializeVideoSnapshot() {
2272         if (mParameters.isVideoSnapshotSupported() && !mIsVideoCaptureIntent) {
2273             findViewById(R.id.camera_preview).setOnTouchListener(this);
2274         }
2275     }
2276 
showVideoSnapshotUI(boolean enabled)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
onTouch(View v, MotionEvent e)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 
JpegPictureCallback(Location loc)2315         public JpegPictureCallback(Location loc) {
2316             mLocation = loc;
2317         }
2318 
2319         @Override
onPictureTaken(byte [] jpegData, android.hardware.Camera camera)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 
storeImage(final byte[] data, Location loc)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 
resetEffect()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 
convertOutputFormatToMimeType(int outputFileFormat)2363     private String convertOutputFormatToMimeType(int outputFileFormat) {
2364         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2365             return "video/mp4";
2366         }
2367         return "video/3gpp";
2368     }
2369 
convertOutputFormatToFileExt(int outputFileFormat)2370     private String convertOutputFormatToFileExt(int outputFileFormat) {
2371         if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2372             return ".mp4";
2373         }
2374         return ".3gp";
2375     }
2376 
closeVideoFileDescriptor()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