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