VideoModule.java revision ce1d8396924bf4ca9be076cfdf9594c4d33ebd9b
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.content.ActivityNotFoundException;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.SharedPreferences.Editor;
29import android.content.res.Configuration;
30import android.graphics.Bitmap;
31import android.hardware.Camera.CameraInfo;
32import android.hardware.Camera.Parameters;
33import android.hardware.Camera.PictureCallback;
34import android.hardware.Camera.Size;
35import android.location.Location;
36import android.media.CamcorderProfile;
37import android.media.CameraProfile;
38import android.media.MediaRecorder;
39import android.net.Uri;
40import android.os.Build;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.os.ParcelFileDescriptor;
45import android.os.SystemClock;
46import android.provider.MediaStore;
47import android.provider.MediaStore.Video;
48import android.util.Log;
49import android.view.KeyEvent;
50import android.view.LayoutInflater;
51import android.view.MotionEvent;
52import android.view.OrientationEventListener;
53import android.view.SurfaceHolder;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.WindowManager;
57import android.widget.ImageView;
58import android.widget.LinearLayout;
59import android.widget.TextView;
60import android.widget.Toast;
61
62import com.android.camera.ui.CameraPicker;
63import com.android.camera.ui.IndicatorControlContainer;
64import com.android.camera.ui.PopupManager;
65import com.android.camera.ui.PreviewSurfaceView;
66import com.android.camera.ui.Rotatable;
67import com.android.camera.ui.RotateImageView;
68import com.android.camera.ui.RotateLayout;
69import com.android.camera.ui.RotateTextToast;
70import com.android.camera.ui.TwoStateImageView;
71import com.android.camera.ui.ZoomControl;
72import com.android.gallery3d.common.ApiHelper;
73
74import java.io.File;
75import java.io.IOException;
76import java.text.SimpleDateFormat;
77import java.util.Date;
78import java.util.Iterator;
79import java.util.List;
80
81public class VideoModule implements CameraModule,
82    CameraPreference.OnPreferenceChangedListener,
83    ShutterButton.OnShutterButtonListener, MediaRecorder.OnErrorListener,
84    MediaRecorder.OnInfoListener, ModePicker.OnModeChangeListener,
85    EffectsRecorder.EffectsListener {
86
87    private static final String TAG = "CAM_VideoModule";
88
89    // We number the request code from 1000 to avoid collision with Gallery.
90    private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
91
92    private static final int CHECK_DISPLAY_ROTATION = 3;
93    private static final int CLEAR_SCREEN_DELAY = 4;
94    private static final int UPDATE_RECORD_TIME = 5;
95    private static final int ENABLE_SHUTTER_BUTTON = 6;
96    private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
97    private static final int SWITCH_CAMERA = 8;
98    private static final int SWITCH_CAMERA_START_ANIMATION = 9;
99    private static final int HIDE_SURFACE_VIEW = 10;
100
101    private static final int SCREEN_DELAY = 2 * 60 * 1000;
102
103    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
104
105    /**
106     * An unpublished intent flag requesting to start recording straight away
107     * and return as soon as recording is stopped.
108     * TODO: consider publishing by moving into MediaStore.
109     */
110    private static final String EXTRA_QUICK_CAPTURE =
111            "android.intent.extra.quickCapture";
112
113    private static final int MIN_THUMB_SIZE = 64;
114    // module fields
115    private CameraActivity mActivity;
116    private View mRootView;
117    private boolean mPaused;
118    private int mCameraId;
119    private Parameters mParameters;
120
121    private boolean mSnapshotInProgress = false;
122
123    private static final String EFFECT_BG_FROM_GALLERY = "gallery";
124
125    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
126
127    private ComboPreferences mPreferences;
128    private PreferenceGroup mPreferenceGroup;
129
130    private PreviewFrameLayout mPreviewFrameLayout;
131    private boolean mSurfaceViewReady;
132    private SurfaceHolder.Callback mSurfaceViewCallback;
133    private PreviewSurfaceView mPreviewSurfaceView;
134    private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener;
135    private IndicatorControlContainer mIndicatorControlContainer;
136    private View mReviewControl;
137    private RotateDialogController mRotateDialog;
138
139    // An review image having same size as preview. It is displayed when
140    // recording is stopped in capture intent.
141    private ImageView mReviewImage;
142    private Rotatable mReviewCancelButton;
143    private Rotatable mReviewDoneButton;
144    private RotateImageView mReviewPlayButton;
145    private View mReviewRetakeButton;
146    private ModePicker mModePicker;
147    private ShutterButton mShutterButton;
148    private TextView mRecordingTimeView;
149    private RotateLayout mBgLearningMessageRotater;
150    private View mBgLearningMessageFrame;
151    private LinearLayout mLabelsLinearLayout;
152
153    private boolean mIsVideoCaptureIntent;
154    private boolean mQuickCapture;
155
156    private MediaRecorder mMediaRecorder;
157    private EffectsRecorder mEffectsRecorder;
158    private boolean mEffectsDisplayResult;
159
160    private int mEffectType = EffectsRecorder.EFFECT_NONE;
161    private Object mEffectParameter = null;
162    private String mEffectUriFromGallery = null;
163    private String mPrefVideoEffectDefault;
164    private boolean mResetEffect = true;
165
166    private boolean mSwitchingCamera;
167    private boolean mMediaRecorderRecording = false;
168    private long mRecordingStartTime;
169    private boolean mRecordingTimeCountsDown = false;
170    private RotateLayout mRecordingTimeRect;
171    private long mOnResumeTime;
172    // The video file that the hardware camera is about to record into
173    // (or is recording into.)
174    private String mVideoFilename;
175    private ParcelFileDescriptor mVideoFileDescriptor;
176
177    // The video file that has already been recorded, and that is being
178    // examined by the user.
179    private String mCurrentVideoFilename;
180    private Uri mCurrentVideoUri;
181    private ContentValues mCurrentVideoValues;
182
183    private CamcorderProfile mProfile;
184
185    // The video duration limit. 0 menas no limit.
186    private int mMaxVideoDurationInMs;
187
188    // Time Lapse parameters.
189    private boolean mCaptureTimeLapse = false;
190    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
191    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
192    private View mTimeLapseLabel;
193
194    private int mDesiredPreviewWidth;
195    private int mDesiredPreviewHeight;
196
197    boolean mPreviewing = false; // True if preview is started.
198    // The display rotation in degrees. This is only valid when mPreviewing is
199    // true.
200    private int mDisplayRotation;
201    private int mCameraDisplayOrientation;
202
203    private ContentResolver mContentResolver;
204
205    private LocationManager mLocationManager;
206
207    private VideoNamer mVideoNamer;
208
209    private final Handler mHandler = new MainHandler();
210
211    private MyOrientationEventListener mOrientationListener;
212    // The degrees of the device rotated clockwise from its natural orientation.
213    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
214    // The orientation compensation for icons and thumbnails. Ex: if the value
215    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
216    private int mOrientationCompensation = 0;
217    // The orientation compensation when we start recording.
218    private int mOrientationCompensationAtRecordStart;
219
220    private int mZoomValue;  // The current zoom value.
221    private int mZoomMax;
222    private ZoomControl mZoomControl;
223    private boolean mRestoreFlash;  // This is used to check if we need to restore the flash
224                                    // status when going back from gallery.
225
226    protected class CameraOpenThread extends Thread {
227        @Override
228        public void run() {
229            try {
230                mActivity.mCameraDevice = Util.openCamera(mActivity, mCameraId);
231                mParameters = mActivity.mCameraDevice.getParameters();
232            } catch (CameraHardwareException e) {
233                mActivity.mOpenCameraFail = true;
234            } catch (CameraDisabledException e) {
235                mActivity.mCameraDisabled = true;
236            }
237        }
238    }
239
240    // This Handler is used to post message back onto the main thread of the
241    // application
242    private class MainHandler extends Handler {
243        @Override
244        public void handleMessage(Message msg) {
245            switch (msg.what) {
246
247                case ENABLE_SHUTTER_BUTTON:
248                    mShutterButton.setEnabled(true);
249                    break;
250
251                case CLEAR_SCREEN_DELAY: {
252                    mActivity.getWindow().clearFlags(
253                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
254                    break;
255                }
256
257                case UPDATE_RECORD_TIME: {
258                    updateRecordingTime();
259                    break;
260                }
261
262                case CHECK_DISPLAY_ROTATION: {
263                    // Restart the preview if display rotation has changed.
264                    // Sometimes this happens when the device is held upside
265                    // down and camera app is opened. Rotation animation will
266                    // take some time and the rotation value we have got may be
267                    // wrong. Framework does not have a callback for this now.
268                    if ((Util.getDisplayRotation(mActivity) != mDisplayRotation)
269                            && !mMediaRecorderRecording && !mSwitchingCamera) {
270                        startPreview();
271                    }
272                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
273                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
274                    }
275                    break;
276                }
277
278                case SHOW_TAP_TO_SNAPSHOT_TOAST: {
279                    showTapToSnapshotToast();
280                    break;
281                }
282
283                case SWITCH_CAMERA: {
284                    switchCamera();
285                    break;
286                }
287
288                case SWITCH_CAMERA_START_ANIMATION: {
289                    ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
290
291                    // Enable all camera controls.
292                    mSwitchingCamera = false;
293                    break;
294                }
295
296                case HIDE_SURFACE_VIEW: {
297                    mPreviewSurfaceView.setVisibility(View.GONE);
298                    break;
299                }
300
301                default:
302                    Log.v(TAG, "Unhandled message: " + msg.what);
303                    break;
304            }
305        }
306    }
307
308    private BroadcastReceiver mReceiver = null;
309
310    private class MyBroadcastReceiver extends BroadcastReceiver {
311        @Override
312        public void onReceive(Context context, Intent intent) {
313            String action = intent.getAction();
314            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
315                stopVideoRecording();
316            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
317                Toast.makeText(mActivity,
318                        mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
319            }
320        }
321    }
322
323    private String createName(long dateTaken) {
324        Date date = new Date(dateTaken);
325        SimpleDateFormat dateFormat = new SimpleDateFormat(
326                mActivity.getString(R.string.video_file_name_format));
327
328        return dateFormat.format(date);
329    }
330
331    private int getPreferredCameraId(ComboPreferences preferences) {
332        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
333        if (intentCameraId != -1) {
334            // Testing purpose. Launch a specific camera through the intent
335            // extras.
336            return intentCameraId;
337        } else {
338            return CameraSettings.readPreferredCameraId(preferences);
339        }
340    }
341
342    private void initializeSurfaceView() {
343        mPreviewSurfaceView = (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view);
344        if (!ApiHelper.HAS_SURFACE_TEXTURE) {  // API level < 11
345            if (mSurfaceViewCallback == null) {
346                mSurfaceViewCallback = new SurfaceViewCallback();
347            }
348            mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback);
349            mPreviewSurfaceView.setVisibility(View.VISIBLE);
350        } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {  // API level < 16
351            if (mSurfaceViewCallback == null) {
352                mSurfaceViewCallback = new SurfaceViewCallback();
353                mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() {
354                    @Override
355                    public void onFrameDrawn(CameraScreenNail c) {
356                        mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW);
357                    }
358                };
359            }
360            mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback);
361        }
362    }
363
364    @Override
365    public void init(CameraActivity activity, View root, boolean reuseScreenNail) {
366        mActivity = activity;
367        mRootView = root;
368        mPreferences = new ComboPreferences(mActivity);
369        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
370        mCameraId = getPreferredCameraId(mPreferences);
371
372        mPreferences.setLocalId(mActivity, mCameraId);
373        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
374
375        mActivity.mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
376        mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
377        resetEffect();
378
379        /*
380         * To reduce startup time, we start the preview in another thread.
381         * We make sure the preview is started at the end of onCreate.
382         */
383        CameraOpenThread cameraOpenThread = new CameraOpenThread();
384        cameraOpenThread.start();
385
386        mContentResolver = mActivity.getContentResolver();
387
388        mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView);
389
390        // Surface texture is from camera screen nail and startPreview needs it.
391        // This must be done before startPreview.
392        mIsVideoCaptureIntent = isVideoCaptureIntent();
393        if (reuseScreenNail) {
394            mActivity.reuseCameraScreenNail(!mIsVideoCaptureIntent);
395        } else {
396            mActivity.createCameraScreenNail(!mIsVideoCaptureIntent);
397        }
398        initializeSurfaceView();
399
400        // Make sure camera device is opened.
401        try {
402            cameraOpenThread.join();
403            if (mActivity.mOpenCameraFail) {
404                Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
405                return;
406            } else if (mActivity.mCameraDisabled) {
407                Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
408                return;
409            }
410        } catch (InterruptedException ex) {
411            // ignore
412        }
413
414        Thread startPreviewThread = new Thread(new Runnable() {
415            @Override
416            public void run() {
417                readVideoPreferences();
418                startPreview();
419            }
420        });
421        startPreviewThread.start();
422
423        initializeControlByIntent();
424        initializeMiscControls();
425
426        mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
427        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
428        mOrientationListener = new MyOrientationEventListener(mActivity);
429        mLocationManager = new LocationManager(mActivity, null);
430
431        // Make sure preview is started.
432        try {
433            startPreviewThread.join();
434            if (mActivity.mOpenCameraFail) {
435                Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
436                return;
437            } else if (mActivity.mCameraDisabled) {
438                Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
439                return;
440            }
441        } catch (InterruptedException ex) {
442            // ignore
443        }
444
445        showTimeLapseUI(mCaptureTimeLapse);
446        initializeVideoSnapshot();
447        resizeForPreviewAspectRatio();
448
449        initializeIndicatorControl();
450    }
451
452    @Override
453    public void onStop() {}
454
455    private void loadCameraPreferences() {
456        CameraSettings settings = new CameraSettings(mActivity, mParameters,
457                mCameraId, CameraHolder.instance().getCameraInfo());
458        // Remove the video quality preference setting when the quality is given in the intent.
459        mPreferenceGroup = filterPreferenceScreenByIntent(
460                settings.getPreferenceGroup(R.xml.video_preferences));
461    }
462
463    @Override
464    public boolean collapseCameraControls() {
465        if ((mIndicatorControlContainer != null)
466                && mIndicatorControlContainer.dismissSettingPopup()) {
467            return true;
468        }
469        if (mModePicker != null && mModePicker.dismissModeSelection()) return true;
470        return false;
471    }
472
473    private void enableCameraControls(boolean enable) {
474        if (mIndicatorControlContainer != null) {
475            mIndicatorControlContainer.setEnabled(enable);
476        }
477        if (mModePicker != null) mModePicker.setEnabled(enable);
478    }
479
480    private void initializeIndicatorControl() {
481        mIndicatorControlContainer =
482                (IndicatorControlContainer) mRootView.findViewById(R.id.indicator_control);
483        if (mIndicatorControlContainer == null) return;
484        loadCameraPreferences();
485
486        final String[] SETTING_KEYS = {
487                    CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
488                    CameraSettings.KEY_WHITE_BALANCE,
489                    CameraSettings.KEY_VIDEO_EFFECT,
490                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
491                    CameraSettings.KEY_VIDEO_QUALITY};
492        final String[] OTHER_SETTING_KEYS = {
493                    CameraSettings.KEY_RECORD_LOCATION};
494
495        CameraPicker.setImageResourceId(R.drawable.ic_switch_video_facing_holo_light);
496        mIndicatorControlContainer.initialize(mActivity, mPreferenceGroup,
497                mParameters.isZoomSupported(), SETTING_KEYS, OTHER_SETTING_KEYS);
498        mIndicatorControlContainer.setListener(this);
499        mActivity.mCameraPicker = (CameraPicker) mIndicatorControlContainer.findViewById(
500                R.id.camera_picker);
501
502        if (effectsActive()) {
503            mIndicatorControlContainer.overrideSettings(
504                    CameraSettings.KEY_VIDEO_QUALITY,
505                    Integer.toString(getLowVideoQuality()));
506        }
507    }
508
509    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
510    private static int getLowVideoQuality() {
511        if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
512            return CamcorderProfile.QUALITY_480P;
513        } else {
514            return CamcorderProfile.QUALITY_LOW;
515        }
516    }
517
518    private class MyOrientationEventListener
519            extends OrientationEventListener {
520        public MyOrientationEventListener(Context context) {
521            super(context);
522        }
523
524        @Override
525        public void onOrientationChanged(int orientation) {
526            // We keep the last known orientation. So if the user first orient
527            // the camera then point the camera to floor or sky, we still have
528            // the correct orientation.
529            if (orientation == ORIENTATION_UNKNOWN) return;
530            int newOrientation = Util.roundOrientation(orientation, mOrientation);
531
532            if (mOrientation != newOrientation) {
533                mOrientation = newOrientation;
534                // The input of effects recorder is affected by
535                // android.hardware.Camera.setDisplayOrientation. Its value only
536                // compensates the camera orientation (no Display.getRotation).
537                // So the orientation hint here should only consider sensor
538                // orientation.
539                if (effectsActive()) {
540                    mEffectsRecorder.setOrientationHint(mOrientation);
541                }
542            }
543
544            // When the screen is unlocked, display rotation may change. Always
545            // calculate the up-to-date orientationCompensation.
546            int orientationCompensation =
547                    (mOrientation + Util.getDisplayRotation(mActivity)) % 360;
548
549            if (mOrientationCompensation != orientationCompensation) {
550                mOrientationCompensation = orientationCompensation;
551                // Do not rotate the icons during recording because the video
552                // orientation is fixed after recording.
553                if (!mMediaRecorderRecording) {
554                    setOrientationIndicator(mOrientationCompensation, true);
555                }
556            }
557
558            // Show the toast after getting the first orientation changed.
559            if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
560                mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
561                showTapToSnapshotToast();
562            }
563        }
564    }
565
566    private void setOrientationIndicator(int orientation, boolean animation) {
567        Rotatable[] indicators = {
568                //mActivity.mThumbnailView, mModePicker,
569                mBgLearningMessageRotater, mIndicatorControlContainer,
570                mReviewDoneButton, mReviewPlayButton, mRotateDialog};
571        for (Rotatable indicator : indicators) {
572            if (indicator != null) indicator.setOrientation(orientation, animation);
573        }
574
575        // We change the orientation of the review cancel button only for tablet
576        // UI because there's a label along with the X icon. For phone UI, we
577        // don't change the orientation because there's only a symmetrical X
578        // icon.
579        if (mReviewCancelButton instanceof RotateLayout) {
580            mReviewCancelButton.setOrientation(orientation, animation);
581        }
582
583        // We change the orientation of the linearlayout only for phone UI because when in portrait
584        // the width is not enough.
585        if (mLabelsLinearLayout != null) {
586            if (((orientation / 90) & 1) == 0) {
587                mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
588            } else {
589                mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
590            }
591        }
592        mRecordingTimeRect.setOrientation(mOrientationCompensation, animation);
593    }
594
595    private void startPlayVideoActivity() {
596        Intent intent = new Intent(Intent.ACTION_VIEW);
597        intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
598        try {
599            mActivity.startActivity(intent);
600        } catch (ActivityNotFoundException ex) {
601            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
602        }
603    }
604
605    @OnClickAttr
606    public void onThumbnailClicked(View v) {
607        if (!mMediaRecorderRecording && mActivity.mThumbnail != null
608                && !mSwitchingCamera) {
609            mActivity.gotoGallery();
610        }
611    }
612
613    @OnClickAttr
614    public void onReviewRetakeClicked(View v) {
615        deleteCurrentVideo();
616        hideAlert();
617    }
618
619    @OnClickAttr
620    public void onReviewPlayClicked(View v) {
621        startPlayVideoActivity();
622    }
623
624    @OnClickAttr
625    public void onReviewDoneClicked(View v) {
626        doReturnToCaller(true);
627    }
628
629    @OnClickAttr
630    public void onReviewCancelClicked(View v) {
631        stopVideoRecording();
632        doReturnToCaller(false);
633    }
634
635    private void onStopVideoRecording() {
636        mEffectsDisplayResult = true;
637        boolean recordFail = stopVideoRecording();
638        if (mIsVideoCaptureIntent) {
639            if (!effectsActive()) {
640                if (mQuickCapture) {
641                    doReturnToCaller(!recordFail);
642                } else if (!recordFail) {
643                    showAlert();
644                }
645            }
646        } else if (!recordFail){
647            // Start capture animation.
648            if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
649                // The capture animation is disabled on ICS because we use SurfaceView
650                // for preview during recording. When the recording is done, we switch
651                // back to use SurfaceTexture for preview and we need to stop then start
652                // the preview. This will cause the preview flicker since the preview
653                // will not be continuous for a short period of time.
654                ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(getCameraRotation());
655            }
656            if (!effectsActive()) getThumbnail();
657        }
658    }
659
660    private int getCameraRotation() {
661        return (mOrientationCompensation - mDisplayRotation + 360) % 360;
662    }
663
664    public void onProtectiveCurtainClick(View v) {
665        // Consume clicks
666    }
667
668    @Override
669    public void onShutterButtonClick() {
670        if (collapseCameraControls() || mSwitchingCamera) return;
671
672        boolean stop = mMediaRecorderRecording;
673
674        if (stop) {
675            onStopVideoRecording();
676        } else {
677            startVideoRecording();
678        }
679        mShutterButton.setEnabled(false);
680
681        // Keep the shutter button disabled when in video capture intent
682        // mode and recording is stopped. It'll be re-enabled when
683        // re-take button is clicked.
684        if (!(mIsVideoCaptureIntent && stop)) {
685            mHandler.sendEmptyMessageDelayed(
686                    ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
687        }
688    }
689
690    @Override
691    public void onShutterButtonFocus(boolean pressed) {
692        // Do nothing (everything happens in onShutterButtonClick).
693    }
694
695    private void readVideoPreferences() {
696        // The preference stores values from ListPreference and is thus string type for all values.
697        // We need to convert it to int manually.
698        String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
699                mActivity.getResources().getString(R.string.pref_video_quality_default));
700        String videoQuality =
701                mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
702                        defaultQuality);
703        int quality = Integer.valueOf(videoQuality);
704
705        // Set video quality.
706        Intent intent = mActivity.getIntent();
707        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
708            int extraVideoQuality =
709                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
710            if (extraVideoQuality > 0) {
711                quality = CamcorderProfile.QUALITY_HIGH;
712            } else {  // 0 is mms.
713                quality = CamcorderProfile.QUALITY_LOW;
714            }
715        }
716
717        // Set video duration limit. The limit is read from the preference,
718        // unless it is specified in the intent.
719        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
720            int seconds =
721                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
722            mMaxVideoDurationInMs = 1000 * seconds;
723        } else {
724            mMaxVideoDurationInMs = CameraSettings.DEFAULT_VIDEO_DURATION;
725        }
726
727        // Set effect
728        mEffectType = CameraSettings.readEffectType(mPreferences);
729        if (mEffectType != EffectsRecorder.EFFECT_NONE) {
730            mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
731            // Set quality to be no higher than 480p.
732            CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
733            if (profile.videoFrameHeight > 480) {
734                quality = getLowVideoQuality();
735            }
736            // On initial startup, can get here before indicator control is
737            // enabled. In that case, UI quality override handled in
738            // initializeIndicatorControl.
739            if (mIndicatorControlContainer != null) {
740                mIndicatorControlContainer.overrideSettings(
741                        CameraSettings.KEY_VIDEO_QUALITY,
742                        Integer.toString(getLowVideoQuality()));
743            }
744        } else {
745            mEffectParameter = null;
746            if (mIndicatorControlContainer != null) {
747                mIndicatorControlContainer.overrideSettings(
748                        CameraSettings.KEY_VIDEO_QUALITY,
749                        null);
750            }
751        }
752        // Read time lapse recording interval.
753        if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
754            String frameIntervalStr = mPreferences.getString(
755                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
756                    mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
757            mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
758            mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
759        }
760        // TODO: This should be checked instead directly +1000.
761        if (mCaptureTimeLapse) quality += 1000;
762        mProfile = CamcorderProfile.get(mCameraId, quality);
763        getDesiredPreviewSize();
764    }
765
766    private void writeDefaultEffectToPrefs()  {
767        ComboPreferences.Editor editor = mPreferences.edit();
768        editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
769                mActivity.getString(R.string.pref_video_effect_default));
770        editor.apply();
771    }
772
773    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
774    private void getDesiredPreviewSize() {
775        mParameters = mActivity.mCameraDevice.getParameters();
776        if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
777            if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
778                mDesiredPreviewWidth = mProfile.videoFrameWidth;
779                mDesiredPreviewHeight = mProfile.videoFrameHeight;
780            } else {  // Driver supports separates outputs for preview and video.
781                List<Size> sizes = mParameters.getSupportedPreviewSizes();
782                Size preferred = mParameters.getPreferredPreviewSizeForVideo();
783                int product = preferred.width * preferred.height;
784                Iterator<Size> it = sizes.iterator();
785                // Remove the preview sizes that are not preferred.
786                while (it.hasNext()) {
787                    Size size = it.next();
788                    if (size.width * size.height > product) {
789                        it.remove();
790                    }
791                }
792                Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
793                        (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
794                mDesiredPreviewWidth = optimalSize.width;
795                mDesiredPreviewHeight = optimalSize.height;
796            }
797        } else {
798            mDesiredPreviewWidth = mProfile.videoFrameWidth;
799            mDesiredPreviewHeight = mProfile.videoFrameHeight;
800        }
801        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
802                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
803    }
804
805    private void resizeForPreviewAspectRatio() {
806        mPreviewFrameLayout.setAspectRatio(
807                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
808    }
809
810    @Override
811    public void installIntentFilter() {
812        // install an intent filter to receive SD card related events.
813        IntentFilter intentFilter =
814                new IntentFilter(Intent.ACTION_MEDIA_EJECT);
815        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
816        intentFilter.addDataScheme("file");
817        mReceiver = new MyBroadcastReceiver();
818        mActivity.registerReceiver(mReceiver, intentFilter);
819    }
820
821    @Override
822    public void onResumeBeforeSuper() {
823        mPaused = false;
824    }
825
826    @Override
827    public void onResumeAfterSuper() {
828        if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled)
829            return;
830
831        mZoomValue = 0;
832
833        showVideoSnapshotUI(false);
834
835        // Start orientation listener as soon as possible because it takes
836        // some time to get first orientation.
837        mOrientationListener.enable();
838        if (!mPreviewing) {
839            if (resetEffect()) {
840                mBgLearningMessageFrame.setVisibility(View.GONE);
841                mIndicatorControlContainer.reloadPreferences();
842            }
843            CameraOpenThread cameraOpenThread = new CameraOpenThread();
844            cameraOpenThread.start();
845            try {
846                cameraOpenThread.join();
847                if (mActivity.mOpenCameraFail) {
848                    Util.showErrorAndFinish(mActivity,
849                            R.string.cannot_connect_camera);
850                    return;
851                } else if (mActivity.mCameraDisabled) {
852                    Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
853                    return;
854                }
855            } catch (InterruptedException ex) {
856                // ignore
857            }
858
859            readVideoPreferences();
860            resizeForPreviewAspectRatio();
861            startPreview();
862        }
863
864        // Initializing it here after the preview is started.
865        initializeZoom();
866
867        keepScreenOnAwhile();
868
869        // Initialize location service.
870        boolean recordLocation = RecordLocationPreference.get(mPreferences,
871                mContentResolver);
872        mLocationManager.recordLocation(recordLocation);
873
874        if (!mIsVideoCaptureIntent) {
875            mActivity.getLastThumbnail();
876            if (mModePicker != null) {
877                mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
878            }
879        }
880
881        if (mPreviewing) {
882            mOnResumeTime = SystemClock.uptimeMillis();
883            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
884        }
885        // Dismiss open menu if exists.
886        PopupManager.getInstance(mActivity).notifyShowPopup(null);
887
888        mVideoNamer = new VideoNamer();
889    }
890
891    private void setDisplayOrientation() {
892        mDisplayRotation = Util.getDisplayRotation(mActivity);
893        if (ApiHelper.HAS_SURFACE_TEXTURE) {
894            // The display rotation is handled by gallery.
895            mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
896        } else {
897            // We need to consider display rotation ourselves.
898            mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
899        }
900    }
901
902    private void startPreview() {
903        Log.v(TAG, "startPreview");
904
905        mActivity.mCameraDevice.setErrorCallback(mErrorCallback);
906        if (mPreviewing == true) {
907            stopPreview();
908            if (effectsActive() && mEffectsRecorder != null) {
909                mEffectsRecorder.release();
910                mEffectsRecorder = null;
911            }
912        }
913
914        setDisplayOrientation();
915        mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
916        setCameraParameters();
917
918        try {
919            if (!effectsActive()) {
920                if (ApiHelper.HAS_SURFACE_TEXTURE) {
921                    mActivity.mCameraDevice.setPreviewTextureAsync(
922                            ((CameraScreenNail) mActivity.mCameraScreenNail).getSurfaceTexture());
923                } else {
924                    mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder());
925                }
926                mActivity.mCameraDevice.startPreviewAsync();
927            } else {
928                initializeEffectsPreview();
929                mEffectsRecorder.startPreview();
930            }
931        } catch (Throwable ex) {
932            closeCamera();
933            throw new RuntimeException("startPreview failed", ex);
934        }
935
936        mPreviewing = true;
937    }
938
939    private void stopPreview() {
940        mActivity.mCameraDevice.stopPreview();
941        mPreviewing = false;
942    }
943
944    // Closing the effects out. Will shut down the effects graph.
945    private void closeEffects() {
946        Log.v(TAG, "Closing effects");
947        mEffectType = EffectsRecorder.EFFECT_NONE;
948        if (mEffectsRecorder == null) {
949            Log.d(TAG, "Effects are already closed. Nothing to do");
950            return;
951        }
952        // This call can handle the case where the camera is already released
953        // after the recording has been stopped.
954        mEffectsRecorder.release();
955        mEffectsRecorder = null;
956    }
957
958    // By default, we want to close the effects as well with the camera.
959    private void closeCamera() {
960        closeCamera(true);
961    }
962
963    // In certain cases, when the effects are active, we may want to shutdown
964    // only the camera related parts, and handle closing the effects in the
965    // effectsUpdate callback.
966    // For example, in onPause, we want to make the camera available to
967    // outside world immediately, however, want to wait till the effects
968    // callback to shut down the effects. In such a case, we just disconnect
969    // the effects from the camera by calling disconnectCamera. That way
970    // the effects can handle that when shutting down.
971    //
972    // @param closeEffectsAlso - indicates whether we want to close the
973    // effects also along with the camera.
974    private void closeCamera(boolean closeEffectsAlso) {
975        Log.v(TAG, "closeCamera");
976        if (mActivity.mCameraDevice == null) {
977            Log.d(TAG, "already stopped.");
978            return;
979        }
980
981        if (mEffectsRecorder != null) {
982            // Disconnect the camera from effects so that camera is ready to
983            // be released to the outside world.
984            mEffectsRecorder.disconnectCamera();
985        }
986        if (closeEffectsAlso) closeEffects();
987        mActivity.mCameraDevice.setZoomChangeListener(null);
988        mActivity.mCameraDevice.setErrorCallback(null);
989        CameraHolder.instance().release();
990        mActivity.mCameraDevice = null;
991        mPreviewing = false;
992        mSnapshotInProgress = false;
993    }
994
995    private void releasePreviewResources() {
996        if (ApiHelper.HAS_SURFACE_TEXTURE) {
997            CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
998            if (screenNail.getSurfaceTexture() != null) {
999                screenNail.releaseSurfaceTexture();
1000            }
1001            if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1002                mHandler.removeMessages(HIDE_SURFACE_VIEW);
1003                mPreviewSurfaceView.setVisibility(View.GONE);
1004            }
1005        }
1006    }
1007
1008    @Override
1009    public void onPauseBeforeSuper() {
1010        mPaused = true;
1011
1012        if (mMediaRecorderRecording) {
1013            // Camera will be released in onStopVideoRecording.
1014            onStopVideoRecording();
1015        } else {
1016            closeCamera();
1017            if (!effectsActive()) releaseMediaRecorder();
1018        }
1019        if (effectsActive()) {
1020            // If the effects are active, make sure we tell the graph that the
1021            // surfacetexture is not valid anymore. Disconnect the graph from
1022            // the display. This should be done before releasing the surface
1023            // texture.
1024            mEffectsRecorder.disconnectDisplay();
1025        } else {
1026            // Close the file descriptor and clear the video namer only if the
1027            // effects are not active. If effects are active, we need to wait
1028            // till we get the callback from the Effects that the graph is done
1029            // recording. That also needs a change in the stopVideoRecording()
1030            // call to not call closeCamera if the effects are active, because
1031            // that will close down the effects are well, thus making this if
1032            // condition invalid.
1033            closeVideoFileDescriptor();
1034            clearVideoNamer();
1035        }
1036
1037        releasePreviewResources();
1038
1039        if (mReceiver != null) {
1040            mActivity.unregisterReceiver(mReceiver);
1041            mReceiver = null;
1042        }
1043        resetScreenOn();
1044        if (mIndicatorControlContainer != null) {
1045            mIndicatorControlContainer.dismissSettingPopup();
1046        }
1047
1048        if (mOrientationListener != null) mOrientationListener.disable();
1049        if (mLocationManager != null) mLocationManager.recordLocation(false);
1050
1051        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1052        mHandler.removeMessages(SWITCH_CAMERA);
1053        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
1054        mActivity.mPendingSwitchCameraId = -1;
1055        mSwitchingCamera = false;
1056        // Call onPause after stopping video recording. So the camera can be
1057        // released as soon as possible.
1058    }
1059
1060    @Override
1061    public void onPauseAfterSuper() {
1062    }
1063
1064    @Override
1065    public void onUserInteraction() {
1066        if (!mMediaRecorderRecording) keepScreenOnAwhile();
1067    }
1068
1069    @Override
1070    public boolean onBackPressed() {
1071        if (mPaused) return true;
1072        if (mMediaRecorderRecording) {
1073            onStopVideoRecording();
1074            return true;
1075        } else {
1076            return collapseCameraControls();
1077        }
1078    }
1079
1080    @Override
1081    public boolean onKeyDown(int keyCode, KeyEvent event) {
1082        // Do not handle any key if the activity is paused.
1083        if (mPaused) {
1084            return true;
1085        }
1086
1087        switch (keyCode) {
1088            case KeyEvent.KEYCODE_CAMERA:
1089                if (event.getRepeatCount() == 0) {
1090                    mShutterButton.performClick();
1091                    return true;
1092                }
1093                break;
1094            case KeyEvent.KEYCODE_DPAD_CENTER:
1095                if (event.getRepeatCount() == 0) {
1096                    mShutterButton.performClick();
1097                    return true;
1098                }
1099                break;
1100            case KeyEvent.KEYCODE_MENU:
1101                if (mMediaRecorderRecording) return true;
1102                break;
1103        }
1104        return false;
1105    }
1106
1107    @Override
1108    public boolean onKeyUp(int keyCode, KeyEvent event) {
1109        switch (keyCode) {
1110            case KeyEvent.KEYCODE_CAMERA:
1111                mShutterButton.setPressed(false);
1112                return true;
1113        }
1114        return false;
1115    }
1116
1117    private boolean isVideoCaptureIntent() {
1118        String action = mActivity.getIntent().getAction();
1119        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1120    }
1121
1122    private void doReturnToCaller(boolean valid) {
1123        Intent resultIntent = new Intent();
1124        int resultCode;
1125        if (valid) {
1126            resultCode = Activity.RESULT_OK;
1127            resultIntent.setData(mCurrentVideoUri);
1128        } else {
1129            resultCode = Activity.RESULT_CANCELED;
1130        }
1131        mActivity.setResultEx(resultCode, resultIntent);
1132        mActivity.finish();
1133    }
1134
1135    private void cleanupEmptyFile() {
1136        if (mVideoFilename != null) {
1137            File f = new File(mVideoFilename);
1138            if (f.length() == 0 && f.delete()) {
1139                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1140                mVideoFilename = null;
1141            }
1142        }
1143    }
1144
1145    private void setupMediaRecorderPreviewDisplay() {
1146        // Nothing to do here if using SurfaceTexture.
1147        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
1148            mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface());
1149        } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1150            // We stop the preview here before unlocking the device because we
1151            // need to change the SurfaceTexture to SurfaceView for preview.
1152            stopPreview();
1153            mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder());
1154            // The orientation for SurfaceTexture is different from that for
1155            // SurfaceView. For SurfaceTexture we don't need to consider the
1156            // display rotation. Just consider the sensor's orientation and we
1157            // will set the orientation correctly when showing the texture.
1158            // Gallery will handle the orientation for the preview. For
1159            // SurfaceView we will have to take everything into account so the
1160            // display rotation is considered.
1161            mActivity.mCameraDevice.setDisplayOrientation(
1162                    Util.getDisplayOrientation(mDisplayRotation, mCameraId));
1163            mActivity.mCameraDevice.startPreviewAsync();
1164            mPreviewing = true;
1165            mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface());
1166        }
1167    }
1168
1169    // Prepares media recorder.
1170    private void initializeRecorder() {
1171        Log.v(TAG, "initializeRecorder");
1172        // If the mCameraDevice is null, then this activity is going to finish
1173        if (mActivity.mCameraDevice == null) return;
1174
1175        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING && ApiHelper.HAS_SURFACE_TEXTURE) {
1176            // Set the SurfaceView to visible so the surface gets created.
1177            // surfaceCreated() is called immediately when the visibility is
1178            // changed to visible. Thus, mSurfaceViewReady should become true
1179            // right after calling setVisibility().
1180            mPreviewSurfaceView.setVisibility(View.VISIBLE);
1181            if (!mSurfaceViewReady) return;
1182        }
1183
1184        Intent intent = mActivity.getIntent();
1185        Bundle myExtras = intent.getExtras();
1186
1187        long requestedSizeLimit = 0;
1188        closeVideoFileDescriptor();
1189        if (mIsVideoCaptureIntent && myExtras != null) {
1190            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1191            if (saveUri != null) {
1192                try {
1193                    mVideoFileDescriptor =
1194                            mContentResolver.openFileDescriptor(saveUri, "rw");
1195                    mCurrentVideoUri = saveUri;
1196                } catch (java.io.FileNotFoundException ex) {
1197                    // invalid uri
1198                    Log.e(TAG, ex.toString());
1199                }
1200            }
1201            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1202        }
1203        mMediaRecorder = new MediaRecorder();
1204
1205        setupMediaRecorderPreviewDisplay();
1206        // Unlock the camera object before passing it to media recorder.
1207        mActivity.mCameraDevice.unlock();
1208        mMediaRecorder.setCamera(mActivity.mCameraDevice.getCamera());
1209        if (!mCaptureTimeLapse) {
1210            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1211        }
1212        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1213        mMediaRecorder.setProfile(mProfile);
1214        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1215        if (mCaptureTimeLapse) {
1216            double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1217            setCaptureRate(mMediaRecorder, fps);
1218        }
1219
1220        setRecordLocation();
1221
1222        // Set output file.
1223        // Try Uri in the intent first. If it doesn't exist, use our own
1224        // instead.
1225        if (mVideoFileDescriptor != null) {
1226            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1227        } else {
1228            generateVideoFilename(mProfile.fileFormat);
1229            mMediaRecorder.setOutputFile(mVideoFilename);
1230        }
1231
1232        // Set maximum file size.
1233        long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1234        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1235            maxFileSize = requestedSizeLimit;
1236        }
1237
1238        try {
1239            mMediaRecorder.setMaxFileSize(maxFileSize);
1240        } catch (RuntimeException exception) {
1241            // We are going to ignore failure of setMaxFileSize here, as
1242            // a) The composer selected may simply not support it, or
1243            // b) The underlying media framework may not handle 64-bit range
1244            // on the size restriction.
1245        }
1246
1247        // See android.hardware.Camera.Parameters.setRotation for
1248        // documentation.
1249        // Note that mOrientation here is the device orientation, which is the opposite of
1250        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1251        // which is the orientation the graphics need to rotate in order to render correctly.
1252        int rotation = 0;
1253        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1254            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1255            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1256                rotation = (info.orientation - mOrientation + 360) % 360;
1257            } else {  // back-facing camera
1258                rotation = (info.orientation + mOrientation) % 360;
1259            }
1260        }
1261        mMediaRecorder.setOrientationHint(rotation);
1262        mOrientationCompensationAtRecordStart = mOrientationCompensation;
1263
1264        try {
1265            mMediaRecorder.prepare();
1266        } catch (IOException e) {
1267            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1268            releaseMediaRecorder();
1269            throw new RuntimeException(e);
1270        }
1271
1272        mMediaRecorder.setOnErrorListener(this);
1273        mMediaRecorder.setOnInfoListener(this);
1274    }
1275
1276    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
1277    private static void setCaptureRate(MediaRecorder recorder, double fps) {
1278        recorder.setCaptureRate(fps);
1279    }
1280
1281    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
1282    private void setRecordLocation() {
1283        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1284            Location loc = mLocationManager.getCurrentLocation();
1285            if (loc != null) {
1286                mMediaRecorder.setLocation((float) loc.getLatitude(),
1287                        (float) loc.getLongitude());
1288            }
1289        }
1290    }
1291
1292    private void initializeEffectsPreview() {
1293        Log.v(TAG, "initializeEffectsPreview");
1294        // If the mCameraDevice is null, then this activity is going to finish
1295        if (mActivity.mCameraDevice == null) return;
1296
1297        boolean inLandscape = (mActivity.getResources().getConfiguration().orientation
1298                == Configuration.ORIENTATION_LANDSCAPE);
1299
1300        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1301
1302        mEffectsDisplayResult = false;
1303        mEffectsRecorder = new EffectsRecorder(mActivity);
1304
1305        // TODO: Confirm none of the following need to go to initializeEffectsRecording()
1306        // and none of these change even when the preview is not refreshed.
1307        mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
1308        mEffectsRecorder.setCamera(mActivity.mCameraDevice);
1309        mEffectsRecorder.setCameraFacing(info.facing);
1310        mEffectsRecorder.setProfile(mProfile);
1311        mEffectsRecorder.setEffectsListener(this);
1312        mEffectsRecorder.setOnInfoListener(this);
1313        mEffectsRecorder.setOnErrorListener(this);
1314
1315        // The input of effects recorder is affected by
1316        // android.hardware.Camera.setDisplayOrientation. Its value only
1317        // compensates the camera orientation (no Display.getRotation). So the
1318        // orientation hint here should only consider sensor orientation.
1319        int orientation = 0;
1320        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1321            orientation = mOrientation;
1322        }
1323        mEffectsRecorder.setOrientationHint(orientation);
1324
1325        mOrientationCompensationAtRecordStart = mOrientationCompensation;
1326
1327        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
1328        mEffectsRecorder.setPreviewSurfaceTexture(screenNail.getSurfaceTexture(),
1329                screenNail.getWidth(), screenNail.getHeight());
1330
1331        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
1332                ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
1333            mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
1334        } else {
1335            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
1336        }
1337    }
1338
1339    private void initializeEffectsRecording() {
1340        Log.v(TAG, "initializeEffectsRecording");
1341
1342        Intent intent = mActivity.getIntent();
1343        Bundle myExtras = intent.getExtras();
1344
1345        long requestedSizeLimit = 0;
1346        closeVideoFileDescriptor();
1347        if (mIsVideoCaptureIntent && myExtras != null) {
1348            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1349            if (saveUri != null) {
1350                try {
1351                    mVideoFileDescriptor =
1352                            mContentResolver.openFileDescriptor(saveUri, "rw");
1353                    mCurrentVideoUri = saveUri;
1354                } catch (java.io.FileNotFoundException ex) {
1355                    // invalid uri
1356                    Log.e(TAG, ex.toString());
1357                }
1358            }
1359            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1360        }
1361
1362        mEffectsRecorder.setProfile(mProfile);
1363        // important to set the capture rate to zero if not timelapsed, since the
1364        // effectsrecorder object does not get created again for each recording
1365        // session
1366        if (mCaptureTimeLapse) {
1367            mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
1368        } else {
1369            mEffectsRecorder.setCaptureRate(0);
1370        }
1371
1372        // Set output file
1373        if (mVideoFileDescriptor != null) {
1374            mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1375        } else {
1376            generateVideoFilename(mProfile.fileFormat);
1377            mEffectsRecorder.setOutputFile(mVideoFilename);
1378        }
1379
1380        // Set maximum file size.
1381        long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
1382        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1383            maxFileSize = requestedSizeLimit;
1384        }
1385        mEffectsRecorder.setMaxFileSize(maxFileSize);
1386        mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
1387    }
1388
1389
1390    private void releaseMediaRecorder() {
1391        Log.v(TAG, "Releasing media recorder.");
1392        if (mMediaRecorder != null) {
1393            cleanupEmptyFile();
1394            mMediaRecorder.reset();
1395            mMediaRecorder.release();
1396            mMediaRecorder = null;
1397        }
1398        mVideoFilename = null;
1399    }
1400
1401    private void releaseEffectsRecorder() {
1402        Log.v(TAG, "Releasing effects recorder.");
1403        if (mEffectsRecorder != null) {
1404            cleanupEmptyFile();
1405            mEffectsRecorder.release();
1406            mEffectsRecorder = null;
1407        }
1408        mEffectType = EffectsRecorder.EFFECT_NONE;
1409        mVideoFilename = null;
1410    }
1411
1412    private void generateVideoFilename(int outputFileFormat) {
1413        long dateTaken = System.currentTimeMillis();
1414        String title = createName(dateTaken);
1415        // Used when emailing.
1416        String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1417        String mime = convertOutputFormatToMimeType(outputFileFormat);
1418        String path = Storage.DIRECTORY + '/' + filename;
1419        String tmpPath = path + ".tmp";
1420        mCurrentVideoValues = new ContentValues(7);
1421        mCurrentVideoValues.put(Video.Media.TITLE, title);
1422        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1423        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1424        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1425        mCurrentVideoValues.put(Video.Media.DATA, path);
1426        mCurrentVideoValues.put(Video.Media.RESOLUTION,
1427                Integer.toString(mProfile.videoFrameWidth) + "x" +
1428                Integer.toString(mProfile.videoFrameHeight));
1429        Location loc = mLocationManager.getCurrentLocation();
1430        if (loc != null) {
1431            mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1432            mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1433        }
1434        mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues);
1435        mVideoFilename = tmpPath;
1436        Log.v(TAG, "New video filename: " + mVideoFilename);
1437    }
1438
1439    private boolean addVideoToMediaStore() {
1440        boolean fail = false;
1441        if (mVideoFileDescriptor == null) {
1442            mCurrentVideoValues.put(Video.Media.SIZE,
1443                    new File(mCurrentVideoFilename).length());
1444            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1445            if (duration > 0) {
1446                if (mCaptureTimeLapse) {
1447                    duration = getTimeLapseVideoLength(duration);
1448                }
1449                mCurrentVideoValues.put(Video.Media.DURATION, duration);
1450            } else {
1451                Log.w(TAG, "Video duration <= 0 : " + duration);
1452            }
1453            try {
1454                mCurrentVideoUri = mVideoNamer.getUri();
1455                mActivity.addSecureAlbumItemIfNeeded(true, mCurrentVideoUri);
1456
1457                // Rename the video file to the final name. This avoids other
1458                // apps reading incomplete data.  We need to do it after the
1459                // above mVideoNamer.getUri() call, so we are certain that the
1460                // previous insert to MediaProvider is completed.
1461                String finalName = mCurrentVideoValues.getAsString(
1462                        Video.Media.DATA);
1463                if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) {
1464                    mCurrentVideoFilename = finalName;
1465                }
1466
1467                mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues
1468                        , null, null);
1469                mActivity.sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO,
1470                        mCurrentVideoUri));
1471            } catch (Exception e) {
1472                // We failed to insert into the database. This can happen if
1473                // the SD card is unmounted.
1474                Log.e(TAG, "failed to add video to media store", e);
1475                mCurrentVideoUri = null;
1476                mCurrentVideoFilename = null;
1477                fail = true;
1478            } finally {
1479                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
1480            }
1481        }
1482        mCurrentVideoValues = null;
1483        return fail;
1484    }
1485
1486    private void deleteCurrentVideo() {
1487        // Remove the video and the uri if the uri is not passed in by intent.
1488        if (mCurrentVideoFilename != null) {
1489            deleteVideoFile(mCurrentVideoFilename);
1490            mCurrentVideoFilename = null;
1491            if (mCurrentVideoUri != null) {
1492                mContentResolver.delete(mCurrentVideoUri, null, null);
1493                mCurrentVideoUri = null;
1494            }
1495        }
1496        mActivity.updateStorageSpaceAndHint();
1497    }
1498
1499    private void deleteVideoFile(String fileName) {
1500        Log.v(TAG, "Deleting video " + fileName);
1501        File f = new File(fileName);
1502        if (!f.delete()) {
1503            Log.v(TAG, "Could not delete " + fileName);
1504        }
1505    }
1506
1507    private PreferenceGroup filterPreferenceScreenByIntent(
1508            PreferenceGroup screen) {
1509        Intent intent = mActivity.getIntent();
1510        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1511            CameraSettings.removePreferenceFromScreen(screen,
1512                    CameraSettings.KEY_VIDEO_QUALITY);
1513        }
1514
1515        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1516            CameraSettings.removePreferenceFromScreen(screen,
1517                    CameraSettings.KEY_VIDEO_QUALITY);
1518        }
1519        return screen;
1520    }
1521
1522    // from MediaRecorder.OnErrorListener
1523    @Override
1524    public void onError(MediaRecorder mr, int what, int extra) {
1525        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1526        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1527            // We may have run out of space on the sdcard.
1528            stopVideoRecording();
1529            mActivity.updateStorageSpaceAndHint();
1530        }
1531    }
1532
1533    // from MediaRecorder.OnInfoListener
1534    @Override
1535    public void onInfo(MediaRecorder mr, int what, int extra) {
1536        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1537            if (mMediaRecorderRecording) onStopVideoRecording();
1538        } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1539            if (mMediaRecorderRecording) onStopVideoRecording();
1540
1541            // Show the toast.
1542            Toast.makeText(mActivity, R.string.video_reach_size_limit,
1543                    Toast.LENGTH_LONG).show();
1544        }
1545    }
1546
1547    /*
1548     * Make sure we're not recording music playing in the background, ask the
1549     * MediaPlaybackService to pause playback.
1550     */
1551    private void pauseAudioPlayback() {
1552        // Shamelessly copied from MediaPlaybackService.java, which
1553        // should be public, but isn't.
1554        Intent i = new Intent("com.android.music.musicservicecommand");
1555        i.putExtra("command", "pause");
1556
1557        mActivity.sendBroadcast(i);
1558    }
1559
1560    // For testing.
1561    public boolean isRecording() {
1562        return mMediaRecorderRecording;
1563    }
1564
1565    private void startVideoRecording() {
1566        Log.v(TAG, "startVideoRecording");
1567        mActivity.setSwipingEnabled(false);
1568        mActivity.hideSwitcher();
1569
1570        mActivity.updateStorageSpaceAndHint();
1571        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
1572            Log.v(TAG, "Storage issue, ignore the start request");
1573            return;
1574        }
1575
1576        mCurrentVideoUri = null;
1577        if (effectsActive()) {
1578            initializeEffectsRecording();
1579            if (mEffectsRecorder == null) {
1580                Log.e(TAG, "Fail to initialize effect recorder");
1581                return;
1582            }
1583        } else {
1584            initializeRecorder();
1585            if (mMediaRecorder == null) {
1586                Log.e(TAG, "Fail to initialize media recorder");
1587                return;
1588            }
1589        }
1590
1591        pauseAudioPlayback();
1592
1593        if (effectsActive()) {
1594            try {
1595                mEffectsRecorder.startRecording();
1596            } catch (RuntimeException e) {
1597                Log.e(TAG, "Could not start effects recorder. ", e);
1598                releaseEffectsRecorder();
1599                return;
1600            }
1601        } else {
1602            try {
1603                mMediaRecorder.start(); // Recording is now started
1604            } catch (RuntimeException e) {
1605                Log.e(TAG, "Could not start media recorder. ", e);
1606                releaseMediaRecorder();
1607                // If start fails, frameworks will not lock the camera for us.
1608                mActivity.mCameraDevice.lock();
1609                return;
1610            }
1611        }
1612
1613        // The parameters may have been changed by MediaRecorder upon starting
1614        // recording. We need to alter the parameters if we support camcorder
1615        // zoom. To reduce latency when setting the parameters during zoom, we
1616        // update mParameters here once.
1617        if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
1618            mParameters = mActivity.mCameraDevice.getParameters();
1619        }
1620
1621        enableCameraControls(false);
1622
1623        mMediaRecorderRecording = true;
1624        mRecordingStartTime = SystemClock.uptimeMillis();
1625        showRecordingUI(true);
1626
1627        updateRecordingTime();
1628        keepScreenOn();
1629    }
1630
1631    private void showRecordingUI(boolean recording) {
1632        if (recording) {
1633            mIndicatorControlContainer.dismissSecondLevelIndicator();
1634            if (mActivity.mThumbnailView != null) mActivity.mThumbnailView.setEnabled(false);
1635            mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
1636            mRecordingTimeView.setText("");
1637            mRecordingTimeView.setVisibility(View.VISIBLE);
1638            if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
1639            if (mCaptureTimeLapse) {
1640                mIndicatorControlContainer.startTimeLapseAnimation(
1641                        mTimeBetweenTimeLapseFrameCaptureMs,
1642                        mRecordingStartTime);
1643            }
1644            // The camera is not allowed to be accessed in older api levels during
1645            // recording. It is therefore necessary to hide the zoom UI on older
1646            // platforms.
1647            // See the documentation of android.media.MediaRecorder.start() for
1648            // further explanation.
1649            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING
1650                    && mParameters.isZoomSupported()) {
1651                mZoomControl.setVisibility(View.GONE);
1652            }
1653        } else {
1654            if (mActivity.mThumbnailView != null) mActivity.mThumbnailView.setEnabled(true);
1655            mShutterButton.setImageResource(R.drawable.btn_shutter_video);
1656            mRecordingTimeView.setVisibility(View.GONE);
1657            if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
1658            if (mCaptureTimeLapse) {
1659                mIndicatorControlContainer.stopTimeLapseAnimation();
1660            }
1661            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING
1662                    && mParameters.isZoomSupported()) {
1663                mZoomControl.setVisibility(View.VISIBLE);
1664            }
1665        }
1666    }
1667
1668    private void getThumbnail() {
1669        if (mCurrentVideoUri != null) {
1670            Bitmap videoFrame = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
1671                    Math.max(mActivity.mThumbnailViewWidth, MIN_THUMB_SIZE));
1672            if (videoFrame != null) {
1673                mActivity.mThumbnail = Thumbnail.createThumbnail(mCurrentVideoUri, videoFrame, 0);
1674                if (mActivity.mThumbnailView != null) {
1675                    mActivity.mThumbnailView.setBitmap(mActivity.mThumbnail.getBitmap());
1676                }
1677            }
1678        }
1679    }
1680
1681    private void showAlert() {
1682        Bitmap bitmap = null;
1683        if (mVideoFileDescriptor != null) {
1684            bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
1685                    mPreviewFrameLayout.getWidth());
1686        } else if (mCurrentVideoFilename != null) {
1687            bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
1688                    mPreviewFrameLayout.getWidth());
1689        }
1690        if (bitmap != null) {
1691            // MetadataRetriever already rotates the thumbnail. We should rotate
1692            // it to match the UI orientation (and mirror if it is front-facing camera).
1693            CameraInfo[] info = CameraHolder.instance().getCameraInfo();
1694            boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
1695            bitmap = Util.rotateAndMirror(bitmap, -mOrientationCompensationAtRecordStart,
1696                    mirror);
1697            mReviewImage.setImageBitmap(bitmap);
1698            mReviewImage.setVisibility(View.VISIBLE);
1699        }
1700
1701        Util.fadeOut(mShutterButton);
1702        Util.fadeOut(mIndicatorControlContainer);
1703
1704        Util.fadeIn(mReviewRetakeButton);
1705        Util.fadeIn((View) mReviewDoneButton);
1706        Util.fadeIn(mReviewPlayButton);
1707
1708        showTimeLapseUI(false);
1709    }
1710
1711    private void hideAlert() {
1712        mReviewImage.setVisibility(View.GONE);
1713        mShutterButton.setEnabled(true);
1714        enableCameraControls(true);
1715
1716        Util.fadeOut((View) mReviewDoneButton);
1717        Util.fadeOut(mReviewRetakeButton);
1718        Util.fadeOut(mReviewPlayButton);
1719
1720        Util.fadeIn(mShutterButton);
1721        Util.fadeIn(mIndicatorControlContainer);
1722
1723        if (mCaptureTimeLapse) {
1724            showTimeLapseUI(true);
1725        }
1726    }
1727
1728    private boolean stopVideoRecording() {
1729        Log.v(TAG, "stopVideoRecording");
1730        mActivity.setSwipingEnabled(true);
1731        mActivity.showSwitcher();
1732
1733        boolean fail = false;
1734        if (mMediaRecorderRecording) {
1735            boolean shouldAddToMediaStoreNow = false;
1736
1737            try {
1738                if (effectsActive()) {
1739                    // This is asynchronous, so we can't add to media store now because thumbnail
1740                    // may not be ready. In such case addVideoToMediaStore is called later
1741                    // through a callback from the MediaEncoderFilter to EffectsRecorder,
1742                    // and then to the VideoCamera.
1743                    mEffectsRecorder.stopRecording();
1744                } else {
1745                    mMediaRecorder.setOnErrorListener(null);
1746                    mMediaRecorder.setOnInfoListener(null);
1747                    mMediaRecorder.stop();
1748                    shouldAddToMediaStoreNow = true;
1749                }
1750                mCurrentVideoFilename = mVideoFilename;
1751                Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1752                        + mCurrentVideoFilename);
1753            } catch (RuntimeException e) {
1754                Log.e(TAG, "stop fail",  e);
1755                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
1756                fail = true;
1757            }
1758            mMediaRecorderRecording = false;
1759
1760            // If the activity is paused, this means activity is interrupted
1761            // during recording. Release the camera as soon as possible because
1762            // face unlock or other applications may need to use the camera.
1763            // However, if the effects are active, then we can only release the
1764            // camera and cannot release the effects recorder since that will
1765            // stop the graph. It is possible to separate out the Camera release
1766            // part and the effects release part. However, the effects recorder
1767            // does hold on to the camera, hence, it needs to be "disconnected"
1768            // from the camera in the closeCamera call.
1769            if (mPaused) {
1770                // Closing only the camera part if effects active. Effects will
1771                // be closed in the callback from effects.
1772                boolean closeEffects = !effectsActive();
1773                closeCamera(closeEffects);
1774            }
1775
1776            showRecordingUI(false);
1777            if (!mIsVideoCaptureIntent) {
1778                enableCameraControls(true);
1779            }
1780            // The orientation was fixed during video recording. Now make it
1781            // reflect the device orientation as video recording is stopped.
1782            setOrientationIndicator(mOrientationCompensation, true);
1783            keepScreenOnAwhile();
1784            if (shouldAddToMediaStoreNow) {
1785                if (addVideoToMediaStore()) fail = true;
1786            }
1787        }
1788        // always release media recorder if no effects running
1789        if (!effectsActive()) {
1790            releaseMediaRecorder();
1791            if (!mPaused) {
1792                mActivity.mCameraDevice.lock();
1793                if (ApiHelper.HAS_SURFACE_TEXTURE &&
1794                    !ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1795                    stopPreview();
1796                    // Switch back to use SurfaceTexture for preview.
1797                    ((CameraScreenNail) mActivity.mCameraScreenNail).setOneTimeOnFrameDrawnListener(
1798                            mFrameDrawnListener);
1799                    startPreview();
1800                }
1801            }
1802        }
1803        // Update the parameters here because the parameters might have been altered
1804        // by MediaRecorder.
1805        if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters();
1806        return fail;
1807    }
1808
1809    private void resetScreenOn() {
1810        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1811        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1812    }
1813
1814    private void keepScreenOnAwhile() {
1815        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1816        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1817        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1818    }
1819
1820    private void keepScreenOn() {
1821        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1822        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1823    }
1824
1825    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1826        long seconds = milliSeconds / 1000; // round down to compute seconds
1827        long minutes = seconds / 60;
1828        long hours = minutes / 60;
1829        long remainderMinutes = minutes - (hours * 60);
1830        long remainderSeconds = seconds - (minutes * 60);
1831
1832        StringBuilder timeStringBuilder = new StringBuilder();
1833
1834        // Hours
1835        if (hours > 0) {
1836            if (hours < 10) {
1837                timeStringBuilder.append('0');
1838            }
1839            timeStringBuilder.append(hours);
1840
1841            timeStringBuilder.append(':');
1842        }
1843
1844        // Minutes
1845        if (remainderMinutes < 10) {
1846            timeStringBuilder.append('0');
1847        }
1848        timeStringBuilder.append(remainderMinutes);
1849        timeStringBuilder.append(':');
1850
1851        // Seconds
1852        if (remainderSeconds < 10) {
1853            timeStringBuilder.append('0');
1854        }
1855        timeStringBuilder.append(remainderSeconds);
1856
1857        // Centi seconds
1858        if (displayCentiSeconds) {
1859            timeStringBuilder.append('.');
1860            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1861            if (remainderCentiSeconds < 10) {
1862                timeStringBuilder.append('0');
1863            }
1864            timeStringBuilder.append(remainderCentiSeconds);
1865        }
1866
1867        return timeStringBuilder.toString();
1868    }
1869
1870    private long getTimeLapseVideoLength(long deltaMs) {
1871        // For better approximation calculate fractional number of frames captured.
1872        // This will update the video time at a higher resolution.
1873        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1874        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1875    }
1876
1877    private void updateRecordingTime() {
1878        if (!mMediaRecorderRecording) {
1879            return;
1880        }
1881        long now = SystemClock.uptimeMillis();
1882        long delta = now - mRecordingStartTime;
1883
1884        // Starting a minute before reaching the max duration
1885        // limit, we'll countdown the remaining time instead.
1886        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1887                && delta >= mMaxVideoDurationInMs - 60000);
1888
1889        long deltaAdjusted = delta;
1890        if (countdownRemainingTime) {
1891            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1892        }
1893        String text;
1894
1895        long targetNextUpdateDelay;
1896        if (!mCaptureTimeLapse) {
1897            text = millisecondToTimeString(deltaAdjusted, false);
1898            targetNextUpdateDelay = 1000;
1899        } else {
1900            // The length of time lapse video is different from the length
1901            // of the actual wall clock time elapsed. Display the video length
1902            // only in format hh:mm:ss.dd, where dd are the centi seconds.
1903            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1904            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1905        }
1906
1907        mRecordingTimeView.setText(text);
1908
1909        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1910            // Avoid setting the color on every update, do it only
1911            // when it needs changing.
1912            mRecordingTimeCountsDown = countdownRemainingTime;
1913
1914            int color = mActivity.getResources().getColor(countdownRemainingTime
1915                    ? R.color.recording_time_remaining_text
1916                    : R.color.recording_time_elapsed_text);
1917
1918            mRecordingTimeView.setTextColor(color);
1919        }
1920
1921        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1922        mHandler.sendEmptyMessageDelayed(
1923                UPDATE_RECORD_TIME, actualNextUpdateDelay);
1924    }
1925
1926    private static boolean isSupported(String value, List<String> supported) {
1927        return supported == null ? false : supported.indexOf(value) >= 0;
1928    }
1929
1930    @SuppressWarnings("deprecation")
1931    private void setCameraParameters() {
1932        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1933        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1934
1935        // Set flash mode.
1936        String flashMode;
1937        if (mActivity.mShowCameraAppView) {
1938            flashMode = mPreferences.getString(
1939                    CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
1940                    mActivity.getString(R.string.pref_camera_video_flashmode_default));
1941        } else {
1942            flashMode = Parameters.FLASH_MODE_OFF;
1943        }
1944        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1945        if (isSupported(flashMode, supportedFlash)) {
1946            mParameters.setFlashMode(flashMode);
1947        } else {
1948            flashMode = mParameters.getFlashMode();
1949            if (flashMode == null) {
1950                flashMode = mActivity.getString(
1951                        R.string.pref_camera_flashmode_no_flash);
1952            }
1953        }
1954
1955        // Set white balance parameter.
1956        String whiteBalance = mPreferences.getString(
1957                CameraSettings.KEY_WHITE_BALANCE,
1958                mActivity.getString(R.string.pref_camera_whitebalance_default));
1959        if (isSupported(whiteBalance,
1960                mParameters.getSupportedWhiteBalance())) {
1961            mParameters.setWhiteBalance(whiteBalance);
1962        } else {
1963            whiteBalance = mParameters.getWhiteBalance();
1964            if (whiteBalance == null) {
1965                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1966            }
1967        }
1968
1969        // Set zoom.
1970        if (mParameters.isZoomSupported()) {
1971            mParameters.setZoom(mZoomValue);
1972        }
1973
1974        // Set continuous autofocus.
1975        List<String> supportedFocus = mParameters.getSupportedFocusModes();
1976        if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1977            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1978        }
1979
1980        mParameters.set(Util.RECORDING_HINT, Util.TRUE);
1981
1982        // Enable video stabilization. Convenience methods not available in API
1983        // level <= 14
1984        String vstabSupported = mParameters.get("video-stabilization-supported");
1985        if ("true".equals(vstabSupported)) {
1986            mParameters.set("video-stabilization", "true");
1987        }
1988
1989        // Set picture size.
1990        // The logic here is different from the logic in still-mode camera.
1991        // There we determine the preview size based on the picture size, but
1992        // here we determine the picture size based on the preview size.
1993        List<Size> supported = mParameters.getSupportedPictureSizes();
1994        Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
1995                (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1996        Size original = mParameters.getPictureSize();
1997        if (!original.equals(optimalSize)) {
1998            mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1999        }
2000        Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
2001                optimalSize.height);
2002
2003        // Set JPEG quality.
2004        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2005                CameraProfile.QUALITY_HIGH);
2006        mParameters.setJpegQuality(jpegQuality);
2007
2008        mActivity.mCameraDevice.setParameters(mParameters);
2009        // Keep preview size up to date.
2010        mParameters = mActivity.mCameraDevice.getParameters();
2011
2012        updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
2013    }
2014
2015    private void updateCameraScreenNailSize(int width, int height) {
2016        if (!ApiHelper.HAS_SURFACE_TEXTURE) return;
2017
2018        if (mCameraDisplayOrientation % 180 != 0) {
2019            int tmp = width;
2020            width = height;
2021            height = tmp;
2022        }
2023
2024        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
2025        int oldWidth = screenNail.getWidth();
2026        int oldHeight = screenNail.getHeight();
2027
2028        if (oldWidth != width || oldHeight != height) {
2029            screenNail.setSize(width, height);
2030            mActivity.notifyScreenNailChanged();
2031        }
2032
2033        if (screenNail.getSurfaceTexture() == null) {
2034            screenNail.acquireSurfaceTexture();
2035        }
2036    }
2037
2038    private void switchToOtherMode(int mode) {
2039        if (mActivity.isFinishing()) return;
2040        if (mActivity.mThumbnail != null) ThumbnailHolder.keep(mActivity.mThumbnail);
2041        MenuHelper.gotoMode(mode, mActivity, false);
2042        mActivity.finish();
2043    }
2044
2045    @Override
2046    public void onModeChanged(int mode) {
2047        if (mode != ModePicker.MODE_VIDEO) switchToOtherMode(mode);
2048    }
2049
2050    @Override
2051    public void onActivityResult(int requestCode, int resultCode, Intent data) {
2052        switch (requestCode) {
2053            case REQUEST_EFFECT_BACKDROPPER:
2054                if (resultCode == Activity.RESULT_OK) {
2055                    // onActivityResult() runs before onResume(), so this parameter will be
2056                    // seen by startPreview from onResume()
2057                    mEffectUriFromGallery = data.getData().toString();
2058                    Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
2059                    mResetEffect = false;
2060                } else {
2061                    mEffectUriFromGallery = null;
2062                    Log.w(TAG, "No URI from gallery");
2063                    mResetEffect = true;
2064                }
2065                break;
2066        }
2067    }
2068
2069    @Override
2070    public void onEffectsUpdate(int effectId, int effectMsg) {
2071        Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
2072        if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
2073            // Effects have shut down. Hide learning message if any,
2074            // and restart regular preview.
2075            mBgLearningMessageFrame.setVisibility(View.GONE);
2076            checkQualityAndStartPreview();
2077        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
2078            // This follows the codepath from onStopVideoRecording.
2079            if (mEffectsDisplayResult && !addVideoToMediaStore()) {
2080                if (mIsVideoCaptureIntent) {
2081                    if (mQuickCapture) {
2082                        doReturnToCaller(true);
2083                    } else {
2084                        showAlert();
2085                    }
2086                } else {
2087                    getThumbnail();
2088                }
2089            }
2090            mEffectsDisplayResult = false;
2091            // In onPause, these were not called if the effects were active. We
2092            // had to wait till the effects recording is complete to do this.
2093            if (mPaused) {
2094                closeVideoFileDescriptor();
2095                clearVideoNamer();
2096            }
2097        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
2098            // Enable the shutter button once the preview is complete.
2099            mShutterButton.setEnabled(true);
2100        } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) {
2101            switch (effectMsg) {
2102                case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING:
2103                    mBgLearningMessageFrame.setVisibility(View.VISIBLE);
2104                    break;
2105                case EffectsRecorder.EFFECT_MSG_DONE_LEARNING:
2106                case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT:
2107                    mBgLearningMessageFrame.setVisibility(View.GONE);
2108                    break;
2109            }
2110        }
2111        // In onPause, this was not called if the effects were active. We had to
2112        // wait till the effects completed to do this.
2113        if (mPaused) {
2114            Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
2115            closeEffects();
2116        }
2117    }
2118
2119    public void onCancelBgTraining(View v) {
2120        // Remove training message
2121        mBgLearningMessageFrame.setVisibility(View.GONE);
2122        // Write default effect out to shared prefs
2123        writeDefaultEffectToPrefs();
2124        // Tell the indicator controller to redraw based on new shared pref values
2125        mIndicatorControlContainer.reloadPreferences();
2126        // Tell VideoCamer to re-init based on new shared pref values.
2127        onSharedPreferenceChanged();
2128    }
2129
2130    @Override
2131    public synchronized void onEffectsError(Exception exception, String fileName) {
2132        // TODO: Eventually we may want to show the user an error dialog, and then restart the
2133        // camera and encoder gracefully. For now, we just delete the file and bail out.
2134        if (fileName != null && new File(fileName).exists()) {
2135            deleteVideoFile(fileName);
2136        }
2137        try {
2138            if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
2139                    .isInstance(exception)) {
2140                Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
2141                return;
2142            }
2143        } catch (ClassNotFoundException ex) {
2144            Log.w(TAG, ex);
2145        }
2146        throw new RuntimeException("Error during recording!", exception);
2147    }
2148
2149    private void initializeControlByIntent() {
2150        if (mIsVideoCaptureIntent) {
2151            // Cannot use RotateImageView for "done" and "cancel" button because
2152            // the tablet layout uses RotateLayout, which cannot be cast to
2153            // RotateImageView.
2154            mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
2155            mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
2156            mReviewPlayButton = (RotateImageView) mRootView.findViewById(R.id.btn_play);
2157            mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake);
2158            mRootView.findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
2159
2160            // Not grayed out upon disabled, to make the follow-up fade-out
2161            // effect look smooth. Note that the review done button in tablet
2162            // layout is not a TwoStateImageView.
2163            if (mReviewDoneButton instanceof TwoStateImageView) {
2164                ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
2165            }
2166        } else {
2167            mActivity.mThumbnailView = (RotateImageView) mRootView.findViewById(R.id.thumbnail);
2168            if (mActivity.mThumbnailView != null) {
2169                mActivity.mThumbnailView.enableFilter(false);
2170                mActivity.mThumbnailView.setVisibility(View.VISIBLE);
2171                mActivity.mThumbnailViewWidth = mActivity.mThumbnailView.getLayoutParams().width;
2172                mModePicker = (ModePicker) mRootView.findViewById(R.id.mode_picker);
2173                mModePicker.setVisibility(View.VISIBLE);
2174                mModePicker.setOnModeChangeListener(this);
2175            }
2176        }
2177    }
2178
2179    private void initializeMiscControls() {
2180        mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
2181        mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
2182        mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
2183
2184        mShutterButton = mActivity.getShutterButton();
2185        mShutterButton.setImageResource(R.drawable.btn_shutter_video);
2186        mShutterButton.setOnShutterButtonListener(this);
2187        mShutterButton.requestFocus();
2188
2189        // Disable the shutter button if effects are ON since it might take
2190        // a little more time for the effects preview to be ready. We do not
2191        // want to allow recording before that happens. The shutter button
2192        // will be enabled when we get the message from effectsrecorder that
2193        // the preview is running. This becomes critical when the camera is
2194        // swapped.
2195        if (effectsActive()) {
2196            mShutterButton.setEnabled(false);
2197        }
2198
2199        mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time);
2200        mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect);
2201        mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label);
2202        // The R.id.labels can only be found in phone layout.
2203        // That is, mLabelsLinearLayout should be null in tablet layout.
2204        mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels);
2205
2206        mBgLearningMessageRotater = (RotateLayout) mRootView.findViewById(R.id.bg_replace_message);
2207        mBgLearningMessageFrame = mRootView.findViewById(R.id.bg_replace_message_frame);
2208    }
2209
2210    @Override
2211    public void onConfigurationChanged(Configuration newConfig) {
2212        setDisplayOrientation();
2213
2214        // Change layout in response to configuration change
2215        LayoutInflater inflater = mActivity.getLayoutInflater();
2216        LinearLayout appRoot = (LinearLayout) mRootView.findViewById(R.id.camera_app_root);
2217        appRoot.setOrientation(
2218                newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
2219                ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
2220        appRoot.removeAllViews();
2221        inflater.inflate(R.layout.preview_frame_video, appRoot);
2222        inflater.inflate(R.layout.camera_control, appRoot);
2223
2224        // from onCreate()
2225        initializeControlByIntent();
2226        initializeSurfaceView();
2227        initializeMiscControls();
2228        showTimeLapseUI(mCaptureTimeLapse);
2229        initializeVideoSnapshot();
2230        resizeForPreviewAspectRatio();
2231        initializeIndicatorControl();
2232
2233        // from onResume()
2234        showVideoSnapshotUI(false);
2235        initializeZoom();
2236        if (!mIsVideoCaptureIntent) {
2237            mActivity.updateThumbnailView();
2238            if (mModePicker != null) {
2239                mModePicker.setCurrentMode(ModePicker.MODE_VIDEO);
2240            }
2241        }
2242    }
2243
2244    @Override
2245    public void onOverriddenPreferencesClicked() {
2246    }
2247
2248    @Override
2249    public void onRestorePreferencesClicked() {
2250        Runnable runnable = new Runnable() {
2251            @Override
2252            public void run() {
2253                restorePreferences();
2254            }
2255        };
2256        mRotateDialog.showAlertDialog(
2257                null,
2258                mActivity.getString(R.string.confirm_restore_message),
2259                mActivity.getString(android.R.string.ok), runnable,
2260                mActivity.getString(android.R.string.cancel), null);
2261    }
2262
2263    private void restorePreferences() {
2264        // Reset the zoom. Zoom value is not stored in preference.
2265        if (mParameters.isZoomSupported()) {
2266            mZoomValue = 0;
2267            setCameraParameters();
2268            mZoomControl.setZoomIndex(0);
2269        }
2270
2271        if (mIndicatorControlContainer != null) {
2272            mIndicatorControlContainer.dismissSettingPopup();
2273            CameraSettings.restorePreferences(mActivity, mPreferences,
2274                    mParameters);
2275            mIndicatorControlContainer.reloadPreferences();
2276            onSharedPreferenceChanged();
2277        }
2278    }
2279
2280    private boolean effectsActive() {
2281        return (mEffectType != EffectsRecorder.EFFECT_NONE);
2282    }
2283
2284    @Override
2285    public void onSharedPreferenceChanged() {
2286        // ignore the events after "onPause()" or preview has not started yet
2287        if (mPaused) return;
2288        synchronized (mPreferences) {
2289            // If mCameraDevice is not ready then we can set the parameter in
2290            // startPreview().
2291            if (mActivity.mCameraDevice == null) return;
2292
2293            boolean recordLocation = RecordLocationPreference.get(
2294                    mPreferences, mContentResolver);
2295            mLocationManager.recordLocation(recordLocation);
2296
2297            // Check if the current effects selection has changed
2298            if (updateEffectSelection()) return;
2299
2300            readVideoPreferences();
2301            showTimeLapseUI(mCaptureTimeLapse);
2302            // We need to restart the preview if preview size is changed.
2303            Size size = mParameters.getPreviewSize();
2304            if (size.width != mDesiredPreviewWidth
2305                    || size.height != mDesiredPreviewHeight) {
2306                if (!effectsActive()) {
2307                    stopPreview();
2308                } else {
2309                    mEffectsRecorder.release();
2310                    mEffectsRecorder = null;
2311                }
2312                resizeForPreviewAspectRatio();
2313                startPreview(); // Parameters will be set in startPreview().
2314            } else {
2315                setCameraParameters();
2316            }
2317        }
2318    }
2319
2320    @Override
2321    public void onCameraPickerClicked(int cameraId) {
2322        if (mPaused || mActivity.mPendingSwitchCameraId != -1) return;
2323
2324        mActivity.mPendingSwitchCameraId = cameraId;
2325        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2326            Log.d(TAG, "Start to copy texture.");
2327            // We need to keep a preview frame for the animation before
2328            // releasing the camera. This will trigger onPreviewTextureCopied.
2329            ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
2330            // Disable all camera controls.
2331            mSwitchingCamera = true;
2332        } else {
2333            switchCamera();
2334        }
2335    }
2336
2337    private void switchCamera() {
2338        if (mPaused) return;
2339
2340        Log.d(TAG, "Start to switch camera.");
2341        mCameraId = mActivity.mPendingSwitchCameraId;
2342        mActivity.mPendingSwitchCameraId = -1;
2343        mActivity.mCameraPicker.setCameraId(mCameraId);
2344
2345        closeCamera();
2346
2347        // Restart the camera and initialize the UI. From onCreate.
2348        mPreferences.setLocalId(mActivity, mCameraId);
2349        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
2350        CameraOpenThread cameraOpenThread = new CameraOpenThread();
2351        cameraOpenThread.start();
2352        try {
2353            cameraOpenThread.join();
2354        } catch (InterruptedException ex) {
2355            // ignore
2356        }
2357        readVideoPreferences();
2358        startPreview();
2359        initializeVideoSnapshot();
2360        resizeForPreviewAspectRatio();
2361        initializeIndicatorControl();
2362
2363        // From onResume
2364        initializeZoom();
2365        setOrientationIndicator(mOrientationCompensation, false);
2366
2367        if (ApiHelper.HAS_SURFACE_TEXTURE) {
2368            // Start switch camera animation. Post a message because
2369            // onFrameAvailable from the old camera may already exist.
2370            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
2371        }
2372    }
2373
2374    // Preview texture has been copied. Now camera can be released and the
2375    // animation can be started.
2376    @Override
2377    public void onPreviewTextureCopied() {
2378        mHandler.sendEmptyMessage(SWITCH_CAMERA);
2379    }
2380
2381    private boolean updateEffectSelection() {
2382        int previousEffectType = mEffectType;
2383        Object previousEffectParameter = mEffectParameter;
2384        mEffectType = CameraSettings.readEffectType(mPreferences);
2385        mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
2386
2387        if (mEffectType == previousEffectType) {
2388            if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
2389            if (mEffectParameter.equals(previousEffectParameter)) return false;
2390        }
2391        Log.v(TAG, "New effect selection: " + mPreferences.getString(
2392                CameraSettings.KEY_VIDEO_EFFECT, "none"));
2393
2394        if (mEffectType == EffectsRecorder.EFFECT_NONE) {
2395            // Stop effects and return to normal preview
2396            mEffectsRecorder.stopPreview();
2397            mPreviewing = false;
2398            return true;
2399        }
2400        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
2401            ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
2402            // Request video from gallery to use for background
2403            Intent i = new Intent(Intent.ACTION_PICK);
2404            i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
2405                             "video/*");
2406            i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
2407            mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
2408            return true;
2409        }
2410        if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
2411            // Stop regular preview and start effects.
2412            stopPreview();
2413            checkQualityAndStartPreview();
2414        } else {
2415            // Switch currently running effect
2416            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
2417        }
2418        return true;
2419    }
2420
2421    // Verifies that the current preview view size is correct before starting
2422    // preview. If not, resets the surface texture and resizes the view.
2423    private void checkQualityAndStartPreview() {
2424        readVideoPreferences();
2425        showTimeLapseUI(mCaptureTimeLapse);
2426        Size size = mParameters.getPreviewSize();
2427        if (size.width != mDesiredPreviewWidth
2428                || size.height != mDesiredPreviewHeight) {
2429            resizeForPreviewAspectRatio();
2430        }
2431        // Start up preview again
2432        startPreview();
2433    }
2434
2435    private void showTimeLapseUI(boolean enable) {
2436        if (mTimeLapseLabel != null) {
2437            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
2438        }
2439    }
2440
2441    @Override
2442    public boolean dispatchTouchEvent(MotionEvent m) {
2443        if (mSwitchingCamera) return true;
2444
2445        // Check if the popup window should be dismissed first.
2446        if (m.getAction() == MotionEvent.ACTION_DOWN) {
2447            float x = m.getX();
2448            float y = m.getY();
2449            // Dismiss the mode selection window if the ACTION_DOWN event is out
2450            // of its view area.
2451            if ((mModePicker != null) && !Util.pointInView(x, y, mModePicker)) {
2452                mModePicker.dismissModeSelection();
2453            }
2454            // Check if the popup window is visible.
2455            View popup = mIndicatorControlContainer.getActiveSettingPopup();
2456            if (popup != null) {
2457                // Let popup window, indicator control or preview frame handle the
2458                // event by themselves. Dismiss the popup window if users touch on
2459                // other areas.
2460                if (!Util.pointInView(x, y, popup)
2461                        && !Util.pointInView(x, y, mIndicatorControlContainer)) {
2462                    mIndicatorControlContainer.dismissSettingPopup();
2463                }
2464            }
2465        }
2466        return false;
2467    }
2468
2469    private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
2470        @Override
2471        public void onZoomValueChanged(int index) {
2472            // Not useful to change zoom value when the activity is paused.
2473            if (mPaused) return;
2474
2475            mZoomValue = index;
2476
2477            // Set zoom parameters asynchronously
2478            mParameters.setZoom(mZoomValue);
2479            mActivity.mCameraDevice.setParametersAsync(mParameters);
2480        }
2481    }
2482
2483    private void initializeZoom() {
2484        mZoomControl = (ZoomControl) mRootView.findViewById(R.id.zoom_control);
2485        if (!mParameters.isZoomSupported()) return;
2486
2487        mZoomMax = mParameters.getMaxZoom();
2488        // Currently we use immediate zoom for fast zooming to get better UX and
2489        // there is no plan to take advantage of the smooth zoom.
2490        mZoomControl.setZoomMax(mZoomMax);
2491        mZoomControl.setZoomIndex(mParameters.getZoom());
2492        mZoomControl.setOnZoomChangeListener(new ZoomChangeListener());
2493    }
2494
2495    private void initializeVideoSnapshot() {
2496        if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
2497            mActivity.setSingleTapUpListener(mPreviewFrameLayout);
2498            // Show the tap to focus toast if this is the first start.
2499            if (mPreferences.getBoolean(
2500                        CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
2501                // Delay the toast for one second to wait for orientation.
2502                mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
2503            }
2504        } else {
2505            mActivity.setSingleTapUpListener(null);
2506        }
2507    }
2508
2509    void showVideoSnapshotUI(boolean enabled) {
2510        if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
2511            mPreviewFrameLayout.showBorder(enabled);
2512            mIndicatorControlContainer.enableZoom(!enabled);
2513            mShutterButton.setEnabled(!enabled);
2514        }
2515    }
2516
2517    // Preview area is touched. Take a picture.
2518    @Override
2519    public void onSingleTapUp(View view, int x, int y) {
2520        if (mMediaRecorderRecording && effectsActive()) {
2521            new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint,
2522                    mOrientation).show();
2523            return;
2524        }
2525
2526        if (mPaused || mSnapshotInProgress
2527                || !mMediaRecorderRecording || effectsActive()) {
2528            return;
2529        }
2530
2531        // Set rotation and gps data.
2532        int rotation = Util.getJpegRotation(mCameraId, mOrientation);
2533        mParameters.setRotation(rotation);
2534        Location loc = mLocationManager.getCurrentLocation();
2535        Util.setGpsParameters(mParameters, loc);
2536        mActivity.mCameraDevice.setParameters(mParameters);
2537
2538        Log.v(TAG, "Video snapshot start");
2539        mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
2540        showVideoSnapshotUI(true);
2541        mSnapshotInProgress = true;
2542    }
2543
2544    @Override
2545    public void updateCameraAppView() {
2546        if (!mPreviewing || mParameters.getFlashMode() == null) return;
2547
2548        // When going to and back from gallery, we need to turn off/on the flash.
2549        if (!mActivity.mShowCameraAppView) {
2550            if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
2551                mRestoreFlash = false;
2552                return;
2553            }
2554            mRestoreFlash = true;
2555            setCameraParameters();
2556        } else if (mRestoreFlash) {
2557            mRestoreFlash = false;
2558            setCameraParameters();
2559        }
2560    }
2561
2562    @Override
2563    public void onFullScreenChanged(boolean full) {
2564        if (ApiHelper.HAS_SURFACE_TEXTURE) return;
2565
2566        if (full) {
2567            mPreviewSurfaceView.expand();
2568        } else {
2569            mPreviewSurfaceView.shrink();
2570        }
2571    }
2572
2573    private final class JpegPictureCallback implements PictureCallback {
2574        Location mLocation;
2575
2576        public JpegPictureCallback(Location loc) {
2577            mLocation = loc;
2578        }
2579
2580        @Override
2581        public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
2582            Log.v(TAG, "onPictureTaken");
2583            mSnapshotInProgress = false;
2584            showVideoSnapshotUI(false);
2585            storeImage(jpegData, mLocation);
2586        }
2587    }
2588
2589    private void storeImage(final byte[] data, Location loc) {
2590        long dateTaken = System.currentTimeMillis();
2591        String title = Util.createJpegName(dateTaken);
2592        int orientation = Exif.getOrientation(data);
2593        Size s = mParameters.getPictureSize();
2594        Uri uri = Storage.addImage(mContentResolver, title, dateTaken, loc, orientation, data,
2595                s.width, s.height);
2596        if (uri != null) {
2597            // Create a thumbnail whose width is equal or bigger than that of the preview.
2598            int ratio = (int) Math.ceil((double) mParameters.getPictureSize().width
2599                    / mPreviewFrameLayout.getWidth());
2600            int inSampleSize = Integer.highestOneBit(ratio);
2601            mActivity.mThumbnail = Thumbnail.createThumbnail(data, orientation, inSampleSize, uri);
2602            if (mActivity.mThumbnail != null && mActivity.mThumbnailView != null) {
2603                mActivity.mThumbnailView.setBitmap(mActivity.mThumbnail.getBitmap());
2604            }
2605            Util.broadcastNewPicture(mActivity, uri);
2606        }
2607    }
2608
2609    private boolean resetEffect() {
2610        if (mResetEffect) {
2611            String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
2612                    mPrefVideoEffectDefault);
2613            if (!mPrefVideoEffectDefault.equals(value)) {
2614                writeDefaultEffectToPrefs();
2615                return true;
2616            }
2617        }
2618        mResetEffect = true;
2619        return false;
2620    }
2621
2622    private String convertOutputFormatToMimeType(int outputFileFormat) {
2623        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2624            return "video/mp4";
2625        }
2626        return "video/3gpp";
2627    }
2628
2629    private String convertOutputFormatToFileExt(int outputFileFormat) {
2630        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
2631            return ".mp4";
2632        }
2633        return ".3gp";
2634    }
2635
2636    private void closeVideoFileDescriptor() {
2637        if (mVideoFileDescriptor != null) {
2638            try {
2639                mVideoFileDescriptor.close();
2640            } catch (IOException e) {
2641                Log.e(TAG, "Fail to close fd", e);
2642            }
2643            mVideoFileDescriptor = null;
2644        }
2645    }
2646
2647    private void showTapToSnapshotToast() {
2648        new RotateTextToast(mActivity, R.string.video_snapshot_hint, mOrientationCompensation)
2649                .show();
2650        // Clear the preference.
2651        Editor editor = mPreferences.edit();
2652        editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
2653        editor.apply();
2654    }
2655
2656    private void clearVideoNamer() {
2657        if (mVideoNamer != null) {
2658            mVideoNamer.finish();
2659            mVideoNamer = null;
2660        }
2661    }
2662
2663    private static class VideoNamer extends Thread {
2664        private boolean mRequestPending;
2665        private ContentResolver mResolver;
2666        private ContentValues mValues;
2667        private boolean mStop;
2668        private Uri mUri;
2669
2670        // Runs in main thread
2671        public VideoNamer() {
2672            start();
2673        }
2674
2675        // Runs in main thread
2676        public synchronized void prepareUri(
2677                ContentResolver resolver, ContentValues values) {
2678            mRequestPending = true;
2679            mResolver = resolver;
2680            mValues = new ContentValues(values);
2681            notifyAll();
2682        }
2683
2684        // Runs in main thread
2685        public synchronized Uri getUri() {
2686            // wait until the request is done.
2687            while (mRequestPending) {
2688                try {
2689                    wait();
2690                } catch (InterruptedException ex) {
2691                    // ignore.
2692                }
2693            }
2694            Uri uri = mUri;
2695            mUri = null;
2696            return uri;
2697        }
2698
2699        // Runs in namer thread
2700        @Override
2701        public synchronized void run() {
2702            while (true) {
2703                if (mStop) break;
2704                if (!mRequestPending) {
2705                    try {
2706                        wait();
2707                    } catch (InterruptedException ex) {
2708                        // ignore.
2709                    }
2710                    continue;
2711                }
2712                cleanOldUri();
2713                generateUri();
2714                mRequestPending = false;
2715                notifyAll();
2716            }
2717            cleanOldUri();
2718        }
2719
2720        // Runs in main thread
2721        public synchronized void finish() {
2722            mStop = true;
2723            notifyAll();
2724        }
2725
2726        // Runs in namer thread
2727        private void generateUri() {
2728            Uri videoTable = Uri.parse("content://media/external/video/media");
2729            mUri = mResolver.insert(videoTable, mValues);
2730        }
2731
2732        // Runs in namer thread
2733        private void cleanOldUri() {
2734            if (mUri == null) return;
2735            mResolver.delete(mUri, null, null);
2736            mUri = null;
2737        }
2738    }
2739
2740    private class SurfaceViewCallback implements SurfaceHolder.Callback {
2741        public SurfaceViewCallback() {}
2742
2743        @Override
2744        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
2745            Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
2746        }
2747
2748        @Override
2749        public void surfaceCreated(SurfaceHolder holder) {
2750            Log.v(TAG, "Surface created");
2751            mSurfaceViewReady = true;
2752            if (mPaused) return;
2753            if (!ApiHelper.HAS_SURFACE_TEXTURE) {
2754                mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder());
2755                if (!mPreviewing) {
2756                    startPreview();
2757                }
2758            }
2759        }
2760
2761        @Override
2762        public void surfaceDestroyed(SurfaceHolder holder) {
2763            Log.v(TAG, "Surface destroyed");
2764            mSurfaceViewReady = false;
2765            if (mPaused) return;
2766            if (!ApiHelper.HAS_SURFACE_TEXTURE) {
2767                stopVideoRecording();
2768                stopPreview();
2769            }
2770        }
2771    }
2772
2773    @Override
2774    public boolean updateStorageHintOnResume() {
2775        return true;
2776    }
2777
2778}
2779