VideoCamera.java revision 79227ee2627163a64a3f8f5133528a6c86d79e04
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.CamcorderHeadUpDisplay;
20import com.android.camera.ui.CameraPicker;
21import com.android.camera.ui.GLRootView;
22import com.android.camera.ui.HeadUpDisplay;
23import com.android.camera.ui.IndicatorWheel;
24import com.android.camera.ui.RotateImageView;
25import com.android.camera.ui.SharePopup;
26import com.android.camera.ui.ZoomPicker;
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.Size;
40import android.media.CamcorderProfile;
41import android.media.MediaRecorder;
42import android.media.ThumbnailUtils;
43import android.net.Uri;
44import android.os.Bundle;
45import android.os.Handler;
46import android.os.Message;
47import android.os.ParcelFileDescriptor;
48import android.os.SystemClock;
49import android.provider.MediaStore;
50import android.provider.MediaStore.Video;
51import android.provider.Settings;
52import android.util.Log;
53import android.view.GestureDetector;
54import android.view.Gravity;
55import android.view.KeyEvent;
56import android.view.Menu;
57import android.view.MenuItem;
58import android.view.MenuItem.OnMenuItemClickListener;
59import android.view.MotionEvent;
60import android.view.OrientationEventListener;
61import android.view.SurfaceHolder;
62import android.view.SurfaceView;
63import android.view.View;
64import android.view.ViewGroup;
65import android.view.Window;
66import android.view.WindowManager;
67import android.view.animation.AlphaAnimation;
68import android.view.animation.Animation;
69import android.widget.Button;
70import android.widget.ImageView;
71import android.widget.TextView;
72import android.widget.Toast;
73
74import java.io.File;
75import java.io.IOException;
76import java.text.SimpleDateFormat;
77import java.util.ArrayList;
78import java.util.Date;
79import java.util.HashMap;
80import java.util.Iterator;
81import java.util.List;
82
83/**
84 * The Camcorder activity.
85 */
86public class VideoCamera extends ActivityBase
87        implements View.OnClickListener,
88        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
89        MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener,
90        Switcher.OnSwitchListener, PreviewFrameLayout.OnSizeChangedListener {
91
92    private static final String TAG = "videocamera";
93
94    private static final String LAST_THUMB_FILENAME = "video_last_thumb";
95
96    private static final int CHECK_DISPLAY_ROTATION = 3;
97    private static final int CLEAR_SCREEN_DELAY = 4;
98    private static final int UPDATE_RECORD_TIME = 5;
99    private static final int ENABLE_SHUTTER_BUTTON = 6;
100
101    private static final int SCREEN_DELAY = 2 * 60 * 1000;
102
103    // The brightness settings used when it is set to automatic in the system.
104    // The reason why it is set to 0.7 is just because 1.0 is too bright.
105    private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f;
106
107    private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L;
108
109    private static final boolean SWITCH_CAMERA = true;
110    private static final boolean SWITCH_VIDEO = false;
111
112    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
113
114    private static final int[] TIME_LAPSE_VIDEO_QUALITY = {
115            CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
116            CamcorderProfile.QUALITY_TIME_LAPSE_720P,
117            CamcorderProfile.QUALITY_TIME_LAPSE_480P,
118            CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
119            CamcorderProfile.QUALITY_TIME_LAPSE_QCIF};
120
121    private static final int[] VIDEO_QUALITY = {
122            CamcorderProfile.QUALITY_1080P,
123            CamcorderProfile.QUALITY_720P,
124            CamcorderProfile.QUALITY_480P,
125            CamcorderProfile.QUALITY_CIF,
126            CamcorderProfile.QUALITY_QCIF};
127
128    /**
129     * An unpublished intent flag requesting to start recording straight away
130     * and return as soon as recording is stopped.
131     * TODO: consider publishing by moving into MediaStore.
132     */
133    private final static String EXTRA_QUICK_CAPTURE =
134            "android.intent.extra.quickCapture";
135
136    private android.hardware.Camera mCameraDevice;
137    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
138
139    private ComboPreferences mPreferences;
140    private PreferenceGroup mPreferenceGroup;
141
142    private PreviewFrameLayout mPreviewFrameLayout;
143    private SurfaceHolder mSurfaceHolder = null;
144    private GLRootView mGLRootView;
145    // w1024dp devices use indicator wheel. Other devices use head-up display.
146    private CamcorderHeadUpDisplay mHeadUpDisplay;
147    private IndicatorWheel mIndicatorWheel;
148    // Front/back camera picker for w1024dp layout.
149    private CameraPicker mCameraPicker;
150    private View mReviewControl;
151
152    private Toast mNoShareToast;
153    // A button showing the last captured video thumbnail. Clicking on it will
154    // show the share popup window.
155    private RotateImageView mThumbnailButton;
156    // A popup window that contains a bigger thumbnail and a list of apps to share.
157    private SharePopup mSharePopup;
158    // The bitmap of the last captured video thumbnail and the URI of the
159    // original video.
160    private Thumbnail mThumbnail;
161    // An review image having same size as preview. It is displayed when
162    // recording is stopped in capture intent.
163    private ImageView mReviewImage;
164    // A button sharing the last picture.
165    private RotateImageView mShareButton;
166    private RotateImageView mCameraSwitchIcon;
167    private RotateImageView mVideoSwitchIcon;
168    private ShutterButton mShutterButton;
169    private TextView mRecordingTimeView;
170    private SwitcherSet mSwitcher;
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 boolean mMediaRecorderRecording = false;
182    private long mRecordingStartTime;
183    private boolean mRecordingTimeCountsDown = false;
184    private long mOnResumeTime;
185    // The video file that the hardware camera is about to record into
186    // (or is recording into.)
187    private String mVideoFilename;
188    private ParcelFileDescriptor mVideoFileDescriptor;
189
190    // The video file that has already been recorded, and that is being
191    // examined by the user.
192    private String mCurrentVideoFilename;
193    private Uri mCurrentVideoUri;
194    private ContentValues mCurrentVideoValues;
195
196    private CamcorderProfile mProfile;
197    // The array of video quality profiles supported by each camera(s). Here the
198    // cameraId is the index of the array to get the profile map which contain
199    // the set of quality string and its real quality of a camera.
200    private HashMap<String, Integer>[] mProfileQuality;
201    private HashMap<String, Integer>[] mTimeLapseProfileQuality;
202
203    // The video duration limit. 0 menas no limit.
204    private int mMaxVideoDurationInMs;
205
206    // Time Lapse parameters.
207    private boolean mCaptureTimeLapse = false;
208    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
209    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
210    private View mTimeLapseLabel;
211    private View mPreviewBorder;
212
213    private int mDesiredPreviewWidth;
214    private int mDesiredPreviewHeight;
215
216    boolean mPausing = false;
217    boolean mPreviewing = false; // True if preview is started.
218    // The display rotation in degrees. This is only valid when mPreviewing is
219    // true.
220    private int mDisplayRotation;
221
222    private ContentResolver mContentResolver;
223
224    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
225
226    private final Handler mHandler = new MainHandler();
227    private Parameters mParameters;
228
229    // multiple cameras support
230    private int mNumberOfCameras;
231    private int mCameraId;
232    private int mFrontCameraId;
233    private int mBackCameraId;
234
235    private GestureDetector mPopupGestureDetector;
236
237    private MyOrientationEventListener mOrientationListener;
238    // The degrees of the device rotated clockwise from its natural orientation.
239    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
240    // The orientation compensation for icons and thumbnails. Ex: if the value
241    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
242    private int mOrientationCompensation = 0;
243    private int mOrientationHint; // the orientation hint for video playback
244
245    private static final int ZOOM_STOPPED = 0;
246    private static final int ZOOM_START = 1;
247    private static final int ZOOM_STOPPING = 2;
248
249    private int mZoomState = ZOOM_STOPPED;
250    private boolean mSmoothZoomSupported = false;
251    private int mZoomValue;  // The current zoom value.
252    private int mZoomMax;
253    private int mTargetZoomValue;
254    private ZoomPicker mZoomPicker;
255    private final ZoomListener mZoomListener = new ZoomListener();
256
257    // This Handler is used to post message back onto the main thread of the
258    // application
259    private class MainHandler extends Handler {
260        @Override
261        public void handleMessage(Message msg) {
262            switch (msg.what) {
263
264                case ENABLE_SHUTTER_BUTTON:
265                    mShutterButton.setEnabled(true);
266                    break;
267
268                case CLEAR_SCREEN_DELAY: {
269                    getWindow().clearFlags(
270                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
271                    break;
272                }
273
274                case UPDATE_RECORD_TIME: {
275                    updateRecordingTime();
276                    break;
277                }
278
279                case CHECK_DISPLAY_ROTATION: {
280                    // Restart the preview if display rotation has changed.
281                    // Sometimes this happens when the device is held upside
282                    // down and camera app is opened. Rotation animation will
283                    // take some time and the rotation value we have got may be
284                    // wrong. Framework does not have a callback for this now.
285                    if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation)
286                            && !mMediaRecorderRecording) {
287                        startPreview();
288                    }
289                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
290                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
291                    }
292                    break;
293                }
294
295                default:
296                    Log.v(TAG, "Unhandled message: " + msg.what);
297                    break;
298            }
299        }
300    }
301
302    private BroadcastReceiver mReceiver = null;
303
304    private class MyBroadcastReceiver extends BroadcastReceiver {
305        @Override
306        public void onReceive(Context context, Intent intent) {
307            String action = intent.getAction();
308            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
309                updateAndShowStorageHint();
310                stopVideoRecording();
311            } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
312                updateAndShowStorageHint();
313                updateThumbnailButton();
314            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
315                // SD card unavailable
316                // handled in ACTION_MEDIA_EJECT
317            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
318                Toast.makeText(VideoCamera.this,
319                        getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
320            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
321                updateAndShowStorageHint();
322            }
323        }
324    }
325
326    private String createName(long dateTaken) {
327        Date date = new Date(dateTaken);
328        SimpleDateFormat dateFormat = new SimpleDateFormat(
329                getString(R.string.video_file_name_format));
330
331        return dateFormat.format(date);
332    }
333
334    @Override
335    public void onCreate(Bundle icicle) {
336        super.onCreate(icicle);
337
338        Window win = getWindow();
339
340        // Overright the brightness settings if it is automatic
341        int mode = Settings.System.getInt(
342                getContentResolver(),
343                Settings.System.SCREEN_BRIGHTNESS_MODE,
344                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
345        if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
346            WindowManager.LayoutParams winParams = win.getAttributes();
347            winParams.screenBrightness = DEFAULT_CAMERA_BRIGHTNESS;
348            win.setAttributes(winParams);
349        }
350
351        mPreferences = new ComboPreferences(this);
352        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
353        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
354
355        //Testing purpose. Launch a specific camera through the intent extras.
356        int intentCameraId = Util.getCameraFacingIntentExtras(this);
357        if (intentCameraId != -1) {
358            mCameraId = intentCameraId;
359        }
360
361        mPreferences.setLocalId(this, mCameraId);
362        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
363
364        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
365
366        /*
367         * To reduce startup time, we start the preview in another thread.
368         * We make sure the preview is started at the end of onCreate.
369         */
370        Thread startPreviewThread = new Thread(new Runnable() {
371            public void run() {
372                try {
373                    mCameraDevice = Util.openCamera(VideoCamera.this, mCameraId);
374                    readVideoPreferences();
375                    startPreview();
376                } catch(CameraHardwareException e) {
377                    mOpenCameraFail = true;
378                } catch(CameraDisabledException e) {
379                    mCameraDisabled = true;
380                }
381            }
382        });
383        startPreviewThread.start();
384
385        mContentResolver = getContentResolver();
386
387        requestWindowFeature(Window.FEATURE_PROGRESS);
388        mIsVideoCaptureIntent = isVideoCaptureIntent();
389        if (mIsVideoCaptureIntent) {
390            setContentView(R.layout.video_camera_attach);
391
392            mReviewControl = findViewById(R.id.review_control);
393            mReviewControl.setVisibility(View.VISIBLE);
394            findViewById(R.id.btn_cancel).setOnClickListener(this);
395            findViewById(R.id.btn_done).setOnClickListener(this);
396            findViewById(R.id.btn_play).setOnClickListener(this);
397            View retake = findViewById(R.id.btn_retake);
398            retake.setOnClickListener(this);
399            if (retake instanceof ImageView) {
400                ((ImageView) retake).setImageResource(R.drawable.btn_ic_review_retake_video);
401            } else {
402                ((Button) retake).setCompoundDrawablesWithIntrinsicBounds(
403                        R.drawable.ic_switch_video_holo_dark, 0, 0, 0);
404            }
405        } else {
406            setContentView(R.layout.video_camera);
407
408            initThumbnailButton();
409            mShareButton = (RotateImageView) findViewById(R.id.btn_share);
410            if (mShareButton != null) mShareButton.setOnClickListener(this);
411            mSwitcher = (SwitcherSet) findViewById(R.id.camera_switch);
412            mSwitcher.setVisibility(View.VISIBLE);
413            mSwitcher.setOnSwitchListener(this);
414        }
415
416        mPreviewFrameLayout = (PreviewFrameLayout)
417                findViewById(R.id.frame_layout);
418        mPreviewFrameLayout.setOnSizeChangedListener(this);
419
420        mReviewImage = (ImageView) findViewById(R.id.review_image);
421        mCameraSwitchIcon = (RotateImageView) findViewById(R.id.camera_switch_icon);
422        mVideoSwitchIcon = (RotateImageView) findViewById(R.id.video_switch_icon);
423
424        // don't set mSurfaceHolder here. We have it set ONLY within
425        // surfaceCreated / surfaceDestroyed, other parts of the code
426        // assume that when it is set, the surface is also set.
427        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
428        SurfaceHolder holder = preview.getHolder();
429        holder.addCallback(this);
430        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
431
432        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
433
434        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
435        mShutterButton.setImageResource(R.drawable.btn_ic_video_record);
436        mShutterButton.setOnShutterButtonListener(this);
437        mShutterButton.requestFocus();
438
439        mRecordingTimeView = (TextView) findViewById(R.id.recording_time);
440        mOrientationListener = new MyOrientationEventListener(VideoCamera.this);
441        mTimeLapseLabel = findViewById(R.id.time_lapse_label);
442        mPreviewBorder = findViewById(R.id.preview_border);
443
444        // Make sure preview is started.
445        try {
446            startPreviewThread.join();
447            if (mOpenCameraFail) {
448                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
449                return;
450            } else if (mCameraDisabled) {
451                Util.showErrorAndFinish(this, R.string.camera_disabled);
452                return;
453            }
454        } catch (InterruptedException ex) {
455            // ignore
456        }
457
458        showTimeLapseUI(mCaptureTimeLapse);
459        resizeForPreviewAspectRatio();
460
461        mBackCameraId = CameraHolder.instance().getBackCameraId();
462        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
463
464        // Initialize after startPreview becuase this need mParameters.
465        initializeIndicatorWheel();
466        // w1024dp devices use indicator wheel. Other devices use head-up display.
467        if (mIndicatorWheel == null) {
468            mHeadUpDisplay = new CamcorderHeadUpDisplay(this);
469            mHeadUpDisplay.setListener(new MyHeadUpDisplayListener());
470            initializeHeadUpDisplay();
471        }
472        initializeCameraPicker();
473        initializeZoomPicker();
474    }
475
476    private void changeHeadUpDisplayState() {
477        if (mHeadUpDisplay == null) return;
478        // If the camera resumes behind the lock screen, the orientation
479        // will be portrait. That causes OOM when we try to allocation GPU
480        // memory for the GLSurfaceView again when the orientation changes. So,
481        // we delayed initialization of HeadUpDisplay until the orientation
482        // becomes landscape.
483        Configuration config = getResources().getConfiguration();
484        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE
485                && !mPausing) {
486            if (mGLRootView == null) attachHeadUpDisplay();
487        } else if (mGLRootView != null) {
488            detachHeadUpDisplay();
489        }
490    }
491
492    private void initializeCameraPicker() {
493        mCameraPicker = (CameraPicker) findViewById(R.id.camera_picker);
494        if (mCameraPicker != null) {
495            mCameraPicker.setImageResource(R.drawable.camera_toggle_video);
496            ListPreference pref = mPreferenceGroup.findPreference(
497                    CameraSettings.KEY_CAMERA_ID);
498            if (pref != null) {
499                mCameraPicker.initialize(pref);
500                mCameraPicker.setListener(new MyCameraPickerListener());
501            }
502        }
503    }
504
505    private void initializeZoomPicker() {
506        Button zoomIncrement = (Button) findViewById(R.id.zoom_increment);
507        Button zoomDecrement = (Button) findViewById(R.id.zoom_decrement);
508        TextView zoomRatio = (TextView) findViewById(R.id.zoom_ratio);
509        if (zoomIncrement != null && zoomDecrement != null && mParameters.isZoomSupported()) {
510            mZoomPicker = new ZoomPicker(this, zoomIncrement, zoomDecrement, zoomRatio);
511        }
512    }
513
514    private void loadCameraPreferences() {
515        CameraSettings settings = new CameraSettings(this, mParameters,
516                mCameraId, CameraHolder.instance().getCameraInfo());
517        mPreferenceGroup = settings.getPreferenceGroup(R.xml.video_preferences);
518    }
519
520    private void initializeHeadUpDisplay() {
521        if (mHeadUpDisplay == null) return;
522        loadCameraPreferences();
523
524        if (mIsVideoCaptureIntent) {
525            mPreferenceGroup = filterPreferenceScreenByIntent(mPreferenceGroup);
526        }
527        mHeadUpDisplay.initialize(this, mPreferenceGroup, mOrientationCompensation);
528    }
529
530    private void attachHeadUpDisplay() {
531        mHeadUpDisplay.setOrientation(mOrientationCompensation);
532        ViewGroup frame = (ViewGroup) findViewById(R.id.frame);
533        mGLRootView = new GLRootView(this);
534        frame.addView(mGLRootView);
535        mGLRootView.setContentPane(mHeadUpDisplay);
536    }
537
538    private void detachHeadUpDisplay() {
539        mHeadUpDisplay.collapse();
540        ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView);
541        mGLRootView = null;
542    }
543
544    private boolean collapseCameraControls() {
545        if (mHeadUpDisplay != null && mHeadUpDisplay.collapse()) {
546            return true;
547        }
548        if (mIndicatorWheel != null && mIndicatorWheel.dismissSettingPopup()) {
549            return true;
550        }
551        return false;
552    }
553
554    private void enableCameraControls(boolean enable) {
555        if (mHeadUpDisplay != null) mHeadUpDisplay.setEnabled(enable);
556        if (mIndicatorWheel != null) mIndicatorWheel.setEnabled(enable);
557        if (mCameraPicker != null) mCameraPicker.setEnabled(enable);
558        if (mSwitcher != null) mSwitcher.setEnabled(enable);
559    }
560
561    private void initializeIndicatorWheel() {
562        mIndicatorWheel = (IndicatorWheel) findViewById(R.id.indicator_wheel);
563        if (mIndicatorWheel == null) return;
564        loadCameraPreferences();
565
566        final String[] SETTING_KEYS = {CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE};
567        final String[] OTHER_SETTING_KEYS = {
568                CameraSettings.KEY_VIDEO_QUALITY,
569                CameraSettings.KEY_WHITE_BALANCE,
570                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL};
571        mIndicatorWheel.initialize(this, mPreferenceGroup, SETTING_KEYS, OTHER_SETTING_KEYS);
572        mIndicatorWheel.setListener(new MyIndicatorWheelListener());
573        mPopupGestureDetector = new GestureDetector(this,
574                new PopupGestureListener());
575    }
576
577    public static int roundOrientation(int orientation) {
578        return ((orientation + 45) / 90 * 90) % 360;
579    }
580
581    private class MyOrientationEventListener
582            extends OrientationEventListener {
583        public MyOrientationEventListener(Context context) {
584            super(context);
585        }
586
587        @Override
588        public void onOrientationChanged(int orientation) {
589            if (mMediaRecorderRecording) return;
590            // We keep the last known orientation. So if the user first orient
591            // the camera then point the camera to floor or sky, we still have
592            // the correct orientation.
593            if (orientation == ORIENTATION_UNKNOWN) return;
594            mOrientation = roundOrientation(orientation);
595            // When the screen is unlocked, display rotation may change. Always
596            // calculate the up-to-date orientationCompensation.
597            int orientationCompensation = mOrientation
598                    + Util.getDisplayRotation(VideoCamera.this);
599            if (mOrientationCompensation != orientationCompensation) {
600                mOrientationCompensation = orientationCompensation;
601                if (!mIsVideoCaptureIntent) {
602                    setOrientationIndicator(mOrientationCompensation);
603                }
604            }
605        }
606    }
607
608    private void setOrientationIndicator(int degree) {
609        if (mHeadUpDisplay != null) mHeadUpDisplay.setOrientation(mOrientationCompensation);
610        if (mThumbnailButton != null) mThumbnailButton.setDegree(degree);
611        if (mShareButton != null) mShareButton.setDegree(degree);
612        if (mCameraSwitchIcon != null) mCameraSwitchIcon.setDegree(degree);
613        if (mVideoSwitchIcon != null) mVideoSwitchIcon.setDegree(degree);
614        if (mSharePopup != null) mSharePopup.setOrientation(degree);
615    }
616
617    private void startPlayVideoActivity() {
618        Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri);
619        try {
620            startActivity(intent);
621        } catch (ActivityNotFoundException ex) {
622            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
623        }
624    }
625
626    public void onClick(View v) {
627        switch (v.getId()) {
628            case R.id.btn_retake:
629                deleteCurrentVideo();
630                hideAlert();
631                break;
632            case R.id.btn_play:
633                startPlayVideoActivity();
634                break;
635            case R.id.btn_done:
636                doReturnToCaller(true);
637                break;
638            case R.id.btn_cancel:
639                stopVideoRecording();
640                doReturnToCaller(false);
641                break;
642            case R.id.review_thumbnail:
643                if (!mMediaRecorderRecording && mThumbnail != null) {
644                    Util.viewUri(mThumbnail.getUri(), this);
645                }
646                break;
647            case R.id.btn_gallery:
648                gotoGallery();
649                break;
650            case R.id.btn_share:
651                if (!mMediaRecorderRecording && mThumbnail != null) {
652                    createSharePopup();
653                }
654                break;
655        }
656    }
657
658    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
659        // Do nothing (everything happens in onShutterButtonClick).
660    }
661
662    private void onStopVideoRecording(boolean valid) {
663        stopVideoRecording();
664        if (mIsVideoCaptureIntent) {
665            if (mQuickCapture) {
666                doReturnToCaller(valid);
667            } else {
668                showAlert();
669            }
670        } else {
671            getThumbnail();
672        }
673    }
674
675    public void onShutterButtonClick(ShutterButton button) {
676        switch (button.getId()) {
677            case R.id.shutter_button:
678                if (collapseCameraControls()) return;
679                boolean stop = mMediaRecorderRecording;
680
681                if (stop) {
682                    onStopVideoRecording(true);
683                } else {
684                    startVideoRecording();
685                }
686                mShutterButton.setEnabled(false);
687
688                // Keep the shutter button disabled when in video capture intent
689                // mode and recording is stopped. It'll be re-enabled when
690                // re-take button is clicked.
691                if (!(mIsVideoCaptureIntent && stop)) {
692                    mHandler.sendEmptyMessageDelayed(
693                            ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
694                }
695                break;
696        }
697    }
698
699    private OnScreenHint mStorageHint;
700
701    private void updateAndShowStorageHint() {
702        mStorageSpace = Storage.getAvailableSpace();
703        showStorageHint();
704    }
705
706    private void showStorageHint() {
707        String errorMessage = null;
708        if (mStorageSpace == Storage.UNAVAILABLE) {
709            errorMessage = getString(R.string.no_storage);
710        } else if (mStorageSpace == Storage.PREPARING) {
711            errorMessage = getString(R.string.preparing_sd);
712        } else if (mStorageSpace == Storage.UNKNOWN_SIZE) {
713            errorMessage = getString(R.string.access_sd_fail);
714        } else if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
715            errorMessage = getString(R.string.spaceIsLow_content);
716        }
717
718        if (errorMessage != null) {
719            if (mStorageHint == null) {
720                mStorageHint = OnScreenHint.makeText(this, errorMessage);
721            } else {
722                mStorageHint.setText(errorMessage);
723            }
724            mStorageHint.show();
725        } else if (mStorageHint != null) {
726            mStorageHint.cancel();
727            mStorageHint = null;
728        }
729    }
730
731    private void readVideoPreferences() {
732        String quality = mPreferences.getString(
733                CameraSettings.KEY_VIDEO_QUALITY,
734                CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE);
735        boolean videoQualityHigh =
736                CameraSettings.getVideoQuality(this, quality);
737
738        // Set video quality.
739        Intent intent = getIntent();
740        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
741            int extraVideoQuality =
742                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
743            if (extraVideoQuality > 0) {
744                quality = getString(R.string.pref_video_quality_high);
745            } else {  // 0 is mms.
746                quality = getString(R.string.pref_video_quality_mms);
747            }
748        }
749
750        // Set video duration limit. The limit is read from the preference,
751        // unless it is specified in the intent.
752        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
753            int seconds =
754                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
755            mMaxVideoDurationInMs = 1000 * seconds;
756        } else {
757            mMaxVideoDurationInMs =
758                    CameraSettings.getVideoDurationInMillis(this, quality, mCameraId);
759        }
760
761        // Read time lapse recording interval.
762        String frameIntervalStr = mPreferences.getString(
763                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
764                getString(R.string.pref_video_time_lapse_frame_interval_default));
765        mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
766
767        mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
768        int profileQuality = getProfileQuality(mCameraId, quality, mCaptureTimeLapse);
769        mProfile = CamcorderProfile.get(mCameraId, profileQuality);
770        getDesiredPreviewSize();
771    }
772
773    int getProfileQuality(int cameraId, String quality, boolean captureTimeLapse) {
774        HashMap<String, Integer>[] qualityMap;
775        if (captureTimeLapse) {
776            if (mTimeLapseProfileQuality == null) {
777                mTimeLapseProfileQuality = new HashMap[
778                        CameraHolder.instance().getNumberOfCameras()];
779            }
780            qualityMap = mTimeLapseProfileQuality;
781            if (qualityMap[cameraId] == null) {
782                qualityMap[cameraId] = buildProfileQuality(cameraId, TIME_LAPSE_VIDEO_QUALITY);
783            }
784        } else {
785            if (mProfileQuality == null) {
786                mProfileQuality = new HashMap[
787                        CameraHolder.instance().getNumberOfCameras()];
788            }
789            qualityMap = mProfileQuality;
790            if (qualityMap[cameraId] == null) {
791                qualityMap[cameraId] = buildProfileQuality(cameraId, VIDEO_QUALITY);
792            }
793        }
794        return qualityMap[cameraId].get(quality);
795    }
796
797    HashMap<String, Integer> buildProfileQuality(int cameraId,
798            int qualityList[]) {
799        HashMap<String, Integer> qualityMap = new HashMap<String, Integer>();
800        int highestQuality = -1, secondHighestQuality = -1,
801                lastEffectiveQuality = -1;
802        for (int i = 0; i < qualityList.length; i++) {
803            if (CamcorderProfile.hasProfile(cameraId, qualityList[i])) {
804                if (highestQuality == -1) {
805                    highestQuality = qualityList[i];
806                } else if (secondHighestQuality == -1) {
807                    secondHighestQuality = qualityList[i];
808                    break;
809                }
810                lastEffectiveQuality = qualityList[i];
811            }
812        }
813        if (secondHighestQuality == -1) {
814            secondHighestQuality = highestQuality;
815        }
816        qualityMap.put(getString(R.string.pref_video_quality_high), highestQuality);
817        qualityMap.put(getString(R.string.pref_video_quality_low), secondHighestQuality);
818        qualityMap.put(getString(R.string.pref_video_quality_youtube), highestQuality);
819        qualityMap.put(getString(R.string.pref_video_quality_mms), lastEffectiveQuality);
820        return qualityMap;
821    }
822
823    private void getDesiredPreviewSize() {
824        mParameters = mCameraDevice.getParameters();
825        if (mParameters.getSupportedVideoSizes() == null) {
826            mDesiredPreviewWidth = mProfile.videoFrameWidth;
827            mDesiredPreviewHeight = mProfile.videoFrameHeight;
828        } else {  // Driver supports separates outputs for preview and video.
829            List<Size> sizes = mParameters.getSupportedPreviewSizes();
830            Size preferred = mParameters.getPreferredPreviewSizeForVideo();
831            int product = preferred.width * preferred.height;
832            Iterator it = sizes.iterator();
833            // Remove the preview sizes that are not preferred.
834            while (it.hasNext()) {
835                Size size = (Size) it.next();
836                if (size.width * size.height > product) {
837                    it.remove();
838                }
839            }
840            Size optimalSize = Util.getOptimalPreviewSize(this, sizes,
841                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
842            mDesiredPreviewWidth = optimalSize.width;
843            mDesiredPreviewHeight = optimalSize.height;
844        }
845        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
846                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
847    }
848
849    private void resizeForPreviewAspectRatio() {
850        mPreviewFrameLayout.setAspectRatio(
851                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
852    }
853
854    @Override
855    protected void onResume() {
856        super.onResume();
857        mPausing = false;
858        if (mOpenCameraFail || mCameraDisabled) return;
859        mZoomValue = 0;
860
861        mReviewImage.setVisibility(View.GONE);
862
863        // Start orientation listener as soon as possible because it takes
864        // some time to get first orientation.
865        mOrientationListener.enable();
866        if (!mPreviewing) {
867            try {
868                mCameraDevice = Util.openCamera(this, mCameraId);
869                readVideoPreferences();
870                resizeForPreviewAspectRatio();
871                startPreview();
872            } catch(CameraHardwareException e) {
873                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
874                return;
875            } catch(CameraDisabledException e) {
876                Util.showErrorAndFinish(this, R.string.camera_disabled);
877                return;
878            }
879        }
880        keepScreenOnAwhile();
881
882        // install an intent filter to receive SD card related events.
883        IntentFilter intentFilter =
884                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
885        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
886        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
887        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
888        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
889        intentFilter.addDataScheme("file");
890        mReceiver = new MyBroadcastReceiver();
891        registerReceiver(mReceiver, intentFilter);
892        mStorageSpace = Storage.getAvailableSpace();
893
894        mHandler.postDelayed(new Runnable() {
895            public void run() {
896                showStorageHint();
897            }
898        }, 200);
899
900        changeHeadUpDisplayState();
901
902        if (!mIsVideoCaptureIntent) {
903            updateThumbnailButton();  // Update the last video thumbnail.
904            mSwitcher.setSwitch(SWITCH_VIDEO);
905        }
906
907        if (mPreviewing) {
908            mOnResumeTime = SystemClock.uptimeMillis();
909            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
910        }
911        initializeZoom();
912    }
913
914    private void setPreviewDisplay(SurfaceHolder holder) {
915        try {
916            mCameraDevice.setPreviewDisplay(holder);
917        } catch (Throwable ex) {
918            closeCamera();
919            throw new RuntimeException("setPreviewDisplay failed", ex);
920        }
921    }
922
923    private void startPreview() {
924        Log.v(TAG, "startPreview");
925        mCameraDevice.setErrorCallback(mErrorCallback);
926
927        if (mPreviewing == true) {
928            mCameraDevice.stopPreview();
929            mPreviewing = false;
930        }
931        setPreviewDisplay(mSurfaceHolder);
932        mDisplayRotation = Util.getDisplayRotation(this);
933        Util.setCameraDisplayOrientation(mDisplayRotation, mCameraId, mCameraDevice);
934        setCameraParameters();
935
936        try {
937            mCameraDevice.startPreview();
938        } catch (Throwable ex) {
939            closeCamera();
940            throw new RuntimeException("startPreview failed", ex);
941        }
942        mZoomState = ZOOM_STOPPED;
943        mPreviewing = true;
944    }
945
946    private void closeCamera() {
947        Log.v(TAG, "closeCamera");
948        if (mCameraDevice == null) {
949            Log.d(TAG, "already stopped.");
950            return;
951        }
952        CameraHolder.instance().release();
953        mCameraDevice = null;
954        mPreviewing = false;
955    }
956
957    private void finishRecorderAndCloseCamera() {
958        // This is similar to what mShutterButton.performClick() does,
959        // but not quite the same.
960        if (mMediaRecorderRecording) {
961            if (mIsVideoCaptureIntent) {
962                stopVideoRecording();
963                showAlert();
964            } else {
965                stopVideoRecording();
966                getThumbnail();
967            }
968        } else {
969            stopVideoRecording();
970        }
971        closeCamera();
972    }
973
974    @Override
975    protected void onPause() {
976        super.onPause();
977        mPausing = true;
978
979        changeHeadUpDisplayState();
980        if (mIndicatorWheel != null) mIndicatorWheel.dismissSettingPopup();
981
982        finishRecorderAndCloseCamera();
983
984        dismissSharePopup();
985
986        if (mReceiver != null) {
987            unregisterReceiver(mReceiver);
988            mReceiver = null;
989        }
990        resetScreenOn();
991
992        if (!mIsVideoCaptureIntent && mThumbnail != null) {
993            mThumbnail.saveTo(new File(getFilesDir(), LAST_THUMB_FILENAME));
994        }
995
996        if (mStorageHint != null) {
997            mStorageHint.cancel();
998            mStorageHint = null;
999        }
1000
1001        mOrientationListener.disable();
1002
1003        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1004    }
1005
1006    @Override
1007    public void onUserInteraction() {
1008        super.onUserInteraction();
1009        if (!mMediaRecorderRecording) keepScreenOnAwhile();
1010    }
1011
1012    @Override
1013    public void onBackPressed() {
1014        if (mPausing) return;
1015        if (mMediaRecorderRecording) {
1016            onStopVideoRecording(false);
1017        } else if (!collapseCameraControls()) {
1018            super.onBackPressed();
1019        }
1020    }
1021
1022    @Override
1023    public boolean onKeyDown(int keyCode, KeyEvent event) {
1024        // Do not handle any key if the activity is paused.
1025        if (mPausing) {
1026            return true;
1027        }
1028
1029        switch (keyCode) {
1030            case KeyEvent.KEYCODE_CAMERA:
1031                if (event.getRepeatCount() == 0) {
1032                    mShutterButton.performClick();
1033                    return true;
1034                }
1035                break;
1036            case KeyEvent.KEYCODE_DPAD_CENTER:
1037                if (event.getRepeatCount() == 0) {
1038                    mShutterButton.performClick();
1039                    return true;
1040                }
1041                break;
1042            case KeyEvent.KEYCODE_MENU:
1043                if (mMediaRecorderRecording) return true;
1044                break;
1045        }
1046
1047        return super.onKeyDown(keyCode, event);
1048    }
1049
1050    @Override
1051    public boolean onKeyUp(int keyCode, KeyEvent event) {
1052        switch (keyCode) {
1053            case KeyEvent.KEYCODE_CAMERA:
1054                mShutterButton.setPressed(false);
1055                return true;
1056        }
1057        return super.onKeyUp(keyCode, event);
1058    }
1059
1060    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1061        // Make sure we have a surface in the holder before proceeding.
1062        if (holder.getSurface() == null) {
1063            Log.d(TAG, "holder.getSurface() == null");
1064            return;
1065        }
1066
1067        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1068
1069        mSurfaceHolder = holder;
1070
1071        if (mPausing) {
1072            // We're pausing, the screen is off and we already stopped
1073            // video recording. We don't want to start the camera again
1074            // in this case in order to conserve power.
1075            // The fact that surfaceChanged is called _after_ an onPause appears
1076            // to be legitimate since in that case the lockscreen always returns
1077            // to portrait orientation possibly triggering the notification.
1078            return;
1079        }
1080
1081        // The mCameraDevice will be null if it is fail to connect to the
1082        // camera hardware. In this case we will show a dialog and then
1083        // finish the activity, so it's OK to ignore it.
1084        if (mCameraDevice == null) return;
1085
1086        // Set preview display if the surface is being created. Preview was
1087        // already started. Also restart the preview if display rotation has
1088        // changed. Sometimes this happens when the device is held in portrait
1089        // and camera app is opened. Rotation animation takes some time and
1090        // display rotation in onCreate may not be what we want.
1091        if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation)
1092                && holder.isCreating()) {
1093            setPreviewDisplay(holder);
1094        } else {
1095            stopVideoRecording();
1096            startPreview();
1097        }
1098    }
1099
1100    public void surfaceCreated(SurfaceHolder holder) {
1101    }
1102
1103    public void surfaceDestroyed(SurfaceHolder holder) {
1104        mSurfaceHolder = null;
1105    }
1106
1107    private void gotoGallery() {
1108        MenuHelper.gotoCameraVideoGallery(this);
1109    }
1110
1111    @Override
1112    public boolean onCreateOptionsMenu(Menu menu) {
1113        super.onCreateOptionsMenu(menu);
1114
1115        if (mIsVideoCaptureIntent) {
1116            // No options menu for attach mode.
1117            return false;
1118        } else {
1119            addBaseMenuItems(menu);
1120        }
1121        return true;
1122    }
1123
1124    private boolean isVideoCaptureIntent() {
1125        String action = getIntent().getAction();
1126        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1127    }
1128
1129    private void doReturnToCaller(boolean valid) {
1130        Intent resultIntent = new Intent();
1131        int resultCode;
1132        if (valid) {
1133            resultCode = RESULT_OK;
1134            resultIntent.setData(mCurrentVideoUri);
1135        } else {
1136            resultCode = RESULT_CANCELED;
1137        }
1138        setResultEx(resultCode, resultIntent);
1139        finish();
1140    }
1141
1142    private void cleanupEmptyFile() {
1143        if (mVideoFilename != null) {
1144            File f = new File(mVideoFilename);
1145            if (f.length() == 0 && f.delete()) {
1146                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1147                mVideoFilename = null;
1148            }
1149        }
1150    }
1151
1152    // Prepares media recorder.
1153    private void initializeRecorder() {
1154        Log.v(TAG, "initializeRecorder");
1155        // If the mCameraDevice is null, then this activity is going to finish
1156        if (mCameraDevice == null) return;
1157
1158        if (mSurfaceHolder == null) {
1159            Log.v(TAG, "Surface holder is null. Wait for surface changed.");
1160            return;
1161        }
1162
1163        Intent intent = getIntent();
1164        Bundle myExtras = intent.getExtras();
1165
1166        long requestedSizeLimit = 0;
1167        if (mIsVideoCaptureIntent && myExtras != null) {
1168            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1169            if (saveUri != null) {
1170                try {
1171                    mVideoFileDescriptor =
1172                            mContentResolver.openFileDescriptor(saveUri, "rw");
1173                    mCurrentVideoUri = saveUri;
1174                } catch (java.io.FileNotFoundException ex) {
1175                    // invalid uri
1176                    Log.e(TAG, ex.toString());
1177                }
1178            }
1179            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1180        }
1181        mMediaRecorder = new MediaRecorder();
1182
1183        // Unlock the camera object before passing it to media recorder.
1184        mCameraDevice.unlock();
1185        mMediaRecorder.setCamera(mCameraDevice);
1186        if (!mCaptureTimeLapse) {
1187            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1188        }
1189        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1190        mMediaRecorder.setProfile(mProfile);
1191        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1192        if (mCaptureTimeLapse) {
1193            mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1194        }
1195
1196        // Set output file.
1197        // Try Uri in the intent first. If it doesn't exist, use our own
1198        // instead.
1199        if (mVideoFileDescriptor != null) {
1200            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1201        } else {
1202            generateVideoFilename(mProfile.fileFormat);
1203            mMediaRecorder.setOutputFile(mVideoFilename);
1204        }
1205
1206        mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
1207
1208        // Set maximum file size.
1209        // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter
1210        // of that to make it more likely that recording can complete
1211        // successfully.
1212        long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD / 4;
1213        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1214            maxFileSize = requestedSizeLimit;
1215        }
1216
1217        try {
1218            mMediaRecorder.setMaxFileSize(maxFileSize);
1219        } catch (RuntimeException exception) {
1220            // We are going to ignore failure of setMaxFileSize here, as
1221            // a) The composer selected may simply not support it, or
1222            // b) The underlying media framework may not handle 64-bit range
1223            // on the size restriction.
1224        }
1225
1226        // See android.hardware.Camera.Parameters.setRotation for
1227        // documentation.
1228        int rotation = 0;
1229        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1230            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1231            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1232                rotation = (info.orientation - mOrientation + 360) % 360;
1233            } else {  // back-facing camera
1234                rotation = (info.orientation + mOrientation) % 360;
1235            }
1236        }
1237        mMediaRecorder.setOrientationHint(rotation);
1238        mOrientationHint = rotation;
1239
1240        try {
1241            mMediaRecorder.prepare();
1242        } catch (IOException e) {
1243            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1244            releaseMediaRecorder();
1245            throw new RuntimeException(e);
1246        }
1247
1248        mMediaRecorder.setOnErrorListener(this);
1249        mMediaRecorder.setOnInfoListener(this);
1250    }
1251
1252    private void releaseMediaRecorder() {
1253        Log.v(TAG, "Releasing media recorder.");
1254        if (mMediaRecorder != null) {
1255            cleanupEmptyFile();
1256            mMediaRecorder.reset();
1257            mMediaRecorder.release();
1258            mMediaRecorder = null;
1259        }
1260        mVideoFilename = null;
1261        if (mVideoFileDescriptor != null) {
1262            try {
1263                mVideoFileDescriptor.close();
1264            } catch (IOException e) {
1265                Log.e(TAG, "Fail to close fd", e);
1266            }
1267            mVideoFileDescriptor = null;
1268        }
1269    }
1270
1271    private void generateVideoFilename(int outputFileFormat) {
1272        long dateTaken = System.currentTimeMillis();
1273        String title = createName(dateTaken);
1274        String filename = title + ".3gp"; // Used when emailing.
1275        String mime = "video/3gpp";
1276        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1277            filename = title + ".mp4";
1278            mime = "video/mp4";
1279        }
1280        mVideoFilename = Storage.DIRECTORY + '/' + filename;
1281        mCurrentVideoValues = new ContentValues(7);
1282        mCurrentVideoValues.put(Video.Media.TITLE, title);
1283        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1284        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1285        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1286        mCurrentVideoValues.put(Video.Media.DATA, mVideoFilename);
1287        Log.v(TAG, "New video filename: " + mVideoFilename);
1288    }
1289
1290    private void registerVideo() {
1291        if (mVideoFileDescriptor == null) {
1292            Uri videoTable = Uri.parse("content://media/external/video/media");
1293            mCurrentVideoValues.put(Video.Media.SIZE,
1294                    new File(mCurrentVideoFilename).length());
1295            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1296            if (duration > 0) {
1297                if (mCaptureTimeLapse) {
1298                    duration = getTimeLapseVideoLength(duration);
1299                }
1300                mCurrentVideoValues.put(Video.Media.DURATION, duration);
1301            } else {
1302                Log.w(TAG, "Video duration <= 0 : " + duration);
1303            }
1304            try {
1305                mCurrentVideoUri = mContentResolver.insert(videoTable,
1306                        mCurrentVideoValues);
1307            } catch (Exception e) {
1308                // We failed to insert into the database. This can happen if
1309                // the SD card is unmounted.
1310                mCurrentVideoUri = null;
1311                mCurrentVideoFilename = null;
1312            } finally {
1313                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1314            }
1315        }
1316        mCurrentVideoValues = null;
1317    }
1318
1319    private void deleteCurrentVideo() {
1320        // Remove the video and the uri if the uri is not passed in by intent.
1321        if (mCurrentVideoFilename != null) {
1322            deleteVideoFile(mCurrentVideoFilename);
1323            mCurrentVideoFilename = null;
1324            if (mCurrentVideoUri != null) {
1325                mContentResolver.delete(mCurrentVideoUri, null, null);
1326                mCurrentVideoUri = null;
1327            }
1328        }
1329        updateAndShowStorageHint();
1330    }
1331
1332    private void deleteVideoFile(String fileName) {
1333        Log.v(TAG, "Deleting video " + fileName);
1334        File f = new File(fileName);
1335        if (!f.delete()) {
1336            Log.v(TAG, "Could not delete " + fileName);
1337        }
1338    }
1339
1340    private void addBaseMenuItems(Menu menu) {
1341        MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() {
1342            public void run() {
1343                switchToCameraMode();
1344            }
1345        });
1346        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1347                MenuHelper.POSITION_GOTO_GALLERY,
1348                R.string.camera_gallery_photos_text)
1349                .setOnMenuItemClickListener(
1350                    new OnMenuItemClickListener() {
1351                        public boolean onMenuItemClick(MenuItem item) {
1352                            gotoGallery();
1353                            return true;
1354                        }
1355                    });
1356        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1357        mGalleryItems.add(gallery);
1358
1359        if (mNumberOfCameras > 1) {
1360            menu.add(Menu.NONE, Menu.NONE,
1361                    MenuHelper.POSITION_SWITCH_CAMERA_ID,
1362                    R.string.switch_camera_id)
1363                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1364                public boolean onMenuItemClick(MenuItem item) {
1365                    CameraSettings.writePreferredCameraId(mPreferences,
1366                            ((mCameraId == mFrontCameraId)
1367                            ? mBackCameraId : mFrontCameraId));
1368                    onSharedPreferenceChanged();
1369                    return true;
1370                }
1371            }).setIcon(android.R.drawable.ic_menu_camera);
1372        }
1373    }
1374
1375    private PreferenceGroup filterPreferenceScreenByIntent(
1376            PreferenceGroup screen) {
1377        Intent intent = getIntent();
1378        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1379            CameraSettings.removePreferenceFromScreen(screen,
1380                    CameraSettings.KEY_VIDEO_QUALITY);
1381        }
1382
1383        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1384            CameraSettings.removePreferenceFromScreen(screen,
1385                    CameraSettings.KEY_VIDEO_QUALITY);
1386        }
1387        return screen;
1388    }
1389
1390    // from MediaRecorder.OnErrorListener
1391    public void onError(MediaRecorder mr, int what, int extra) {
1392        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1393        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1394            // We may have run out of space on the sdcard.
1395            stopVideoRecording();
1396            updateAndShowStorageHint();
1397        }
1398    }
1399
1400    // from MediaRecorder.OnInfoListener
1401    public void onInfo(MediaRecorder mr, int what, int extra) {
1402        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1403            if (mMediaRecorderRecording) onStopVideoRecording(true);
1404        } else if (what
1405                == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1406            if (mMediaRecorderRecording) onStopVideoRecording(true);
1407
1408            // Show the toast.
1409            Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit,
1410                           Toast.LENGTH_LONG).show();
1411        }
1412    }
1413
1414    /*
1415     * Make sure we're not recording music playing in the background, ask the
1416     * MediaPlaybackService to pause playback.
1417     */
1418    private void pauseAudioPlayback() {
1419        // Shamelessly copied from MediaPlaybackService.java, which
1420        // should be public, but isn't.
1421        Intent i = new Intent("com.android.music.musicservicecommand");
1422        i.putExtra("command", "pause");
1423
1424        sendBroadcast(i);
1425    }
1426
1427    // For testing.
1428    public boolean isRecording() {
1429        return mMediaRecorderRecording;
1430    }
1431
1432    private void startVideoRecording() {
1433        Log.v(TAG, "startVideoRecording");
1434
1435        updateAndShowStorageHint();
1436        if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
1437            Log.v(TAG, "Storage issue, ignore the start request");
1438            return;
1439        }
1440
1441        initializeRecorder();
1442        if (mMediaRecorder == null) {
1443            Log.e(TAG, "Fail to initialize media recorder");
1444            return;
1445        }
1446
1447        pauseAudioPlayback();
1448
1449        try {
1450            mMediaRecorder.start(); // Recording is now started
1451        } catch (RuntimeException e) {
1452            Log.e(TAG, "Could not start media recorder. ", e);
1453            releaseMediaRecorder();
1454            // If start fails, frameworks will not lock the camera for us.
1455            mCameraDevice.lock();
1456            return;
1457        }
1458        enableCameraControls(false);
1459
1460        mMediaRecorderRecording = true;
1461        mRecordingStartTime = SystemClock.uptimeMillis();
1462        showRecordingUI(true);
1463
1464        updateRecordingTime();
1465        keepScreenOn();
1466    }
1467
1468    private void showRecordingUI(boolean recording) {
1469        if (recording) {
1470            mShutterButton.setImageDrawable(getResources().getDrawable(
1471                    R.drawable.btn_ic_video_record_stop));
1472            mShutterButton.setBackgroundResource(R.drawable.btn_shutter_recording);
1473            mRecordingTimeView.setText("");
1474            mRecordingTimeView.setVisibility(View.VISIBLE);
1475            if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
1476            if (mCaptureTimeLapse) {
1477                mIndicatorWheel.startTimeLapseAnimation(
1478                        mTimeBetweenTimeLapseFrameCaptureMs,
1479                        mRecordingStartTime);
1480            }
1481        } else {
1482            mShutterButton.setImageDrawable(getResources().getDrawable(
1483                    R.drawable.btn_ic_video_record));
1484            mShutterButton.setBackgroundResource(R.drawable.btn_shutter);
1485            mRecordingTimeView.setVisibility(View.GONE);
1486            if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
1487            if (mCaptureTimeLapse) {
1488                mIndicatorWheel.stopTimeLapseAnimation();
1489            }
1490        }
1491    }
1492
1493    private void getThumbnail() {
1494        if (mCurrentVideoUri != null) {
1495            Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail(
1496                    mCurrentVideoFilename, Video.Thumbnails.MINI_KIND);
1497            if (videoFrame != null) {
1498                mThumbnail = new Thumbnail(mCurrentVideoUri, videoFrame, 0);
1499                mThumbnailButton.setBitmap(mThumbnail.getBitmap());
1500            }
1501        }
1502    }
1503
1504    private void showAlert() {
1505        if (mIndicatorWheel == null) {
1506            fadeOut(findViewById(R.id.shutter_button));
1507        }
1508        if (mCurrentVideoFilename != null) {
1509            Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(
1510                    mCurrentVideoFilename, Video.Thumbnails.MINI_KIND);
1511            if (bitmap != null) {
1512                // MetadataRetriever already rotates the thumbnail. We should rotate
1513                // it back (and mirror if it is front-facing camera).
1514                CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1515                if (info[mCameraId].facing == CameraInfo.CAMERA_FACING_BACK) {
1516                    bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, false);
1517                } else {
1518                    bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, true);
1519                }
1520                mReviewImage.setImageBitmap(bitmap);
1521                mReviewImage.setVisibility(View.VISIBLE);
1522            }
1523        }
1524        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1525        for (int id : pickIds) {
1526            View button = findViewById(id);
1527            fadeIn(((View) button.getParent()));
1528        }
1529
1530        // Remove the text of the cancel button
1531        View view = findViewById(R.id.btn_cancel);
1532        if (view instanceof Button) ((Button) view).setText("");
1533        showTimeLapseUI(false);
1534    }
1535
1536    private void hideAlert() {
1537        mReviewImage.setVisibility(View.INVISIBLE);
1538        fadeIn(findViewById(R.id.shutter_button));
1539        mShutterButton.setEnabled(true);
1540        enableCameraControls(true);
1541
1542        // Restore the text of the cancel button
1543        View view = findViewById(R.id.btn_cancel);
1544        if (view instanceof Button) {
1545            ((Button) view).setText(R.string.review_cancel);
1546        }
1547
1548        int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play};
1549        for (int id : pickIds) {
1550            View button = findViewById(id);
1551            ((View) button.getParent()).setVisibility(View.GONE);
1552        }
1553        if (mCaptureTimeLapse) {
1554            showTimeLapseUI(true);
1555        }
1556    }
1557
1558    private static void fadeIn(View view) {
1559        view.setVisibility(View.VISIBLE);
1560        Animation animation = new AlphaAnimation(0F, 1F);
1561        animation.setDuration(500);
1562        view.startAnimation(animation);
1563    }
1564
1565    private static void fadeOut(View view) {
1566        view.setVisibility(View.INVISIBLE);
1567        Animation animation = new AlphaAnimation(1F, 0F);
1568        animation.setDuration(500);
1569        view.startAnimation(animation);
1570    }
1571
1572    private boolean isAlertVisible() {
1573        return this.mReviewImage.getVisibility() == View.VISIBLE;
1574    }
1575
1576    private void stopVideoRecording() {
1577        Log.v(TAG, "stopVideoRecording");
1578        if (mMediaRecorderRecording) {
1579            boolean needToRegisterRecording = false;
1580            mMediaRecorder.setOnErrorListener(null);
1581            mMediaRecorder.setOnInfoListener(null);
1582            try {
1583                mMediaRecorder.stop();
1584                mCurrentVideoFilename = mVideoFilename;
1585                Log.v(TAG, "Setting current video filename: "
1586                        + mCurrentVideoFilename);
1587                needToRegisterRecording = true;
1588            } catch (RuntimeException e) {
1589                Log.e(TAG, "stop fail",  e);
1590                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1591            }
1592            mMediaRecorderRecording = false;
1593            showRecordingUI(false);
1594            if (!mIsVideoCaptureIntent) {
1595                enableCameraControls(true);
1596            }
1597            keepScreenOnAwhile();
1598            if (needToRegisterRecording && mStorageSpace >= LOW_STORAGE_THRESHOLD) {
1599                registerVideo();
1600            }
1601        }
1602        releaseMediaRecorder();  // always release media recorder
1603    }
1604
1605    private void resetScreenOn() {
1606        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1607        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1608    }
1609
1610    private void keepScreenOnAwhile() {
1611        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1612        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1613        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1614    }
1615
1616    private void keepScreenOn() {
1617        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1618        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1619    }
1620
1621    private void initThumbnailButton() {
1622        mThumbnailButton = (RotateImageView) findViewById(R.id.review_thumbnail);
1623        mThumbnailButton.setOnClickListener(this);
1624        // Load the thumbnail from the disk.
1625        mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), LAST_THUMB_FILENAME));
1626    }
1627
1628    private void updateThumbnailButton() {
1629        if (mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver)) {
1630            mThumbnail = Thumbnail.getLastVideoThumbnail(mContentResolver);
1631        }
1632        if (mThumbnail != null) {
1633            mThumbnailButton.setBitmap(mThumbnail.getBitmap());
1634        } else {
1635            mThumbnailButton.setBitmap(null);
1636        }
1637    }
1638
1639    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1640        long seconds = milliSeconds / 1000; // round down to compute seconds
1641        long minutes = seconds / 60;
1642        long hours = minutes / 60;
1643        long remainderMinutes = minutes - (hours * 60);
1644        long remainderSeconds = seconds - (minutes * 60);
1645
1646        StringBuilder timeStringBuilder = new StringBuilder();
1647
1648        // Hours
1649        if (hours > 0) {
1650            if (hours < 10) {
1651                timeStringBuilder.append('0');
1652            }
1653            timeStringBuilder.append(hours);
1654
1655            timeStringBuilder.append(':');
1656        }
1657
1658        // Minutes
1659        if (remainderMinutes < 10) {
1660            timeStringBuilder.append('0');
1661        }
1662        timeStringBuilder.append(remainderMinutes);
1663        timeStringBuilder.append(':');
1664
1665        // Seconds
1666        if (remainderSeconds < 10) {
1667            timeStringBuilder.append('0');
1668        }
1669        timeStringBuilder.append(remainderSeconds);
1670
1671        // Centi seconds
1672        if (displayCentiSeconds) {
1673            timeStringBuilder.append('.');
1674            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1675            if (remainderCentiSeconds < 10) {
1676                timeStringBuilder.append('0');
1677            }
1678            timeStringBuilder.append(remainderCentiSeconds);
1679        }
1680
1681        return timeStringBuilder.toString();
1682    }
1683
1684    private long getTimeLapseVideoLength(long deltaMs) {
1685        // For better approximation calculate fractional number of frames captured.
1686        // This will update the video time at a higher resolution.
1687        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1688        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1689    }
1690
1691    private void updateRecordingTime() {
1692        if (!mMediaRecorderRecording) {
1693            return;
1694        }
1695        long now = SystemClock.uptimeMillis();
1696        long delta = now - mRecordingStartTime;
1697
1698        // Starting a minute before reaching the max duration
1699        // limit, we'll countdown the remaining time instead.
1700        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1701                && delta >= mMaxVideoDurationInMs - 60000);
1702
1703        long deltaAdjusted = delta;
1704        if (countdownRemainingTime) {
1705            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1706        }
1707        String text;
1708
1709        long targetNextUpdateDelay;
1710        if (!mCaptureTimeLapse) {
1711            text = millisecondToTimeString(deltaAdjusted, false);
1712            targetNextUpdateDelay = 1000;
1713        } else {
1714            // The length of time lapse video is different from the length
1715            // of the actual wall clock time elapsed. Display the video length
1716            // only in format hh:mm:ss.dd, where dd are the centi seconds.
1717            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1718            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1719        }
1720
1721        mRecordingTimeView.setText(text);
1722
1723        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1724            // Avoid setting the color on every update, do it only
1725            // when it needs changing.
1726            mRecordingTimeCountsDown = countdownRemainingTime;
1727
1728            int color = getResources().getColor(countdownRemainingTime
1729                    ? R.color.recording_time_remaining_text
1730                    : R.color.recording_time_elapsed_text);
1731
1732            mRecordingTimeView.setTextColor(color);
1733        }
1734
1735        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1736        mHandler.sendEmptyMessageDelayed(
1737                UPDATE_RECORD_TIME, actualNextUpdateDelay);
1738    }
1739
1740    private static boolean isSupported(String value, List<String> supported) {
1741        return supported == null ? false : supported.indexOf(value) >= 0;
1742    }
1743
1744    private void setCameraParameters() {
1745        mParameters = mCameraDevice.getParameters();
1746
1747        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1748        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1749
1750        // Set flash mode.
1751        String flashMode = mPreferences.getString(
1752                CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1753                getString(R.string.pref_camera_video_flashmode_default));
1754        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1755        if (isSupported(flashMode, supportedFlash)) {
1756            mParameters.setFlashMode(flashMode);
1757        } else {
1758            flashMode = mParameters.getFlashMode();
1759            if (flashMode == null) {
1760                flashMode = getString(
1761                        R.string.pref_camera_flashmode_no_flash);
1762            }
1763        }
1764
1765        // Set white balance parameter.
1766        String whiteBalance = mPreferences.getString(
1767                CameraSettings.KEY_WHITE_BALANCE,
1768                getString(R.string.pref_camera_whitebalance_default));
1769        if (isSupported(whiteBalance,
1770                mParameters.getSupportedWhiteBalance())) {
1771            mParameters.setWhiteBalance(whiteBalance);
1772        } else {
1773            whiteBalance = mParameters.getWhiteBalance();
1774            if (whiteBalance == null) {
1775                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1776            }
1777        }
1778
1779        // Set zoom.
1780        if (mParameters.isZoomSupported()) {
1781            mParameters.setZoom(mZoomValue);
1782        }
1783
1784        mCameraDevice.setParameters(mParameters);
1785        // Keep preview size up to date.
1786        mParameters = mCameraDevice.getParameters();
1787    }
1788
1789    private boolean switchToCameraMode() {
1790        if (isFinishing() || mMediaRecorderRecording) return false;
1791        MenuHelper.gotoCameraMode(VideoCamera.this);
1792        finish();
1793        return true;
1794    }
1795
1796    public boolean onSwitchChanged(Switcher source, boolean onOff) {
1797        if (onOff == SWITCH_CAMERA) {
1798            return switchToCameraMode();
1799        } else {
1800            return true;
1801        }
1802    }
1803
1804    @Override
1805    public void onConfigurationChanged(Configuration config) {
1806        super.onConfigurationChanged(config);
1807
1808        // If the camera resumes behind the lock screen, the orientation
1809        // will be portrait. That causes OOM when we try to allocation GPU
1810        // memory for the GLSurfaceView again when the orientation changes. So,
1811        // we delayed initialization of HeadUpDisplay until the orientation
1812        // becomes landscape.
1813        changeHeadUpDisplayState();
1814    }
1815
1816    public void onSizeChanged() {
1817        // TODO: update the content on GLRootView
1818    }
1819
1820    private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
1821        public void onSharedPreferenceChanged() {
1822            mHandler.post(new Runnable() {
1823                public void run() {
1824                    VideoCamera.this.onSharedPreferenceChanged();
1825                }
1826            });
1827        }
1828
1829        public void onRestorePreferencesClicked() {
1830            mHandler.post(new Runnable() {
1831                public void run() {
1832                    VideoCamera.this.onRestorePreferencesClicked();
1833                }
1834            });
1835        }
1836
1837        public void onPopupWindowVisibilityChanged(final int visibility) {
1838        }
1839    }
1840
1841    private void onRestorePreferencesClicked() {
1842        Runnable runnable = new Runnable() {
1843            public void run() {
1844                restorePreferences();
1845            }
1846        };
1847        MenuHelper.confirmAction(this,
1848                getString(R.string.confirm_restore_title),
1849                getString(R.string.confirm_restore_message),
1850                runnable);
1851    }
1852
1853    private void restorePreferences() {
1854        // Reset the zoom. Zoom value is not stored in preference.
1855        if (mParameters.isZoomSupported()) {
1856            mZoomValue = 0;
1857            setCameraParameters();
1858            if (mZoomPicker != null) mZoomPicker.setZoomIndex(0);
1859        }
1860
1861        if (mHeadUpDisplay != null) {
1862            mHeadUpDisplay.restorePreferences(mParameters);
1863        }
1864
1865        if (mIndicatorWheel != null) {
1866            mIndicatorWheel.dismissSettingPopup();
1867            CameraSettings.restorePreferences(VideoCamera.this, mPreferences,
1868                    mParameters);
1869            initializeIndicatorWheel();
1870            onSharedPreferenceChanged();
1871        }
1872    }
1873
1874    private void onSharedPreferenceChanged() {
1875        // ignore the events after "onPause()" or preview has not started yet
1876        if (mPausing) return;
1877        synchronized (mPreferences) {
1878            // If mCameraDevice is not ready then we can set the parameter in
1879            // startPreview().
1880            if (mCameraDevice == null) return;
1881
1882            // Check if camera id is changed.
1883            int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
1884            if (mCameraId != cameraId) {
1885                // Restart the activity to have a crossfade animation.
1886                // TODO: Use SurfaceTexture to implement a better and faster
1887                // animation.
1888                if (mIsVideoCaptureIntent) {
1889                    // If the intent is video capture, stay in video capture mode.
1890                    MenuHelper.gotoVideoMode(this, getIntent());
1891                } else {
1892                    MenuHelper.gotoVideoMode(this);
1893                }
1894                finish();
1895            } else {
1896                readVideoPreferences();
1897                // We need to restart the preview if preview size is changed.
1898                Size size = mParameters.getPreviewSize();
1899                if (size.width != mDesiredPreviewWidth
1900                        || size.height != mDesiredPreviewHeight) {
1901                    mCameraDevice.stopPreview();
1902                    resizeForPreviewAspectRatio();
1903                    startPreview(); // Parameters will be set in startPreview().
1904                } else {
1905                    setCameraParameters();
1906                }
1907            }
1908            showTimeLapseUI(mCaptureTimeLapse);
1909        }
1910    }
1911
1912    private void showTimeLapseUI(boolean enable) {
1913        if (mTimeLapseLabel != null) {
1914            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
1915        }
1916        if (mPreviewBorder != null) {
1917            mPreviewBorder.setBackgroundResource(enable
1918                    ? R.drawable.border_preview_time_lapse
1919                    : R.drawable.border_preview);
1920        }
1921
1922    }
1923
1924    private void createSharePopup() {
1925        if (mSharePopup != null) mSharePopup.dismiss();
1926        mSharePopup = new SharePopup(this, mThumbnail.getUri(),
1927                mThumbnail.getBitmap(), mOrientationCompensation, mThumbnailButton);
1928        mSharePopup.showAtLocation(mThumbnailButton, Gravity.NO_GRAVITY, 0, 0);
1929    }
1930
1931    private void dismissSharePopup() {
1932        if (mSharePopup != null) {
1933            mSharePopup.dismiss();
1934            mSharePopup = null;
1935        }
1936    }
1937
1938    private void onShareButtonClicked() {
1939        if (mPausing) return;
1940
1941        // Share the last captured video.
1942        if (mThumbnail != null) {
1943            Intent intent = new Intent(Intent.ACTION_SEND);
1944            intent.setType("video/*");
1945            intent.putExtra(Intent.EXTRA_STREAM, mThumbnail.getUri());
1946            startActivity(Intent.createChooser(intent, getString(R.string.share_video_via)));
1947        } else {  // No last picture
1948            if (mNoShareToast == null) {
1949                mNoShareToast = Toast.makeText(this,
1950                        getResources().getString(R.string.no_video_to_share), Toast.LENGTH_SHORT);
1951            }
1952            mNoShareToast.show();
1953        }
1954    }
1955
1956    private class MyIndicatorWheelListener implements IndicatorWheel.Listener {
1957        public void onSharedPreferenceChanged() {
1958            VideoCamera.this.onSharedPreferenceChanged();
1959        }
1960
1961        public void onRestorePreferencesClicked() {
1962            VideoCamera.this.onRestorePreferencesClicked();
1963        }
1964
1965        public void onOverriddenPreferencesClicked() {
1966        }
1967
1968        public void onShareButtonClicked() {
1969            VideoCamera.this.onShareButtonClicked();
1970        }
1971    }
1972
1973    private class MyCameraPickerListener implements CameraPicker.Listener {
1974        public void onSharedPreferenceChanged() {
1975            VideoCamera.this.onSharedPreferenceChanged();
1976        }
1977    }
1978
1979    @Override
1980    public boolean dispatchTouchEvent(MotionEvent m) {
1981        // Check if the popup window should be dismissed first.
1982        if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
1983            return true;
1984        }
1985
1986        return super.dispatchTouchEvent(m);
1987    }
1988
1989    private class PopupGestureListener extends
1990            GestureDetector.SimpleOnGestureListener {
1991        @Override
1992        public boolean onDown(MotionEvent e) {
1993            // Check if the popup window is visible.
1994            View popup = mIndicatorWheel.getActiveSettingPopup();
1995            if (popup == null) return false;
1996
1997            // Let popup window or indicator wheel handle the event by
1998            // themselves. Dismiss the popup window if users touch on other
1999            // areas.
2000            if (!Util.pointInView(e.getX(), e.getY(), popup)
2001                    && !Util.pointInView(e.getX(), e.getY(), mIndicatorWheel)) {
2002                mIndicatorWheel.dismissSettingPopup();
2003                // Let event fall through.
2004            }
2005            return false;
2006        }
2007    }
2008
2009    private void initializeZoom() {
2010        if (!mParameters.isZoomSupported()) return;
2011
2012        mZoomMax = mParameters.getMaxZoom();
2013        mSmoothZoomSupported = mParameters.isSmoothZoomSupported();
2014        if (mZoomPicker != null) {
2015            mZoomPicker.setZoomRatios(Util.convertZoomRatios(mParameters.getZoomRatios()));
2016            mZoomPicker.setZoomIndex(mParameters.getZoom());
2017            mZoomPicker.setSmoothZoomSupported(mSmoothZoomSupported);
2018            mZoomPicker.setOnZoomChangeListener(
2019                    new ZoomPicker.OnZoomChangedListener() {
2020                // only for immediate zoom
2021                @Override
2022                public void onZoomValueChanged(int index) {
2023                    VideoCamera.this.onZoomValueChanged(index);
2024                }
2025
2026                // only for smooth zoom
2027                @Override
2028                public void onZoomStateChanged(int state) {
2029                    if (mPausing) return;
2030
2031                    Log.v(TAG, "zoom picker state=" + state);
2032                    if (state == ZoomPicker.ZOOM_IN) {
2033                        VideoCamera.this.onZoomValueChanged(mZoomMax);
2034                    } else if (state == ZoomPicker.ZOOM_OUT){
2035                        VideoCamera.this.onZoomValueChanged(0);
2036                    } else {
2037                        mTargetZoomValue = -1;
2038                        if (mZoomState == ZOOM_START) {
2039                            mZoomState = ZOOM_STOPPING;
2040                            mCameraDevice.stopSmoothZoom();
2041                        }
2042                    }
2043                }
2044            });
2045        }
2046
2047        mCameraDevice.setZoomChangeListener(mZoomListener);
2048    }
2049
2050    private final class ZoomListener
2051            implements android.hardware.Camera.OnZoomChangeListener {
2052        @Override
2053        public void onZoomChange(int value, boolean stopped, android.hardware.Camera camera) {
2054            Log.v(TAG, "Zoom changed: value=" + value + ". stopped="+ stopped);
2055            mZoomValue = value;
2056
2057            // Update the UI when we get zoom value.
2058            if (mZoomPicker != null) mZoomPicker.setZoomIndex(value);
2059
2060            // Keep mParameters up to date. We do not getParameter again in
2061            // takePicture. If we do not do this, wrong zoom value will be set.
2062            mParameters.setZoom(value);
2063
2064            if (stopped && mZoomState != ZOOM_STOPPED) {
2065                if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
2066                    mCameraDevice.startSmoothZoom(mTargetZoomValue);
2067                    mZoomState = ZOOM_START;
2068                } else {
2069                    mZoomState = ZOOM_STOPPED;
2070                }
2071            }
2072        }
2073    }
2074
2075    private void onZoomValueChanged(int index) {
2076        // Not useful to change zoom value when the activity is paused.
2077        if (mPausing) return;
2078
2079        if (mSmoothZoomSupported) {
2080            if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
2081                mTargetZoomValue = index;
2082                if (mZoomState == ZOOM_START) {
2083                    mZoomState = ZOOM_STOPPING;
2084                    mCameraDevice.stopSmoothZoom();
2085                }
2086            } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
2087                mTargetZoomValue = index;
2088                mCameraDevice.startSmoothZoom(index);
2089                mZoomState = ZOOM_START;
2090            }
2091        } else {
2092            mZoomValue = index;
2093            setCameraParameters();
2094        }
2095    }
2096}
2097