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