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