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