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