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