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