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