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