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