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