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