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