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