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