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