VideoCamera.java revision 67668bc2d1433d40437ae8ae320a9533fb47bae9
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 android.hardware.Camera mCameraDevice;
138    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
139
140    private ComboPreferences mPreferences;
141    private PreferenceGroup mPreferenceGroup;
142
143    private View mPreviewPanel;  // The container of PreviewFrameLayout.
144    private PreviewFrameLayout mPreviewFrameLayout;
145    private SurfaceHolder mSurfaceHolder = null;
146    private IndicatorControlContainer mIndicatorControlContainer;
147    private int mSurfaceWidth;
148    private int mSurfaceHeight;
149    private View mReviewControl;
150
151    private Toast mNoShareToast;
152    // An review image having same size as preview. It is displayed when
153    // recording is stopped in capture intent.
154    private ImageView mReviewImage;
155    // A popup window that contains a bigger thumbnail and a list of apps to share.
156    private SharePopup mSharePopup;
157    // The bitmap of the last captured video thumbnail and the URI of the
158    // original video.
159    private Thumbnail mThumbnail;
160    // An imageview showing showing the last captured picture thumbnail.
161    private RotateImageView mThumbnailView;
162    private Rotatable mReviewCancelButton;
163    private Rotatable mReviewDoneButton;
164    private Rotatable mReviewPlayButton;
165    private ModePicker mModePicker;
166    private ShutterButton mShutterButton;
167    private TextView mRecordingTimeView;
168    private RotateLayout mBgLearningMessageRotater;
169    private View mBgLearningMessageFrame;
170    private LinearLayout mLabelsLinearLayout;
171
172    private boolean mIsVideoCaptureIntent;
173    private boolean mQuickCapture;
174
175    private boolean mOpenCameraFail = false;
176    private boolean mCameraDisabled = false;
177
178    private long mStorageSpace;
179
180    private MediaRecorder mMediaRecorder;
181    private EffectsRecorder mEffectsRecorder;
182
183    private int mEffectType = EffectsRecorder.EFFECT_NONE;
184    private Object mEffectParameter = null;
185    private String mEffectUriFromGallery = null;
186    private String mPrefVideoEffectDefault;
187    private boolean mResetEffect = true;
188    public static final String RESET_EFFECT_EXTRA = "reset_effect";
189
190    private boolean mMediaRecorderRecording = false;
191    private long mRecordingStartTime;
192    private boolean mRecordingTimeCountsDown = false;
193    private RotateLayout mRecordingTimeRect;
194    private long mOnResumeTime;
195    // The video file that the hardware camera is about to record into
196    // (or is recording into.)
197    private String mVideoFilename;
198    private ParcelFileDescriptor mVideoFileDescriptor;
199
200    // The video file that has already been recorded, and that is being
201    // examined by the user.
202    private String mCurrentVideoFilename;
203    private Uri mCurrentVideoUri;
204    private ContentValues mCurrentVideoValues;
205
206    private CamcorderProfile mProfile;
207
208    // The video duration limit. 0 menas no limit.
209    private int mMaxVideoDurationInMs;
210
211    // Time Lapse parameters.
212    private boolean mCaptureTimeLapse = false;
213    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
214    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
215    private View mTimeLapseLabel;
216
217    private int mDesiredPreviewWidth;
218    private int mDesiredPreviewHeight;
219
220    boolean mPausing = false;
221    boolean mPreviewing = false; // True if preview is started.
222    // The display rotation in degrees. This is only valid when mPreviewing is
223    // true.
224    private int mDisplayRotation;
225
226    private ContentResolver mContentResolver;
227
228    private LocationManager mLocationManager;
229
230    private final Handler mHandler = new MainHandler();
231    private Parameters mParameters;
232
233    // multiple cameras support
234    private int mNumberOfCameras;
235    private int mCameraId;
236    private int mFrontCameraId;
237    private int mBackCameraId;
238
239    private GestureDetector mPopupGestureDetector;
240
241    private MyOrientationEventListener mOrientationListener;
242    // The degrees of the device rotated clockwise from its natural orientation.
243    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
244    // The orientation compensation for icons and thumbnails. Ex: if the value
245    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
246    private int mOrientationCompensation = 0;
247    // The orientation compenstaion when we start recording.
248    private int mOrientationCompensationAtRecordStart;
249
250    private static final int ZOOM_STOPPED = 0;
251    private static final int ZOOM_START = 1;
252    private static final int ZOOM_STOPPING = 2;
253
254    private int mZoomState = ZOOM_STOPPED;
255    private boolean mSmoothZoomSupported = false;
256    private int mZoomValue;  // The current zoom value.
257    private int mZoomMax;
258    private int mTargetZoomValue;
259    private ZoomControl mZoomControl;
260    private final ZoomListener mZoomListener = new ZoomListener();
261
262    // This Handler is used to post message back onto the main thread of the
263    // application
264    private class MainHandler extends Handler {
265        @Override
266        public void handleMessage(Message msg) {
267            switch (msg.what) {
268
269                case ENABLE_SHUTTER_BUTTON:
270                    mShutterButton.setEnabled(true);
271                    break;
272
273                case CLEAR_SCREEN_DELAY: {
274                    getWindow().clearFlags(
275                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
276                    break;
277                }
278
279                case UPDATE_RECORD_TIME: {
280                    updateRecordingTime();
281                    break;
282                }
283
284                case CHECK_DISPLAY_ROTATION: {
285                    // Restart the preview if display rotation has changed.
286                    // Sometimes this happens when the device is held upside
287                    // down and camera app is opened. Rotation animation will
288                    // take some time and the rotation value we have got may be
289                    // wrong. Framework does not have a callback for this now.
290                    if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation)
291                            && !mMediaRecorderRecording) {
292                        startPreview();
293                    }
294                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
295                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
296                    }
297                    break;
298                }
299
300                default:
301                    Log.v(TAG, "Unhandled message: " + msg.what);
302                    break;
303            }
304        }
305    }
306
307    private BroadcastReceiver mReceiver = null;
308
309    private class MyBroadcastReceiver extends BroadcastReceiver {
310        @Override
311        public void onReceive(Context context, Intent intent) {
312            String action = intent.getAction();
313            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
314                updateAndShowStorageHint();
315                stopVideoRecording();
316            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
317                updateAndShowStorageHint();
318                updateThumbnailButton();
319            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
320                // SD card unavailable
321                // handled in ACTION_MEDIA_EJECT
322            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
323                Toast.makeText(VideoCamera.this,
324                        getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
325            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
326                updateAndShowStorageHint();
327            }
328        }
329    }
330
331    private String createName(long dateTaken) {
332        Date date = new Date(dateTaken);
333        SimpleDateFormat dateFormat = new SimpleDateFormat(
334                getString(R.string.video_file_name_format));
335
336        return dateFormat.format(date);
337    }
338
339    @Override
340    public void onCreate(Bundle icicle) {
341        super.onCreate(icicle);
342
343        Util.initializeScreenBrightness(getWindow(), getContentResolver());
344
345        mPreferences = new ComboPreferences(this);
346        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
347        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
348
349        //Testing purpose. Launch a specific camera through the intent extras.
350        int intentCameraId = Util.getCameraFacingIntentExtras(this);
351        if (intentCameraId != -1) {
352            mCameraId = intentCameraId;
353        }
354
355        mPreferences.setLocalId(this, mCameraId);
356        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
357
358        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
359        mPrefVideoEffectDefault = getString(R.string.pref_video_effect_default);
360        // Do not reset the effect if users are switching between back and front
361        // cameras.
362        mResetEffect = getIntent().getBooleanExtra(RESET_EFFECT_EXTRA, true);
363        resetEffect();
364
365        /*
366         * To reduce startup time, we start the preview in another thread.
367         * We make sure the preview is started at the end of onCreate.
368         */
369        Thread startPreviewThread = new Thread(new Runnable() {
370            public void run() {
371                try {
372                    mCameraDevice = Util.openCamera(VideoCamera.this, mCameraId);
373                    readVideoPreferences();
374                    startPreview();
375                } catch (CameraHardwareException e) {
376                    mOpenCameraFail = true;
377                } catch (CameraDisabledException e) {
378                    mCameraDisabled = true;
379                }
380            }
381        });
382        startPreviewThread.start();
383
384        Util.enterLightsOutMode(getWindow());
385
386        mContentResolver = getContentResolver();
387
388        requestWindowFeature(Window.FEATURE_PROGRESS);
389        mIsVideoCaptureIntent = isVideoCaptureIntent();
390        setContentView(R.layout.video_camera);
391        if (mIsVideoCaptureIntent) {
392            mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
393            mReviewPlayButton = (Rotatable) findViewById(R.id.btn_play);
394            mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
395            findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
396        } else {
397            initThumbnailButton();
398            mModePicker = (ModePicker) findViewById(R.id.mode_picker);
399            mModePicker.setVisibility(View.VISIBLE);
400            mModePicker.setOnModeChangeListener(this);
401        }
402
403        mPreviewPanel = findViewById(R.id.frame_layout);
404        mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
405        mReviewImage = (ImageView) findViewById(R.id.review_image);
406
407        // don't set mSurfaceHolder here. We have it set ONLY within
408        // surfaceCreated / surfaceDestroyed, other parts of the code
409        // assume that when it is set, the surface is also set.
410        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
411        SurfaceHolder holder = preview.getHolder();
412        holder.addCallback(this);
413        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
414
415        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
416
417        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
418        mShutterButton.setBackgroundResource(R.drawable.btn_shutter_video);
419        mShutterButton.setOnShutterButtonListener(this);
420        mShutterButton.requestFocus();
421
422        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
423        mRecordingTimeRect = (RotateLayout) findViewById(R.id.recording_time_rect);
424        mOrientationListener = new MyOrientationEventListener(this);
425        mTimeLapseLabel = findViewById(R.id.time_lapse_label);
426        // The R.id.labels can only be found in phone layout. For tablet, the id is
427        // R.id.labels_w1024. That is, mLabelsLinearLayout should be null in tablet layout.
428        mLabelsLinearLayout = (LinearLayout) findViewById(R.id.labels);
429
430        mBgLearningMessageRotater = (RotateLayout) findViewById(R.id.bg_replace_message);
431        mBgLearningMessageFrame = findViewById(R.id.bg_replace_message_frame);
432
433        mLocationManager = new LocationManager(this, null);
434
435        // Make sure preview is started.
436        try {
437            startPreviewThread.join();
438            if (mOpenCameraFail) {
439                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
440                return;
441            } else if (mCameraDisabled) {
442                Util.showErrorAndFinish(this, R.string.camera_disabled);
443                return;
444            }
445        } catch (InterruptedException ex) {
446            // ignore
447        }
448
449        showTimeLapseUI(mCaptureTimeLapse);
450        initializeVideoSnapshot();
451        resizeForPreviewAspectRatio();
452
453        mBackCameraId = CameraHolder.instance().getBackCameraId();
454        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
455
456        initializeIndicatorControl();
457    }
458
459    private void loadCameraPreferences() {
460        CameraSettings settings = new CameraSettings(this, mParameters,
461                mCameraId, CameraHolder.instance().getCameraInfo());
462        // Remove the video quality preference setting when the quality is given in the intent.
463        mPreferenceGroup = filterPreferenceScreenByIntent(
464                settings.getPreferenceGroup(R.xml.video_preferences));
465    }
466
467    private boolean collapseCameraControls() {
468        if ((mIndicatorControlContainer != null)
469                && mIndicatorControlContainer.dismissSettingPopup()) {
470            return true;
471        }
472        return false;
473    }
474
475    private void enableCameraControls(boolean enable) {
476        if (mIndicatorControlContainer != null) {
477            mIndicatorControlContainer.setEnabled(enable);
478        }
479        if (mModePicker != null) mModePicker.setEnabled(enable);
480    }
481
482    private void initializeIndicatorControl() {
483        mIndicatorControlContainer =
484                (IndicatorControlContainer) findViewById(R.id.indicator_control);
485        if (mIndicatorControlContainer == null) return;
486        loadCameraPreferences();
487
488        final String[] SETTING_KEYS = {
489                    CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
490                    CameraSettings.KEY_WHITE_BALANCE,
491                    CameraSettings.KEY_VIDEO_EFFECT,
492                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
493                    CameraSettings.KEY_VIDEO_QUALITY};
494        final String[] OTHER_SETTING_KEYS = {
495                    CameraSettings.KEY_RECORD_LOCATION};
496
497        CameraPicker.setImageResourceId(R.drawable.ic_switch_video_facing_holo_light);
498        mIndicatorControlContainer.initialize(this, mPreferenceGroup,
499                mParameters.isZoomSupported(), SETTING_KEYS, OTHER_SETTING_KEYS);
500        mIndicatorControlContainer.setListener(this);
501        mPopupGestureDetector = new GestureDetector(this,
502                new PopupGestureListener());
503
504        if (effectsActive()) {
505            mIndicatorControlContainer.overrideSettings(
506                    CameraSettings.KEY_VIDEO_QUALITY,
507                    Integer.toString(CamcorderProfile.QUALITY_480P));
508        }
509    }
510
511
512    private class MyOrientationEventListener
513            extends OrientationEventListener {
514        public MyOrientationEventListener(Context context) {
515            super(context);
516        }
517
518        @Override
519        public void onOrientationChanged(int orientation) {
520            // We keep the last known orientation. So if the user first orient
521            // the camera then point the camera to floor or sky, we still have
522            // the correct orientation.
523            if (orientation == ORIENTATION_UNKNOWN) return;
524            mOrientation = Util.roundOrientation(orientation, mOrientation);
525            // When the screen is unlocked, display rotation may change. Always
526            // calculate the up-to-date orientationCompensation.
527            int orientationCompensation = mOrientation
528                    + Util.getDisplayRotation(VideoCamera.this);
529
530            if (mOrientationCompensation != orientationCompensation) {
531                mOrientationCompensation = orientationCompensation;
532                if (effectsActive()) {
533                    CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
534                    int rotation = (info.orientation + mOrientation) % 360;
535                    mEffectsRecorder.setOrientationHint(rotation);
536                }
537                // Do not rotate the icons during recording because the video
538                // orientation is fixed after recording.
539                if (!mMediaRecorderRecording) {
540                    setOrientationIndicator(mOrientationCompensation);
541                }
542            }
543        }
544    }
545
546    private void setOrientationIndicator(int orientation) {
547        Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup,
548                mBgLearningMessageRotater, mIndicatorControlContainer,
549                mReviewDoneButton, mReviewPlayButton, mReviewCancelButton};
550        for (Rotatable indicator : indicators) {
551            if (indicator != null) indicator.setOrientation(orientation);
552        }
553
554        // We change the orientation of the linearlayout only for phone UI because when in portrait
555        // the width is not enough.
556        if (mLabelsLinearLayout != null) {
557            if (((orientation / 90) & 1) == 1) {
558                mLabelsLinearLayout.setOrientation(mLabelsLinearLayout.VERTICAL);
559            } else {
560                mLabelsLinearLayout.setOrientation(mLabelsLinearLayout.HORIZONTAL);
561            }
562        }
563        mRecordingTimeRect.setOrientation(mOrientationCompensation);
564    }
565
566    private void startPlayVideoActivity() {
567        Intent intent = new Intent(Intent.ACTION_VIEW);
568        intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
569        try {
570            startActivity(intent);
571        } catch (ActivityNotFoundException ex) {
572            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
573        }
574    }
575
576    @OnClickAttr
577    public void onThumbnailClicked(View v) {
578        if (!mMediaRecorderRecording && mThumbnail != null) {
579            showSharePopup();
580        }
581    }
582
583    @OnClickAttr
584    public void onReviewRetakeClicked(View v) {
585        deleteCurrentVideo();
586        hideAlert();
587    }
588
589    @OnClickAttr
590    public void onReviewPlayClicked(View v) {
591        startPlayVideoActivity();
592    }
593
594    @OnClickAttr
595    public void onReviewDoneClicked(View v) {
596        doReturnToCaller(true);
597    }
598
599    @OnClickAttr
600    public void onReviewCancelClicked(View v) {
601        stopVideoRecording();
602        doReturnToCaller(false);
603    }
604
605    private void onStopVideoRecording(boolean valid) {
606        stopVideoRecording();
607        if (mIsVideoCaptureIntent) {
608            if (mQuickCapture) {
609                doReturnToCaller(valid);
610            } else {
611                showAlert();
612            }
613        } else {
614            getThumbnail();
615        }
616    }
617
618    public void onProtectiveCurtainClick(View v) {
619        // Consume clicks
620    }
621
622    @Override
623    public void onShutterButtonClick() {
624        if (collapseCameraControls()) return;
625        boolean stop = mMediaRecorderRecording;
626
627        if (stop) {
628            onStopVideoRecording(true);
629        } else {
630            startVideoRecording();
631        }
632        mShutterButton.setEnabled(false);
633
634        // Keep the shutter button disabled when in video capture intent
635        // mode and recording is stopped. It'll be re-enabled when
636        // re-take button is clicked.
637        if (!(mIsVideoCaptureIntent && stop)) {
638            mHandler.sendEmptyMessageDelayed(
639                    ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
640        }
641    }
642
643    @Override
644    public void onShutterButtonFocus(boolean pressed) {
645        // Do nothing (everything happens in onShutterButtonClick).
646    }
647
648    private OnScreenHint mStorageHint;
649
650    private void updateAndShowStorageHint() {
651        mStorageSpace = Storage.getAvailableSpace();
652        showStorageHint();
653    }
654
655    private void showStorageHint() {
656        String errorMessage = null;
657        if (mStorageSpace == Storage.UNAVAILABLE) {
658            errorMessage = getString(R.string.no_storage);
659        } else if (mStorageSpace == Storage.PREPARING) {
660            errorMessage = getString(R.string.preparing_sd);
661        } else if (mStorageSpace == Storage.UNKNOWN_SIZE) {
662            errorMessage = getString(R.string.access_sd_fail);
663        } else if (mStorageSpace < Storage.LOW_STORAGE_THRESHOLD) {
664            errorMessage = getString(R.string.spaceIsLow_content);
665        }
666
667        if (errorMessage != null) {
668            if (mStorageHint == null) {
669                mStorageHint = OnScreenHint.makeText(this, errorMessage);
670            } else {
671                mStorageHint.setText(errorMessage);
672            }
673            mStorageHint.show();
674        } else if (mStorageHint != null) {
675            mStorageHint.cancel();
676            mStorageHint = null;
677        }
678    }
679
680    private void readVideoPreferences() {
681        // The preference stores values from ListPreference and is thus string type for all values.
682        // We need to convert it to int manually.
683        String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
684                getResources().getString(R.string.pref_video_quality_default));
685        String videoQuality =
686                mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
687                        defaultQuality);
688        int quality = Integer.valueOf(videoQuality);
689
690        // Set video quality.
691        Intent intent = getIntent();
692        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
693            int extraVideoQuality =
694                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
695            if (extraVideoQuality > 0) {
696                quality = CamcorderProfile.QUALITY_HIGH;
697            } else {  // 0 is mms.
698                quality = CamcorderProfile.QUALITY_LOW;
699            }
700        }
701
702        // Set video duration limit. The limit is read from the preference,
703        // unless it is specified in the intent.
704        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
705            int seconds =
706                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
707            mMaxVideoDurationInMs = 1000 * seconds;
708        } else {
709            mMaxVideoDurationInMs = CameraSettings.DEFAULT_VIDEO_DURATION;
710        }
711
712        // Set effect
713        mEffectType = CameraSettings.readEffectType(mPreferences);
714        if (mEffectType != EffectsRecorder.EFFECT_NONE) {
715            mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
716            // Set quality to 480p for effects, unless intent is overriding it
717            if (!intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
718                quality = CamcorderProfile.QUALITY_480P;
719            }
720            // On initial startup, can get here before indicator control is
721            // enabled. In that case, UI quality override handled in
722            // initializeIndicatorControl.
723            if (mIndicatorControlContainer != null) {
724                mIndicatorControlContainer.overrideSettings(
725                        CameraSettings.KEY_VIDEO_QUALITY,
726                        Integer.toString(CamcorderProfile.QUALITY_480P));
727            }
728        } else {
729            mEffectParameter = null;
730            if (mIndicatorControlContainer != null) {
731                mIndicatorControlContainer.overrideSettings(
732                        CameraSettings.KEY_VIDEO_QUALITY,
733                        null);
734            }
735        }
736        // Read time lapse recording interval.
737        String frameIntervalStr = mPreferences.getString(
738                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
739                getString(R.string.pref_video_time_lapse_frame_interval_default));
740        mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
741
742        mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
743        // TODO: This should be checked instead directly +1000.
744        if (mCaptureTimeLapse) quality += 1000;
745        mProfile = CamcorderProfile.get(mCameraId, quality);
746        getDesiredPreviewSize();
747    }
748
749    private void writeDefaultEffectToPrefs()  {
750        ComboPreferences.Editor editor = mPreferences.edit();
751        editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
752                getString(R.string.pref_video_effect_default));
753        editor.apply();
754    }
755
756    private void getDesiredPreviewSize() {
757        mParameters = mCameraDevice.getParameters();
758        if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
759            mDesiredPreviewWidth = mProfile.videoFrameWidth;
760            mDesiredPreviewHeight = mProfile.videoFrameHeight;
761        } else {  // Driver supports separates outputs for preview and video.
762            List<Size> sizes = mParameters.getSupportedPreviewSizes();
763            Size preferred = mParameters.getPreferredPreviewSizeForVideo();
764            int product = preferred.width * preferred.height;
765            Iterator it = sizes.iterator();
766            // Remove the preview sizes that are not preferred.
767            while (it.hasNext()) {
768                Size size = (Size) it.next();
769                if (size.width * size.height > product) {
770                    it.remove();
771                }
772            }
773            Size optimalSize = Util.getOptimalPreviewSize(this, sizes,
774                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
775            mDesiredPreviewWidth = optimalSize.width;
776            mDesiredPreviewHeight = optimalSize.height;
777        }
778        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
779                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
780    }
781
782    private void resizeForPreviewAspectRatio() {
783        mPreviewFrameLayout.setAspectRatio(
784                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
785    }
786
787    @Override
788    protected void onResume() {
789        super.onResume();
790        mPausing = false;
791        if (mOpenCameraFail || mCameraDisabled) return;
792        mZoomValue = 0;
793
794        showVideoSnapshotUI(false);
795
796        // Start orientation listener as soon as possible because it takes
797        // some time to get first orientation.
798        mOrientationListener.enable();
799        if (!mPreviewing) {
800            if (resetEffect()) {
801                mBgLearningMessageFrame.setVisibility(View.GONE);
802                mIndicatorControlContainer.reloadPreferences();
803            }
804            try {
805                mCameraDevice = Util.openCamera(this, mCameraId);
806                readVideoPreferences();
807                resizeForPreviewAspectRatio();
808                startPreview();
809            } catch (CameraHardwareException e) {
810                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
811                return;
812            } catch (CameraDisabledException e) {
813                Util.showErrorAndFinish(this, R.string.camera_disabled);
814                return;
815            }
816        }
817
818        // Initializing it here after the preview is started.
819        initializeZoom();
820
821        keepScreenOnAwhile();
822
823        // install an intent filter to receive SD card related events.
824        IntentFilter intentFilter =
825                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
826        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
827        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
828        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
829        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
830        intentFilter.addDataScheme("file");
831        mReceiver = new MyBroadcastReceiver();
832        registerReceiver(mReceiver, intentFilter);
833        mStorageSpace = Storage.getAvailableSpace();
834
835        mHandler.postDelayed(new Runnable() {
836            public void run() {
837                showStorageHint();
838            }
839        }, 200);
840
841        // Initialize location sevice.
842        boolean recordLocation = RecordLocationPreference.get(
843                mPreferences, getContentResolver());
844        mLocationManager.recordLocation(recordLocation);
845
846        if (!mIsVideoCaptureIntent) {
847            updateThumbnailButton();  // Update the last video thumbnail.
848            mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
849        }
850
851        if (mPreviewing) {
852            mOnResumeTime = SystemClock.uptimeMillis();
853            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
854        }
855    }
856
857    private void setPreviewDisplay(SurfaceHolder holder) {
858        try {
859            if (effectsActive()) {
860                mEffectsRecorder.setPreviewDisplay(
861                        mSurfaceHolder,
862                        mSurfaceWidth,
863                        mSurfaceHeight);
864            } else {
865                mCameraDevice.setPreviewDisplay(holder);
866            }
867        } catch (Throwable ex) {
868            closeCamera();
869            throw new RuntimeException("setPreviewDisplay failed", ex);
870        }
871    }
872
873    private void startPreview() {
874        Log.v(TAG, "startPreview");
875
876        mCameraDevice.setErrorCallback(mErrorCallback);
877        if (mPreviewing == true) {
878            mCameraDevice.stopPreview();
879            if (effectsActive() && mEffectsRecorder != null) {
880                mEffectsRecorder.release();
881            }
882            mPreviewing = false;
883        }
884
885        mDisplayRotation = Util.getDisplayRotation(this);
886        int orientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
887        mCameraDevice.setDisplayOrientation(orientation);
888        setCameraParameters();
889
890        if (!effectsActive()) {
891            setPreviewDisplay(mSurfaceHolder);
892            try {
893                mCameraDevice.startPreview();
894            } catch (Throwable ex) {
895                closeCamera();
896                throw new RuntimeException("startPreview failed", ex);
897            }
898        } else {
899            initializeEffectsPreview();
900            Log.v(TAG, "effectsStartPreview");
901            mEffectsRecorder.startPreview();
902        }
903
904        mZoomState = ZOOM_STOPPED;
905        mPreviewing = true;
906    }
907
908    private void closeCamera() {
909        Log.v(TAG, "closeCamera");
910        if (mCameraDevice == null) {
911            Log.d(TAG, "already stopped.");
912            return;
913        }
914        if (mEffectsRecorder != null) {
915            mEffectsRecorder.release();
916        }
917        mEffectType = EffectsRecorder.EFFECT_NONE;
918        CameraHolder.instance().release();
919        mCameraDevice.setZoomChangeListener(null);
920        mCameraDevice.setErrorCallback(null);
921        mCameraDevice = null;
922        mPreviewing = false;
923        mSnapshotInProgress = false;
924    }
925
926    private void finishRecorderAndCloseCamera() {
927        // This is similar to what mShutterButton.performClick() does,
928        // but not quite the same.
929        if (mMediaRecorderRecording) {
930            if (mIsVideoCaptureIntent) {
931                stopVideoRecording();
932                showAlert();
933            } else {
934                stopVideoRecording();
935                getThumbnail();
936            }
937        } else {
938            stopVideoRecording();
939        }
940        closeCamera();
941    }
942
943    @Override
944    protected void onPause() {
945        super.onPause();
946        mPausing = true;
947
948        if (mIndicatorControlContainer != null) {
949            mIndicatorControlContainer.dismissSettingPopup();
950        }
951
952        finishRecorderAndCloseCamera();
953
954        if (mSharePopup != null) mSharePopup.dismiss();
955
956        if (mReceiver != null) {
957            unregisterReceiver(mReceiver);
958            mReceiver = null;
959        }
960        resetScreenOn();
961
962        if (!mIsVideoCaptureIntent && mThumbnail != null && !mThumbnail.fromFile()) {
963            mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
964        }
965
966        if (mStorageHint != null) {
967            mStorageHint.cancel();
968            mStorageHint = null;
969        }
970
971        mOrientationListener.disable();
972        mLocationManager.recordLocation(false);
973
974        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
975    }
976
977    @Override
978    public void onUserInteraction() {
979        super.onUserInteraction();
980        if (!mMediaRecorderRecording) keepScreenOnAwhile();
981    }
982
983    @Override
984    public void onBackPressed() {
985        if (mPausing) return;
986        if (mMediaRecorderRecording) {
987            onStopVideoRecording(false);
988        } else if (!collapseCameraControls()) {
989            super.onBackPressed();
990        }
991    }
992
993    @Override
994    public boolean onKeyDown(int keyCode, KeyEvent event) {
995        // Do not handle any key if the activity is paused.
996        if (mPausing) {
997            return true;
998        }
999
1000        switch (keyCode) {
1001            case KeyEvent.KEYCODE_CAMERA:
1002                if (event.getRepeatCount() == 0) {
1003                    mShutterButton.performClick();
1004                    return true;
1005                }
1006                break;
1007            case KeyEvent.KEYCODE_DPAD_CENTER:
1008                if (event.getRepeatCount() == 0) {
1009                    mShutterButton.performClick();
1010                    return true;
1011                }
1012                break;
1013            case KeyEvent.KEYCODE_MENU:
1014                if (mMediaRecorderRecording) return true;
1015                break;
1016        }
1017
1018        return super.onKeyDown(keyCode, event);
1019    }
1020
1021    @Override
1022    public boolean onKeyUp(int keyCode, KeyEvent event) {
1023        switch (keyCode) {
1024            case KeyEvent.KEYCODE_CAMERA:
1025                mShutterButton.setPressed(false);
1026                return true;
1027        }
1028        return super.onKeyUp(keyCode, event);
1029    }
1030
1031    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1032        // Make sure we have a surface in the holder before proceeding.
1033        if (holder.getSurface() == null) {
1034            Log.d(TAG, "holder.getSurface() == null");
1035            return;
1036        }
1037
1038        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1039
1040        mSurfaceHolder = holder;
1041        mSurfaceWidth = w;
1042        mSurfaceHeight = h;
1043
1044        if (mPausing) {
1045            // We're pausing, the screen is off and we already stopped
1046            // video recording. We don't want to start the camera again
1047            // in this case in order to conserve power.
1048            // The fact that surfaceChanged is called _after_ an onPause appears
1049            // to be legitimate since in that case the lockscreen always returns
1050            // to portrait orientation possibly triggering the notification.
1051            return;
1052        }
1053
1054        // The mCameraDevice will be null if it is fail to connect to the
1055        // camera hardware. In this case we will show a dialog and then
1056        // finish the activity, so it's OK to ignore it.
1057        if (mCameraDevice == null) return;
1058
1059        // Set preview display if the surface is being created. Preview was
1060        // already started. Also restart the preview if display rotation has
1061        // changed. Sometimes this happens when the device is held in portrait
1062        // and camera app is opened. Rotation animation takes some time and
1063        // display rotation in onCreate may not be what we want.
1064        if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation)
1065                && holder.isCreating()) {
1066            setPreviewDisplay(holder);
1067        } else {
1068            stopVideoRecording();
1069            startPreview();
1070        }
1071    }
1072
1073    public void surfaceCreated(SurfaceHolder holder) {
1074    }
1075
1076    public void surfaceDestroyed(SurfaceHolder holder) {
1077        mSurfaceHolder = null;
1078    }
1079
1080    private void gotoGallery() {
1081        MenuHelper.gotoCameraVideoGallery(this);
1082    }
1083
1084    @Override
1085    public boolean onCreateOptionsMenu(Menu menu) {
1086        super.onCreateOptionsMenu(menu);
1087
1088        if (mIsVideoCaptureIntent) {
1089            // No options menu for attach mode.
1090            return false;
1091        } else {
1092            addBaseMenuItems(menu);
1093        }
1094        return true;
1095    }
1096
1097    private boolean isVideoCaptureIntent() {
1098        String action = getIntent().getAction();
1099        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1100    }
1101
1102    private void doReturnToCaller(boolean valid) {
1103        Intent resultIntent = new Intent();
1104        int resultCode;
1105        if (valid) {
1106            resultCode = RESULT_OK;
1107            resultIntent.setData(mCurrentVideoUri);
1108        } else {
1109            resultCode = RESULT_CANCELED;
1110        }
1111        setResultEx(resultCode, resultIntent);
1112        finish();
1113    }
1114
1115    private void cleanupEmptyFile() {
1116        if (mVideoFilename != null) {
1117            File f = new File(mVideoFilename);
1118            if (f.length() == 0 && f.delete()) {
1119                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1120                mVideoFilename = null;
1121            }
1122        }
1123    }
1124
1125    // Prepares media recorder.
1126    private void initializeRecorder() {
1127        Log.v(TAG, "initializeRecorder");
1128        // If the mCameraDevice is null, then this activity is going to finish
1129        if (mCameraDevice == null) return;
1130
1131        if (mSurfaceHolder == null) {
1132            Log.v(TAG, "Surface holder is null. Wait for surface changed.");
1133            return;
1134        }
1135
1136        Intent intent = getIntent();
1137        Bundle myExtras = intent.getExtras();
1138
1139        long requestedSizeLimit = 0;
1140        if (mIsVideoCaptureIntent && myExtras != null) {
1141            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1142            if (saveUri != null) {
1143                try {
1144                    mVideoFileDescriptor =
1145                            mContentResolver.openFileDescriptor(saveUri, "rw");
1146                    mCurrentVideoUri = saveUri;
1147                } catch (java.io.FileNotFoundException ex) {
1148                    // invalid uri
1149                    Log.e(TAG, ex.toString());
1150                }
1151            }
1152            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1153        }
1154        mMediaRecorder = new MediaRecorder();
1155
1156        // Unlock the camera object before passing it to media recorder.
1157        mCameraDevice.unlock();
1158        mMediaRecorder.setCamera(mCameraDevice);
1159        if (!mCaptureTimeLapse) {
1160            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1161        }
1162        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1163        mMediaRecorder.setProfile(mProfile);
1164        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1165        if (mCaptureTimeLapse) {
1166            mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1167        }
1168
1169        Location loc = mLocationManager.getCurrentLocation();
1170        if (loc != null) {
1171            mMediaRecorder.setLocation((float) loc.getLatitude(),
1172                    (float) loc.getLongitude());
1173        }
1174
1175
1176        // Set output file.
1177        // Try Uri in the intent first. If it doesn't exist, use our own
1178        // instead.
1179        if (mVideoFileDescriptor != null) {
1180            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1181        } else {
1182            generateVideoFilename(mProfile.fileFormat);
1183            mMediaRecorder.setOutputFile(mVideoFilename);
1184        }
1185
1186        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
1187
1188        // Set maximum file size.
1189        long maxFileSize = mStorageSpace - Storage.LOW_STORAGE_THRESHOLD;
1190        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1191            maxFileSize = requestedSizeLimit;
1192        }
1193
1194        try {
1195            mMediaRecorder.setMaxFileSize(maxFileSize);
1196        } catch (RuntimeException exception) {
1197            // We are going to ignore failure of setMaxFileSize here, as
1198            // a) The composer selected may simply not support it, or
1199            // b) The underlying media framework may not handle 64-bit range
1200            // on the size restriction.
1201        }
1202
1203        // See android.hardware.Camera.Parameters.setRotation for
1204        // documentation.
1205        // Note that mOrientation here is the device orientation, which is the opposite of
1206        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1207        // which is the orientation the graphics need to rotate in order to render correctly.
1208        int rotation = 0;
1209        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1210            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1211            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1212                rotation = (info.orientation - mOrientation + 360) % 360;
1213            } else {  // back-facing camera
1214                rotation = (info.orientation + mOrientation) % 360;
1215            }
1216        }
1217        mMediaRecorder.setOrientationHint(rotation);
1218        mOrientationCompensationAtRecordStart = mOrientationCompensation;
1219
1220        try {
1221            mMediaRecorder.prepare();
1222        } catch (IOException e) {
1223            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1224            releaseMediaRecorder();
1225            throw new RuntimeException(e);
1226        }
1227
1228        mMediaRecorder.setOnErrorListener(this);
1229        mMediaRecorder.setOnInfoListener(this);
1230    }
1231
1232    private void initializeEffectsPreview() {
1233        Log.v(TAG, "initializeEffectsPreview");
1234        // If the mCameraDevice is null, then this activity is going to finish
1235        if (mCameraDevice == null) return;
1236
1237        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1238
1239        mEffectsRecorder = new EffectsRecorder(this);
1240
1241        // TODO: Confirm none of the foll need to go to initializeEffectsRecording()
1242        // and none of these change even when the preview is not refreshed.
1243        mEffectsRecorder.setCamera(mCameraDevice);
1244        mEffectsRecorder.setCameraFacing(info.facing);
1245        mEffectsRecorder.setProfile(mProfile);
1246        mEffectsRecorder.setEffectsListener(this);
1247        mEffectsRecorder.setOnInfoListener(this);
1248        mEffectsRecorder.setOnErrorListener(this);
1249
1250        // See android.hardware.Camera.Parameters.setRotation for
1251        // documentation.
1252        int rotation = 0;
1253        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1254            rotation = (info.orientation + mOrientation) % 360;
1255        }
1256        mEffectsRecorder.setOrientationHint(rotation);
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