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