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