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