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