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