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.graphics.Bitmap;
29import android.graphics.Point;
30import android.graphics.SurfaceTexture;
31import android.location.Location;
32import android.media.AudioManager;
33import android.media.CamcorderProfile;
34import android.media.CameraProfile;
35import android.media.MediaRecorder;
36import android.net.Uri;
37import android.os.Build;
38import android.os.Bundle;
39import android.os.Handler;
40import android.os.Looper;
41import android.os.Message;
42import android.os.ParcelFileDescriptor;
43import android.os.SystemClock;
44import android.provider.MediaStore;
45import android.provider.MediaStore.MediaColumns;
46import android.provider.MediaStore.Video;
47import android.view.KeyEvent;
48import android.view.OrientationEventListener;
49import android.view.View;
50import android.widget.Toast;
51
52import com.android.camera.app.AppController;
53import com.android.camera.app.CameraAppUI;
54import com.android.camera.app.LocationManager;
55import com.android.camera.app.MediaSaver;
56import com.android.camera.app.MemoryManager;
57import com.android.camera.app.MemoryManager.MemoryListener;
58import com.android.camera.debug.Log;
59import com.android.camera.exif.ExifInterface;
60import com.android.camera.hardware.HardwareSpec;
61import com.android.camera.hardware.HardwareSpecImpl;
62import com.android.camera.module.ModuleController;
63import com.android.camera.settings.Keys;
64import com.android.camera.settings.SettingsManager;
65import com.android.camera.settings.SettingsUtil;
66import com.android.camera.ui.TouchCoordinate;
67import com.android.camera.util.ApiHelper;
68import com.android.camera.util.CameraUtil;
69import com.android.camera.util.UsageStatistics;
70import com.android.camera2.R;
71import com.android.ex.camera2.portability.CameraAgent;
72import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
73import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
74import com.android.ex.camera2.portability.CameraCapabilities;
75import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
76import com.android.ex.camera2.portability.CameraSettings;
77import com.android.ex.camera2.portability.Size;
78import com.google.common.logging.eventprotos;
79
80import java.io.File;
81import java.io.IOException;
82import java.text.SimpleDateFormat;
83import java.util.ArrayList;
84import java.util.Date;
85import java.util.Iterator;
86import java.util.List;
87import java.util.Set;
88
89public class VideoModule extends CameraModule
90    implements ModuleController,
91    VideoController,
92    MemoryListener,
93    MediaRecorder.OnErrorListener,
94    MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
95
96    private static final String VIDEO_MODULE_STRING_ID = "VideoModule";
97
98    private static final Log.Tag TAG = new Log.Tag(VIDEO_MODULE_STRING_ID);
99
100    // Messages defined for the UI thread handler.
101    private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
102    private static final int MSG_UPDATE_RECORD_TIME = 5;
103    private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
104    private static final int MSG_SWITCH_CAMERA = 8;
105    private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
106
107    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
108
109    /**
110     * An unpublished intent flag requesting to start recording straight away
111     * and return as soon as recording is stopped.
112     * TODO: consider publishing by moving into MediaStore.
113     */
114    private static final String EXTRA_QUICK_CAPTURE =
115            "android.intent.extra.quickCapture";
116
117    // module fields
118    private CameraActivity mActivity;
119    private boolean mPaused;
120
121    // if, during and intent capture, the activity is paused (e.g. when app switching or reviewing a
122    // shot video), we don't want the bottom bar intent ui to reset to the capture button
123    private boolean mDontResetIntentUiOnResume;
124
125    private int mCameraId;
126    private CameraSettings mCameraSettings;
127    private CameraCapabilities mCameraCapabilities;
128
129    private boolean mIsInReviewMode;
130    private boolean mSnapshotInProgress = false;
131
132    // Preference must be read before starting preview. We check this before starting
133    // preview.
134    private boolean mPreferenceRead;
135
136    private boolean mIsVideoCaptureIntent;
137    private boolean mQuickCapture;
138
139    private MediaRecorder mMediaRecorder;
140
141    private boolean mSwitchingCamera;
142    private boolean mMediaRecorderRecording = false;
143    private long mRecordingStartTime;
144    private boolean mRecordingTimeCountsDown = false;
145    private long mOnResumeTime;
146    // The video file that the hardware camera is about to record into
147    // (or is recording into.
148    private String mVideoFilename;
149    private ParcelFileDescriptor mVideoFileDescriptor;
150
151    // The video file that has already been recorded, and that is being
152    // examined by the user.
153    private String mCurrentVideoFilename;
154    private Uri mCurrentVideoUri;
155    private boolean mCurrentVideoUriFromMediaSaved;
156    private ContentValues mCurrentVideoValues;
157
158    private CamcorderProfile mProfile;
159
160    // The video duration limit. 0 means no limit.
161    private int mMaxVideoDurationInMs;
162
163    boolean mPreviewing = false; // True if preview is started.
164    // The display rotation in degrees. This is only valid when mPreviewing is
165    // true.
166    private int mDisplayRotation;
167    private int mCameraDisplayOrientation;
168    private AppController mAppController;
169
170    private int mDesiredPreviewWidth;
171    private int mDesiredPreviewHeight;
172    private ContentResolver mContentResolver;
173
174    private LocationManager mLocationManager;
175
176    private int mPendingSwitchCameraId;
177    private final Handler mHandler = new MainHandler();
178    private VideoUI mUI;
179    private CameraProxy mCameraDevice;
180
181    // The degrees of the device rotated clockwise from its natural orientation.
182    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
183
184    private float mZoomValue;  // The current zoom ratio.
185
186    private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
187            new MediaSaver.OnMediaSavedListener() {
188                @Override
189                public void onMediaSaved(Uri uri) {
190                    if (uri != null) {
191                        mCurrentVideoUri = uri;
192                        mCurrentVideoUriFromMediaSaved = true;
193                        onVideoSaved();
194                        mActivity.notifyNewMedia(uri);
195                    }
196                }
197            };
198
199    private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
200            new MediaSaver.OnMediaSavedListener() {
201                @Override
202                public void onMediaSaved(Uri uri) {
203                    if (uri != null) {
204                        mActivity.notifyNewMedia(uri);
205                    }
206                }
207            };
208    private FocusOverlayManager mFocusManager;
209    private boolean mMirror;
210    private boolean mFocusAreaSupported;
211    private boolean mMeteringAreaSupported;
212
213    private final CameraAgent.CameraAFCallback mAutoFocusCallback =
214            new CameraAgent.CameraAFCallback() {
215        @Override
216        public void onAutoFocus(boolean focused, CameraProxy camera) {
217            if (mPaused) {
218                return;
219            }
220            mFocusManager.onAutoFocus(focused, false);
221        }
222    };
223
224    private final Object mAutoFocusMoveCallback =
225            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
226                    ? new CameraAgent.CameraAFMoveCallback() {
227                @Override
228                public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
229                    // mFocusManager.onAutoFocusMoving(moving) not called because UI
230                    // not compatible with vertical video hint UI.
231                }
232            } : null;
233
234    /**
235     * This Handler is used to post message back onto the main thread of the
236     * application.
237     */
238    private class MainHandler extends Handler {
239        @Override
240        public void handleMessage(Message msg) {
241            switch (msg.what) {
242
243                case MSG_ENABLE_SHUTTER_BUTTON:
244                    mAppController.setShutterEnabled(true);
245                    break;
246
247                case MSG_UPDATE_RECORD_TIME: {
248                    updateRecordingTime();
249                    break;
250                }
251
252                case MSG_CHECK_DISPLAY_ROTATION: {
253                    // Restart the preview if display rotation has changed.
254                    // Sometimes this happens when the device is held upside
255                    // down and camera app is opened. Rotation animation will
256                    // take some time and the rotation value we have got may be
257                    // wrong. Framework does not have a callback for this now.
258                    if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
259                            && !mMediaRecorderRecording && !mSwitchingCamera) {
260                        startPreview();
261                    }
262                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
263                        mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
264                    }
265                    break;
266                }
267
268                case MSG_SWITCH_CAMERA: {
269                    switchCamera();
270                    break;
271                }
272
273                case MSG_SWITCH_CAMERA_START_ANIMATION: {
274                    //TODO:
275                    //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
276
277                    // Enable all camera controls.
278                    mSwitchingCamera = false;
279                    break;
280                }
281
282                default:
283                    Log.v(TAG, "Unhandled message: " + msg.what);
284                    break;
285            }
286        }
287    }
288
289    private BroadcastReceiver mReceiver = null;
290
291    private class MyBroadcastReceiver extends BroadcastReceiver {
292        @Override
293        public void onReceive(Context context, Intent intent) {
294            String action = intent.getAction();
295            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
296                stopVideoRecording();
297            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
298                Toast.makeText(mActivity,
299                        mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
300            }
301        }
302    }
303
304    private int mShutterIconId;
305
306
307    /**
308     * Construct a new video module.
309     */
310    public VideoModule(AppController app) {
311        super(app);
312    }
313
314    @Override
315    public String getPeekAccessibilityString() {
316        return mAppController.getAndroidContext()
317            .getResources().getString(R.string.video_accessibility_peek);
318    }
319
320    private String createName(long dateTaken) {
321        Date date = new Date(dateTaken);
322        SimpleDateFormat dateFormat = new SimpleDateFormat(
323                mActivity.getString(R.string.video_file_name_format));
324
325        return dateFormat.format(date);
326    }
327
328    @Override
329    public String getModuleStringIdentifier() {
330        return VIDEO_MODULE_STRING_ID;
331    }
332
333    @Override
334    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
335        mActivity = activity;
336        // TODO: Need to look at the controller interface to see if we can get
337        // rid of passing in the activity directly.
338        mAppController = mActivity;
339
340        mActivity.updateStorageSpaceAndHint(null);
341
342        mUI = new VideoUI(mActivity, this,  mActivity.getModuleLayoutRoot());
343        mActivity.setPreviewStatusListener(mUI);
344
345        SettingsManager settingsManager = mActivity.getSettingsManager();
346        mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
347                                               Keys.KEY_CAMERA_ID);
348
349        /*
350         * To reduce startup time, we start the preview in another thread.
351         * We make sure the preview is started at the end of onCreate.
352         */
353        requestCamera(mCameraId);
354
355        mContentResolver = mActivity.getContentResolver();
356
357        // Surface texture is from camera screen nail and startPreview needs it.
358        // This must be done before startPreview.
359        mIsVideoCaptureIntent = isVideoCaptureIntent();
360
361        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
362        mLocationManager = mActivity.getLocationManager();
363
364        mUI.setOrientationIndicator(0, false);
365        setDisplayOrientation();
366
367        mPendingSwitchCameraId = -1;
368
369        mShutterIconId = CameraUtil.getCameraShutterIconId(
370                mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
371    }
372
373    @Override
374    public boolean isUsingBottomBar() {
375        return true;
376    }
377
378    private void initializeControlByIntent() {
379        if (isVideoCaptureIntent()) {
380            if (!mDontResetIntentUiOnResume) {
381                mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
382            }
383            // reset the flag
384            mDontResetIntentUiOnResume = false;
385        }
386    }
387
388    @Override
389    public void onSingleTapUp(View view, int x, int y) {
390        if (mPaused || mCameraDevice == null) {
391            return;
392        }
393        if (mMediaRecorderRecording) {
394            if (!mSnapshotInProgress) {
395                takeASnapshot();
396            }
397            return;
398        }
399        // Check if metering area or focus area is supported.
400        if (!mFocusAreaSupported && !mMeteringAreaSupported) {
401            return;
402        }
403        // Tap to focus.
404        mFocusManager.onSingleTapUp(x, y);
405    }
406
407    private void takeASnapshot() {
408        // Only take snapshots if video snapshot is supported by device
409        if(!mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_SNAPSHOT)) {
410            Log.w(TAG, "Cannot take a video snapshot - not supported by hardware");
411            return;
412        }
413        if (!mIsVideoCaptureIntent) {
414            if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress
415                    || !mAppController.isShutterEnabled() || mCameraDevice == null) {
416                return;
417            }
418
419            Location loc = mLocationManager.getCurrentLocation();
420            CameraUtil.setGpsParameters(mCameraSettings, loc);
421            mCameraDevice.applySettings(mCameraSettings);
422
423            Log.i(TAG, "Video snapshot start");
424            mCameraDevice.takePicture(mHandler,
425                    null, null, null, new JpegPictureCallback(loc));
426            showVideoSnapshotUI(true);
427            mSnapshotInProgress = true;
428        }
429    }
430
431    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
432     private void updateAutoFocusMoveCallback() {
433        if (mPaused || mCameraDevice == null) {
434            return;
435        }
436
437        if (mCameraSettings.getCurrentFocusMode() == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
438            mCameraDevice.setAutoFocusMoveCallback(mHandler,
439                    (CameraAgent.CameraAFMoveCallback) mAutoFocusMoveCallback);
440        } else {
441            mCameraDevice.setAutoFocusMoveCallback(null, null);
442        }
443    }
444
445    /**
446     * @return Whether the currently active camera is front-facing.
447     */
448    private boolean isCameraFrontFacing() {
449        return mAppController.getCameraProvider().getCharacteristics(mCameraId)
450                .isFacingFront();
451    }
452
453    /**
454     * @return Whether the currently active camera is back-facing.
455     */
456    private boolean isCameraBackFacing() {
457        return mAppController.getCameraProvider().getCharacteristics(mCameraId)
458                .isFacingBack();
459    }
460
461    /**
462     * The focus manager gets initialized after camera is available.
463     */
464    private void initializeFocusManager() {
465        // Create FocusManager object. startPreview needs it.
466        // if mFocusManager not null, reuse it
467        // otherwise create a new instance
468        if (mFocusManager != null) {
469            mFocusManager.removeMessages();
470        } else {
471            mMirror = isCameraFrontFacing();
472            String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
473                    R.array.pref_camera_focusmode_default_array);
474            CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
475            ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
476                    new ArrayList<CameraCapabilities.FocusMode>();
477            for (String modeString : defaultFocusModesStrings) {
478                CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
479                if (mode != null) {
480                    defaultFocusModes.add(mode);
481                }
482            }
483            mFocusManager = new FocusOverlayManager(mAppController,
484                    defaultFocusModes, mCameraCapabilities, this, mMirror,
485                    mActivity.getMainLooper(), mUI.getFocusUI());
486        }
487        mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
488    }
489
490    @Override
491    public void onOrientationChanged(int orientation) {
492        // We keep the last known orientation. So if the user first orient
493        // the camera then point the camera to floor or sky, we still have
494        // the correct orientation.
495        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
496            return;
497        }
498        int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
499
500        if (mOrientation != newOrientation) {
501            mOrientation = newOrientation;
502        }
503        mUI.onOrientationChanged(orientation);
504
505    }
506
507    private final ButtonManager.ButtonCallback mFlashCallback =
508        new ButtonManager.ButtonCallback() {
509            @Override
510            public void onStateChanged(int state) {
511                // Update flash parameters.
512                enableTorchMode(true);
513            }
514        };
515
516    private final ButtonManager.ButtonCallback mCameraCallback =
517        new ButtonManager.ButtonCallback() {
518            @Override
519            public void onStateChanged(int state) {
520                if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
521                    return;
522                }
523                mPendingSwitchCameraId = state;
524                Log.d(TAG, "Start to copy texture.");
525
526                // Disable all camera controls.
527                mSwitchingCamera = true;
528                switchCamera();
529            }
530        };
531
532    private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
533        @Override
534        public void onClick(View v) {
535            onReviewCancelClicked(v);
536        }
537    };
538
539    private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
540        @Override
541        public void onClick(View v) {
542            onReviewDoneClicked(v);
543        }
544    };
545    private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
546        @Override
547        public void onClick(View v) {
548            onReviewPlayClicked(v);
549        }
550    };
551
552    @Override
553    public void hardResetSettings(SettingsManager settingsManager) {
554        // VideoModule does not need to hard reset any settings.
555    }
556
557    @Override
558    public HardwareSpec getHardwareSpec() {
559        return (mCameraSettings != null ?
560                new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
561    }
562
563    @Override
564    public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
565        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
566
567        bottomBarSpec.enableCamera = true;
568        bottomBarSpec.cameraCallback = mCameraCallback;
569        bottomBarSpec.enableTorchFlash = true;
570        bottomBarSpec.flashCallback = mFlashCallback;
571        bottomBarSpec.hideHdr = true;
572        bottomBarSpec.enableGridLines = true;
573
574        if (isVideoCaptureIntent()) {
575            bottomBarSpec.showCancel = true;
576            bottomBarSpec.cancelCallback = mCancelCallback;
577            bottomBarSpec.showDone = true;
578            bottomBarSpec.doneCallback = mDoneCallback;
579            bottomBarSpec.showReview = true;
580            bottomBarSpec.reviewCallback = mReviewCallback;
581        }
582
583        return bottomBarSpec;
584    }
585
586    @Override
587    public void onCameraAvailable(CameraProxy cameraProxy) {
588        if (cameraProxy == null) {
589            Log.w(TAG, "onCameraAvailable returns a null CameraProxy object");
590            return;
591        }
592        mCameraDevice = cameraProxy;
593        mCameraCapabilities = mCameraDevice.getCapabilities();
594        mCameraSettings = mCameraDevice.getSettings();
595        mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
596        mMeteringAreaSupported =
597                mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
598        readVideoPreferences();
599        updateDesiredPreviewSize();
600        resizeForPreviewAspectRatio();
601        initializeFocusManager();
602        // TODO: Having focus overlay manager caching the parameters is prone to error,
603        // we should consider passing the parameters to focus overlay to ensure the
604        // parameters are up to date.
605        mFocusManager.updateCapabilities(mCameraCapabilities);
606
607        startPreview();
608        initializeVideoSnapshot();
609        mUI.initializeZoom(mCameraSettings, mCameraCapabilities);
610        initializeControlByIntent();
611    }
612
613    private void startPlayVideoActivity() {
614        Intent intent = new Intent(Intent.ACTION_VIEW);
615        intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
616        try {
617            mActivity.launchActivityByIntent(intent);
618        } catch (ActivityNotFoundException ex) {
619            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
620        }
621    }
622
623    @Override
624    @OnClickAttr
625    public void onReviewPlayClicked(View v) {
626        startPlayVideoActivity();
627    }
628
629    @Override
630    @OnClickAttr
631    public void onReviewDoneClicked(View v) {
632        mIsInReviewMode = false;
633        doReturnToCaller(true);
634    }
635
636    @Override
637    @OnClickAttr
638    public void onReviewCancelClicked(View v) {
639        // TODO: It should be better to not even insert the URI at all before we
640        // confirm done in review, which means we need to handle temporary video
641        // files in a quite different way than we currently had.
642        // Make sure we don't delete the Uri sent from the video capture intent.
643        if (mCurrentVideoUriFromMediaSaved) {
644            mContentResolver.delete(mCurrentVideoUri, null, null);
645        }
646        mIsInReviewMode = false;
647        doReturnToCaller(false);
648    }
649
650    @Override
651    public boolean isInReviewMode() {
652        return mIsInReviewMode;
653    }
654
655    private void onStopVideoRecording() {
656        mAppController.getCameraAppUI().setSwipeEnabled(true);
657        boolean recordFail = stopVideoRecording();
658        if (mIsVideoCaptureIntent) {
659            if (mQuickCapture) {
660                doReturnToCaller(!recordFail);
661            } else if (!recordFail) {
662                showCaptureResult();
663            }
664        } else if (!recordFail){
665            // Start capture animation.
666            if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
667                // The capture animation is disabled on ICS because we use SurfaceView
668                // for preview during recording. When the recording is done, we switch
669                // back to use SurfaceTexture for preview and we need to stop then start
670                // the preview. This will cause the preview flicker since the preview
671                // will not be continuous for a short period of time.
672
673                mUI.animateFlash();
674            }
675        }
676    }
677
678    public void onVideoSaved() {
679        if (mIsVideoCaptureIntent) {
680            showCaptureResult();
681        }
682    }
683
684    public void onProtectiveCurtainClick(View v) {
685        // Consume clicks
686    }
687
688    @Override
689    public void onShutterButtonClick() {
690        if (mSwitchingCamera) {
691            return;
692        }
693        boolean stop = mMediaRecorderRecording;
694
695        if (stop) {
696            // CameraAppUI mishandles mode option enable/disable
697            // for video, override that
698            mAppController.getCameraAppUI().enableModeOptions();
699            onStopVideoRecording();
700        } else {
701            // CameraAppUI mishandles mode option enable/disable
702            // for video, override that
703            mAppController.getCameraAppUI().disableModeOptions();
704            startVideoRecording();
705        }
706        mAppController.setShutterEnabled(false);
707        if (mCameraSettings != null) {
708            mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());
709        }
710
711        // Keep the shutter button disabled when in video capture intent
712        // mode and recording is stopped. It'll be re-enabled when
713        // re-take button is clicked.
714        if (!(mIsVideoCaptureIntent && stop)) {
715            mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
716        }
717    }
718
719    @Override
720    public void onShutterCoordinate(TouchCoordinate coord) {
721        // Do nothing.
722    }
723
724    @Override
725    public void onShutterButtonFocus(boolean pressed) {
726        // TODO: Remove this when old camera controls are removed from the UI.
727    }
728
729    private void readVideoPreferences() {
730        // The preference stores values from ListPreference and is thus string type for all values.
731        // We need to convert it to int manually.
732        SettingsManager settingsManager = mActivity.getSettingsManager();
733        String videoQualityKey = isCameraFrontFacing() ? Keys.KEY_VIDEO_QUALITY_FRONT
734            : Keys.KEY_VIDEO_QUALITY_BACK;
735        String videoQuality = settingsManager
736                .getString(SettingsManager.SCOPE_GLOBAL, videoQualityKey);
737        int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
738        Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
739
740        // Set video quality.
741        Intent intent = mActivity.getIntent();
742        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
743            int extraVideoQuality =
744                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
745            if (extraVideoQuality > 0) {
746                quality = CamcorderProfile.QUALITY_HIGH;
747            } else {  // 0 is mms.
748                quality = CamcorderProfile.QUALITY_LOW;
749            }
750        }
751
752        // Set video duration limit. The limit is read from the preference,
753        // unless it is specified in the intent.
754        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
755            int seconds =
756                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
757            mMaxVideoDurationInMs = 1000 * seconds;
758        } else {
759            mMaxVideoDurationInMs = SettingsUtil.getMaxVideoDuration(mActivity
760                    .getAndroidContext());
761        }
762
763        // If quality is not supported, request QUALITY_HIGH which is always supported.
764        if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
765            quality = CamcorderProfile.QUALITY_HIGH;
766        }
767        mProfile = CamcorderProfile.get(mCameraId, quality);
768        mPreferenceRead = true;
769    }
770
771    /**
772     * Calculates and sets local class variables for Desired Preview sizes.
773     * This function should be called after every change in preview camera
774     * resolution and/or before the preview starts. Note that these values still
775     * need to be pushed to the CameraSettings to actually change the preview
776     * resolution.  Does nothing when camera pointer is null.
777     */
778    private void updateDesiredPreviewSize() {
779        if (mCameraDevice == null) {
780            return;
781        }
782
783        mCameraSettings = mCameraDevice.getSettings();
784        Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
785                mCameraSettings, mCameraCapabilities, mProfile, mUI.getPreviewScreenSize());
786        mDesiredPreviewWidth = desiredPreviewSize.x;
787        mDesiredPreviewHeight = desiredPreviewSize.y;
788        mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
789        Log.v(TAG, "Updated DesiredPreview=" + mDesiredPreviewWidth + "x"
790                + mDesiredPreviewHeight);
791    }
792
793    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
794    /**
795     * Calculates the preview size and stores it in mDesiredPreviewWidth and
796     * mDesiredPreviewHeight.
797     *
798     * <p>This function checks {@link
799     * com.android.camera.cameradevice.CameraCapabilities#getPreferredPreviewSizeForVideo()}
800     * but also considers the current preview area size on screen and make sure
801     * the final preview size will not be smaller than 1/2 of the current
802     * on screen preview area in terms of their short sides.  This function has
803     * highest priority of WYSIWYG, 1:1 matching as its best match, even if
804     * there's a larger preview that meets the condition above. </p>
805     *
806     * @return The preferred preview size or {@code null} if the camera is not
807     *         opened yet.
808     */
809    private static Point getDesiredPreviewSize(Context context, CameraSettings settings,
810            CameraCapabilities capabilities, CamcorderProfile profile, Point previewScreenSize) {
811        if (capabilities.getSupportedVideoSizes() == null) {
812            // Driver doesn't support separate outputs for preview and video.
813            return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
814        }
815
816        final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
817                previewScreenSize.x : previewScreenSize.y);
818        List<Size> sizes = capabilities.getSupportedPreviewSizes();
819        Size preferred = capabilities.getPreferredPreviewSizeForVideo();
820        final int preferredPreviewSizeShortSide = (preferred.width() < preferred.height() ?
821                preferred.width() : preferred.height());
822        if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
823            preferred = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
824        }
825        int product = preferred.width() * preferred.height();
826        Iterator<Size> it = sizes.iterator();
827        // Remove the preview sizes that are not preferred.
828        while (it.hasNext()) {
829            Size size = it.next();
830            if (size.width() * size.height() > product) {
831                it.remove();
832            }
833        }
834
835        // Take highest priority for WYSIWYG when the preview exactly matches
836        // video frame size.  The variable sizes is assumed to be filtered
837        // for sizes beyond the UI size.
838        for (Size size : sizes) {
839            if (size.width() == profile.videoFrameWidth
840                    && size.height() == profile.videoFrameHeight) {
841                Log.v(TAG, "Selected =" + size.width() + "x" + size.height()
842                           + " on WYSIWYG Priority");
843                return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
844            }
845        }
846
847        Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
848                (double) profile.videoFrameWidth / profile.videoFrameHeight);
849        return new Point(optimalSize.width(), optimalSize.height());
850    }
851
852    private void resizeForPreviewAspectRatio() {
853        mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
854    }
855
856    private void installIntentFilter() {
857        // install an intent filter to receive SD card related events.
858        IntentFilter intentFilter =
859                new IntentFilter(Intent.ACTION_MEDIA_EJECT);
860        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
861        intentFilter.addDataScheme("file");
862        mReceiver = new MyBroadcastReceiver();
863        mActivity.registerReceiver(mReceiver, intentFilter);
864    }
865
866    private void setDisplayOrientation() {
867        mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
868        Characteristics info =
869                mActivity.getCameraProvider().getCharacteristics(mCameraId);
870        mCameraDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
871        // Change the camera display orientation
872        if (mCameraDevice != null) {
873            mCameraDevice.setDisplayOrientation(mDisplayRotation);
874        }
875        if (mFocusManager != null) {
876            mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
877        }
878    }
879
880    @Override
881    public void updateCameraOrientation() {
882        if (mMediaRecorderRecording) {
883            return;
884        }
885        if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
886            setDisplayOrientation();
887        }
888    }
889
890    @Override
891    public void updatePreviewAspectRatio(float aspectRatio) {
892        mAppController.updatePreviewAspectRatio(aspectRatio);
893    }
894
895    /**
896     * Returns current Zoom value, with 1.0 as the value for no zoom.
897     */
898    private float currentZoomValue() {
899        return mCameraSettings.getCurrentZoomRatio();
900    }
901
902    @Override
903    public void onZoomChanged(float ratio) {
904        // Not useful to change zoom value when the activity is paused.
905        if (mPaused) {
906            return;
907        }
908        mZoomValue = ratio;
909        if (mCameraSettings == null || mCameraDevice == null) {
910            return;
911        }
912        // Set zoom parameters asynchronously
913        mCameraSettings.setZoomRatio(mZoomValue);
914        mCameraDevice.applySettings(mCameraSettings);
915    }
916
917    private void startPreview() {
918        Log.i(TAG, "startPreview");
919
920        SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
921        if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
922                mCameraDevice == null) {
923            return;
924        }
925
926        if (mPreviewing == true) {
927            stopPreview();
928        }
929
930        setDisplayOrientation();
931        mCameraDevice.setDisplayOrientation(mDisplayRotation);
932        setCameraParameters();
933
934        if (mFocusManager != null) {
935            // If the focus mode is continuous autofocus, call cancelAutoFocus
936            // to resume it because it may have been paused by autoFocus call.
937            CameraCapabilities.FocusMode focusMode =
938                    mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode());
939            if (focusMode == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
940                mCameraDevice.cancelAutoFocus();
941            }
942        }
943
944        // This is to notify app controller that preview will start next, so app
945        // controller can set preview callbacks if needed. This has to happen before
946        // preview is started as a workaround of the framework issue related to preview
947        // callbacks that causes preview stretch and crash. (More details see b/12210027
948        // and b/12591410. Don't apply this to L, see b/16649297.
949        if (!ApiHelper.isLOrHigher()) {
950            Log.v(TAG, "calling onPreviewReadyToStart to set one shot callback");
951            mAppController.onPreviewReadyToStart();
952        } else {
953            Log.v(TAG, "on L, no one shot callback necessary");
954        }
955        try {
956            mCameraDevice.setPreviewTexture(surfaceTexture);
957            mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()),
958                    new CameraAgent.CameraStartPreviewCallback() {
959                @Override
960                public void onPreviewStarted() {
961                    VideoModule.this.onPreviewStarted();
962                }
963            });
964            mPreviewing = true;
965        } catch (Throwable ex) {
966            closeCamera();
967            throw new RuntimeException("startPreview failed", ex);
968        }
969    }
970
971    private void onPreviewStarted() {
972        mAppController.setShutterEnabled(true);
973        mAppController.onPreviewStarted();
974        if (mFocusManager != null) {
975            mFocusManager.onPreviewStarted();
976        }
977    }
978
979    @Override
980    public void onPreviewInitialDataReceived() {
981    }
982
983    @Override
984    public void stopPreview() {
985        if (!mPreviewing) {
986            Log.v(TAG, "Skip stopPreview since it's not mPreviewing");
987            return;
988        }
989        if (mCameraDevice == null) {
990            Log.v(TAG, "Skip stopPreview since mCameraDevice is null");
991            return;
992        }
993
994        Log.v(TAG, "stopPreview");
995        mCameraDevice.stopPreview();
996        if (mFocusManager != null) {
997            mFocusManager.onPreviewStopped();
998        }
999        mPreviewing = false;
1000    }
1001
1002    private void closeCamera() {
1003        Log.i(TAG, "closeCamera");
1004        if (mCameraDevice == null) {
1005            Log.d(TAG, "already stopped.");
1006            return;
1007        }
1008        mCameraDevice.setZoomChangeListener(null);
1009        mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1010        mCameraDevice = null;
1011        mPreviewing = false;
1012        mSnapshotInProgress = false;
1013        if (mFocusManager != null) {
1014            mFocusManager.onCameraReleased();
1015        }
1016    }
1017
1018    @Override
1019    public boolean onBackPressed() {
1020        if (mPaused) {
1021            return true;
1022        }
1023        if (mMediaRecorderRecording) {
1024            onStopVideoRecording();
1025            return true;
1026        } else {
1027            return false;
1028        }
1029    }
1030
1031    @Override
1032    public boolean onKeyDown(int keyCode, KeyEvent event) {
1033        // Do not handle any key if the activity is paused.
1034        if (mPaused) {
1035            return true;
1036        }
1037
1038        switch (keyCode) {
1039            case KeyEvent.KEYCODE_CAMERA:
1040                if (event.getRepeatCount() == 0) {
1041                    onShutterButtonClick();
1042                    return true;
1043                }
1044            case KeyEvent.KEYCODE_DPAD_CENTER:
1045                if (event.getRepeatCount() == 0) {
1046                    onShutterButtonClick();
1047                    return true;
1048                }
1049            case KeyEvent.KEYCODE_MENU:
1050                // Consume menu button presses during capture.
1051                return mMediaRecorderRecording;
1052        }
1053        return false;
1054    }
1055
1056    @Override
1057    public boolean onKeyUp(int keyCode, KeyEvent event) {
1058        switch (keyCode) {
1059            case KeyEvent.KEYCODE_CAMERA:
1060                onShutterButtonClick();
1061                return true;
1062            case KeyEvent.KEYCODE_MENU:
1063                // Consume menu button presses during capture.
1064                return mMediaRecorderRecording;
1065        }
1066        return false;
1067    }
1068
1069    @Override
1070    public boolean isVideoCaptureIntent() {
1071        String action = mActivity.getIntent().getAction();
1072        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
1073    }
1074
1075    private void doReturnToCaller(boolean valid) {
1076        Intent resultIntent = new Intent();
1077        int resultCode;
1078        if (valid) {
1079            resultCode = Activity.RESULT_OK;
1080            resultIntent.setData(mCurrentVideoUri);
1081            resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1082        } else {
1083            resultCode = Activity.RESULT_CANCELED;
1084        }
1085        mActivity.setResultEx(resultCode, resultIntent);
1086        mActivity.finish();
1087    }
1088
1089    private void cleanupEmptyFile() {
1090        if (mVideoFilename != null) {
1091            File f = new File(mVideoFilename);
1092            if (f.length() == 0 && f.delete()) {
1093                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
1094                mVideoFilename = null;
1095            }
1096        }
1097    }
1098
1099    // Prepares media recorder.
1100    private void initializeRecorder() {
1101        Log.i(TAG, "initializeRecorder: " + Thread.currentThread());
1102        // If the mCameraDevice is null, then this activity is going to finish
1103        if (mCameraDevice == null) {
1104            return;
1105        }
1106
1107        Intent intent = mActivity.getIntent();
1108        Bundle myExtras = intent.getExtras();
1109
1110        long requestedSizeLimit = 0;
1111        closeVideoFileDescriptor();
1112        mCurrentVideoUriFromMediaSaved = false;
1113        if (mIsVideoCaptureIntent && myExtras != null) {
1114            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1115            if (saveUri != null) {
1116                try {
1117                    mVideoFileDescriptor =
1118                            mContentResolver.openFileDescriptor(saveUri, "rw");
1119                    mCurrentVideoUri = saveUri;
1120                } catch (java.io.FileNotFoundException ex) {
1121                    // invalid uri
1122                    Log.e(TAG, ex.toString());
1123                }
1124            }
1125            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1126        }
1127        mMediaRecorder = new MediaRecorder();
1128        // Unlock the camera object before passing it to media recorder.
1129        mCameraDevice.unlock();
1130        mMediaRecorder.setCamera(mCameraDevice.getCamera());
1131        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1132        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1133        mMediaRecorder.setProfile(mProfile);
1134        mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
1135        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1136
1137        setRecordLocation();
1138
1139        // Set output file.
1140        // Try Uri in the intent first. If it doesn't exist, use our own
1141        // instead.
1142        if (mVideoFileDescriptor != null) {
1143            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1144        } else {
1145            generateVideoFilename(mProfile.fileFormat);
1146            mMediaRecorder.setOutputFile(mVideoFilename);
1147        }
1148
1149        // Set maximum file size.
1150        long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
1151        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1152            maxFileSize = requestedSizeLimit;
1153        }
1154
1155        try {
1156            mMediaRecorder.setMaxFileSize(maxFileSize);
1157        } catch (RuntimeException exception) {
1158            // We are going to ignore failure of setMaxFileSize here, as
1159            // a) The composer selected may simply not support it, or
1160            // b) The underlying media framework may not handle 64-bit range
1161            // on the size restriction.
1162        }
1163
1164        // See com.android.camera.cameradevice.CameraSettings.setPhotoRotationDegrees
1165        // for documentation.
1166        // Note that mOrientation here is the device orientation, which is the opposite of
1167        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1168        // which is the orientation the graphics need to rotate in order to render correctly.
1169        int rotation = 0;
1170        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1171            Characteristics info =
1172                    mActivity.getCameraProvider().getCharacteristics(mCameraId);
1173            if (isCameraFrontFacing()) {
1174                rotation = (info.getSensorOrientation() - mOrientation + 360) % 360;
1175            } else if (isCameraBackFacing()) {
1176                rotation = (info.getSensorOrientation() + mOrientation) % 360;
1177            } else {
1178                Log.e(TAG, "Camera is facing unhandled direction");
1179            }
1180        }
1181        mMediaRecorder.setOrientationHint(rotation);
1182
1183        try {
1184            mMediaRecorder.prepare();
1185        } catch (IOException e) {
1186            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1187            releaseMediaRecorder();
1188            throw new RuntimeException(e);
1189        }
1190
1191        mMediaRecorder.setOnErrorListener(this);
1192        mMediaRecorder.setOnInfoListener(this);
1193    }
1194
1195    private static void setCaptureRate(MediaRecorder recorder, double fps) {
1196        recorder.setCaptureRate(fps);
1197    }
1198
1199    private void setRecordLocation() {
1200        Location loc = mLocationManager.getCurrentLocation();
1201        if (loc != null) {
1202            mMediaRecorder.setLocation((float) loc.getLatitude(),
1203                    (float) loc.getLongitude());
1204        }
1205    }
1206
1207    private void releaseMediaRecorder() {
1208        Log.i(TAG, "Releasing media recorder.");
1209        if (mMediaRecorder != null) {
1210            cleanupEmptyFile();
1211            mMediaRecorder.reset();
1212            mMediaRecorder.release();
1213            mMediaRecorder = null;
1214        }
1215        mVideoFilename = null;
1216    }
1217
1218    private void generateVideoFilename(int outputFileFormat) {
1219        long dateTaken = System.currentTimeMillis();
1220        String title = createName(dateTaken);
1221        // Used when emailing.
1222        String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1223        String mime = convertOutputFormatToMimeType(outputFileFormat);
1224        String path = Storage.DIRECTORY + '/' + filename;
1225        String tmpPath = path + ".tmp";
1226        mCurrentVideoValues = new ContentValues(9);
1227        mCurrentVideoValues.put(Video.Media.TITLE, title);
1228        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1229        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1230        mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
1231        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1232        mCurrentVideoValues.put(Video.Media.DATA, path);
1233        mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
1234        mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
1235        mCurrentVideoValues.put(Video.Media.RESOLUTION,
1236                Integer.toString(mProfile.videoFrameWidth) + "x" +
1237                Integer.toString(mProfile.videoFrameHeight));
1238        Location loc = mLocationManager.getCurrentLocation();
1239        if (loc != null) {
1240            mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1241            mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1242        }
1243        mVideoFilename = tmpPath;
1244        Log.v(TAG, "New video filename: " + mVideoFilename);
1245    }
1246
1247    private void logVideoCapture(long duration) {
1248        String flashSetting = mActivity.getSettingsManager()
1249                .getString(mAppController.getCameraScope(),
1250                           Keys.KEY_VIDEOCAMERA_FLASH_MODE);
1251        boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
1252        int width = (Integer) mCurrentVideoValues.get(Video.Media.WIDTH);
1253        int height = (Integer) mCurrentVideoValues.get(Video.Media.HEIGHT);
1254        long size = new File(mCurrentVideoFilename).length();
1255        String name = new File(mCurrentVideoValues.getAsString(Video.Media.DATA)).getName();
1256        UsageStatistics.instance().videoCaptureDoneEvent(name, duration, isCameraFrontFacing(),
1257                currentZoomValue(), width, height, size, flashSetting, gridLinesOn);
1258    }
1259
1260    private void saveVideo() {
1261        if (mVideoFileDescriptor == null) {
1262            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1263            if (duration > 0) {
1264                //
1265            } else {
1266                Log.w(TAG, "Video duration <= 0 : " + duration);
1267            }
1268            mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length());
1269            mCurrentVideoValues.put(Video.Media.DURATION, duration);
1270            getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
1271                    mCurrentVideoValues, mOnVideoSavedListener, mContentResolver);
1272            logVideoCapture(duration);
1273        }
1274        mCurrentVideoValues = null;
1275    }
1276
1277    private void deleteVideoFile(String fileName) {
1278        Log.v(TAG, "Deleting video " + fileName);
1279        File f = new File(fileName);
1280        if (!f.delete()) {
1281            Log.v(TAG, "Could not delete " + fileName);
1282        }
1283    }
1284
1285    // from MediaRecorder.OnErrorListener
1286    @Override
1287    public void onError(MediaRecorder mr, int what, int extra) {
1288        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1289        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1290            // We may have run out of space on the sdcard.
1291            stopVideoRecording();
1292            mActivity.updateStorageSpaceAndHint(null);
1293        }
1294    }
1295
1296    // from MediaRecorder.OnInfoListener
1297    @Override
1298    public void onInfo(MediaRecorder mr, int what, int extra) {
1299        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1300            if (mMediaRecorderRecording) {
1301                onStopVideoRecording();
1302            }
1303        } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1304            if (mMediaRecorderRecording) {
1305                onStopVideoRecording();
1306            }
1307
1308            // Show the toast.
1309            Toast.makeText(mActivity, R.string.video_reach_size_limit,
1310                    Toast.LENGTH_LONG).show();
1311        }
1312    }
1313
1314    /*
1315     * Make sure we're not recording music playing in the background, ask the
1316     * MediaPlaybackService to pause playback.
1317     */
1318    private void pauseAudioPlayback() {
1319        AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1320        am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1321    }
1322
1323    // For testing.
1324    public boolean isRecording() {
1325        return mMediaRecorderRecording;
1326    }
1327
1328    private void startVideoRecording() {
1329        Log.i(TAG, "startVideoRecording: " + Thread.currentThread());
1330        mUI.cancelAnimations();
1331        mUI.setSwipingEnabled(false);
1332        mUI.showFocusUI(false);
1333        mUI.showVideoRecordingHints(false);
1334
1335        mActivity.updateStorageSpaceAndHint(new CameraActivity.OnStorageUpdateDoneListener() {
1336            @Override
1337            public void onStorageUpdateDone(long bytes) {
1338                if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1339                    Log.w(TAG, "Storage issue, ignore the start request");
1340                } else {
1341                    if (mCameraDevice == null) {
1342                        Log.v(TAG, "in storage callback after camera closed");
1343                        return;
1344                    }
1345                    if (mPaused == true) {
1346                        Log.v(TAG, "in storage callback after module paused");
1347                        return;
1348                    }
1349
1350                    // Monkey is so fast so it could trigger startVideoRecording twice. To prevent
1351                    // app crash (b/17313985), do nothing here for the second storage-checking
1352                    // callback because recording is already started.
1353                    if (mMediaRecorderRecording) {
1354                        Log.v(TAG, "in storage callback after recording started");
1355                        return;
1356                    }
1357
1358                    mCurrentVideoUri = null;
1359
1360                    initializeRecorder();
1361                    if (mMediaRecorder == null) {
1362                        Log.e(TAG, "Fail to initialize media recorder");
1363                        return;
1364                    }
1365
1366                    pauseAudioPlayback();
1367
1368                    try {
1369                        mMediaRecorder.start(); // Recording is now started
1370                    } catch (RuntimeException e) {
1371                        Log.e(TAG, "Could not start media recorder. ", e);
1372                        releaseMediaRecorder();
1373                        // If start fails, frameworks will not lock the camera for us.
1374                        mCameraDevice.lock();
1375                        return;
1376                    }
1377                    mAppController.getCameraAppUI().setSwipeEnabled(false);
1378
1379                    // The parameters might have been altered by MediaRecorder already.
1380                    // We need to force mCameraDevice to refresh before getting it.
1381                    mCameraDevice.refreshSettings();
1382                    // The parameters may have been changed by MediaRecorder upon starting
1383                    // recording. We need to alter the parameters if we support camcorder
1384                    // zoom. To reduce latency when setting the parameters during zoom, we
1385                    // update the settings here once.
1386                    mCameraSettings = mCameraDevice.getSettings();
1387
1388                    mMediaRecorderRecording = true;
1389                    mActivity.lockOrientation();
1390                    mRecordingStartTime = SystemClock.uptimeMillis();
1391
1392                    // A special case of mode options closing: during capture it should
1393                    // not be possible to change mode state.
1394                    mAppController.getCameraAppUI().hideModeOptions();
1395                    mAppController.getCameraAppUI().animateBottomBarToVideoStop(R.drawable.ic_stop);
1396                    mUI.showRecordingUI(true);
1397
1398                    setFocusParameters();
1399
1400                    updateRecordingTime();
1401                    mActivity.enableKeepScreenOn(true);
1402                }
1403            }
1404        });
1405    }
1406
1407    private Bitmap getVideoThumbnail() {
1408        Bitmap bitmap = null;
1409        if (mVideoFileDescriptor != null) {
1410            bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
1411                    mDesiredPreviewWidth);
1412        } else if (mCurrentVideoUri != null) {
1413            try {
1414                mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1415                bitmap = Thumbnail.createVideoThumbnailBitmap(
1416                        mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1417            } catch (java.io.FileNotFoundException ex) {
1418                // invalid uri
1419                Log.e(TAG, ex.toString());
1420            }
1421        }
1422
1423        if (bitmap != null) {
1424            // MetadataRetriever already rotates the thumbnail. We should rotate
1425            // it to match the UI orientation (and mirror if it is front-facing camera).
1426            bitmap = CameraUtil.rotateAndMirror(bitmap, 0, isCameraFrontFacing());
1427        }
1428        return bitmap;
1429    }
1430
1431    private void showCaptureResult() {
1432        mIsInReviewMode = true;
1433        Bitmap bitmap = getVideoThumbnail();
1434        if (bitmap != null) {
1435            mUI.showReviewImage(bitmap);
1436        }
1437        mUI.showReviewControls();
1438    }
1439
1440    private boolean stopVideoRecording() {
1441        // Do nothing if camera device is still capturing photo. Monkey test can trigger app crashes
1442        // (b/17313985) without this check. Crash could also be reproduced by continuously tapping
1443        // on shutter button and preview with two fingers.
1444        if (mSnapshotInProgress) {
1445            Log.v(TAG, "Skip stopVideoRecording since snapshot in progress");
1446            return true;
1447        }
1448        Log.v(TAG, "stopVideoRecording");
1449
1450        mUI.setSwipingEnabled(true);
1451        mUI.showFocusUI(true);
1452        mUI.showVideoRecordingHints(true);
1453
1454        boolean fail = false;
1455        if (mMediaRecorderRecording) {
1456            boolean shouldAddToMediaStoreNow = false;
1457
1458            try {
1459                mMediaRecorder.setOnErrorListener(null);
1460                mMediaRecorder.setOnInfoListener(null);
1461                mMediaRecorder.stop();
1462                shouldAddToMediaStoreNow = true;
1463                mCurrentVideoFilename = mVideoFilename;
1464                Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename);
1465            } catch (RuntimeException e) {
1466                Log.e(TAG, "stop fail",  e);
1467                if (mVideoFilename != null) {
1468                    deleteVideoFile(mVideoFilename);
1469                }
1470                fail = true;
1471            }
1472            mMediaRecorderRecording = false;
1473            mActivity.unlockOrientation();
1474
1475            // If the activity is paused, this means activity is interrupted
1476            // during recording. Release the camera as soon as possible because
1477            // face unlock or other applications may need to use the camera.
1478            if (mPaused) {
1479                // b/16300704: Monkey is fast so it could pause the module while recording.
1480                // stopPreview should definitely be called before switching off.
1481                stopPreview();
1482
1483                closeCamera();
1484            }
1485
1486            mUI.showRecordingUI(false);
1487            // The orientation was fixed during video recording. Now make it
1488            // reflect the device orientation as video recording is stopped.
1489            mUI.setOrientationIndicator(0, true);
1490            mActivity.enableKeepScreenOn(false);
1491            if (shouldAddToMediaStoreNow && !fail) {
1492                if (mVideoFileDescriptor == null) {
1493                    saveVideo();
1494                } else if (mIsVideoCaptureIntent) {
1495                    // if no file save is needed, we can show the post capture UI now
1496                    showCaptureResult();
1497                }
1498            }
1499        }
1500        // release media recorder
1501        releaseMediaRecorder();
1502
1503        mAppController.getCameraAppUI().showModeOptions();
1504        mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
1505        if (!mPaused && mCameraDevice != null) {
1506            setFocusParameters();
1507            mCameraDevice.lock();
1508            if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1509                stopPreview();
1510                // Switch back to use SurfaceTexture for preview.
1511                startPreview();
1512            }
1513            // Update the parameters here because the parameters might have been altered
1514            // by MediaRecorder.
1515            mCameraSettings = mCameraDevice.getSettings();
1516        }
1517
1518        // Check this in advance of each shot so we don't add to shutter
1519        // latency. It's true that someone else could write to the SD card
1520        // in the mean time and fill it, but that could have happened
1521        // between the shutter press and saving the file too.
1522        mActivity.updateStorageSpaceAndHint(null);
1523
1524        return fail;
1525    }
1526
1527    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1528        long seconds = milliSeconds / 1000; // round down to compute seconds
1529        long minutes = seconds / 60;
1530        long hours = minutes / 60;
1531        long remainderMinutes = minutes - (hours * 60);
1532        long remainderSeconds = seconds - (minutes * 60);
1533
1534        StringBuilder timeStringBuilder = new StringBuilder();
1535
1536        // Hours
1537        if (hours > 0) {
1538            if (hours < 10) {
1539                timeStringBuilder.append('0');
1540            }
1541            timeStringBuilder.append(hours);
1542
1543            timeStringBuilder.append(':');
1544        }
1545
1546        // Minutes
1547        if (remainderMinutes < 10) {
1548            timeStringBuilder.append('0');
1549        }
1550        timeStringBuilder.append(remainderMinutes);
1551        timeStringBuilder.append(':');
1552
1553        // Seconds
1554        if (remainderSeconds < 10) {
1555            timeStringBuilder.append('0');
1556        }
1557        timeStringBuilder.append(remainderSeconds);
1558
1559        // Centi seconds
1560        if (displayCentiSeconds) {
1561            timeStringBuilder.append('.');
1562            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1563            if (remainderCentiSeconds < 10) {
1564                timeStringBuilder.append('0');
1565            }
1566            timeStringBuilder.append(remainderCentiSeconds);
1567        }
1568
1569        return timeStringBuilder.toString();
1570    }
1571
1572    private void updateRecordingTime() {
1573        if (!mMediaRecorderRecording) {
1574            return;
1575        }
1576        long now = SystemClock.uptimeMillis();
1577        long delta = now - mRecordingStartTime;
1578
1579        // Starting a minute before reaching the max duration
1580        // limit, we'll countdown the remaining time instead.
1581        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1582                && delta >= mMaxVideoDurationInMs - 60000);
1583
1584        long deltaAdjusted = delta;
1585        if (countdownRemainingTime) {
1586            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1587        }
1588        String text;
1589
1590        long targetNextUpdateDelay;
1591
1592        text = millisecondToTimeString(deltaAdjusted, false);
1593        targetNextUpdateDelay = 1000;
1594
1595        mUI.setRecordingTime(text);
1596
1597        if (mRecordingTimeCountsDown != countdownRemainingTime) {
1598            // Avoid setting the color on every update, do it only
1599            // when it needs changing.
1600            mRecordingTimeCountsDown = countdownRemainingTime;
1601
1602            int color = mActivity.getResources().getColor(R.color.recording_time_remaining_text);
1603
1604            mUI.setRecordingTimeTextColor(color);
1605        }
1606
1607        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1608        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
1609    }
1610
1611    private static boolean isSupported(String value, List<String> supported) {
1612        return supported == null ? false : supported.indexOf(value) >= 0;
1613    }
1614
1615    @SuppressWarnings("deprecation")
1616    private void setCameraParameters() {
1617        SettingsManager settingsManager = mActivity.getSettingsManager();
1618
1619        // Update Desired Preview size in case video camera resolution has changed.
1620        updateDesiredPreviewSize();
1621
1622        mCameraSettings.setPreviewSize(new Size(mDesiredPreviewWidth, mDesiredPreviewHeight));
1623        // This is required for Samsung SGH-I337 and probably other Samsung S4 versions
1624        if (Build.BRAND.toLowerCase().contains("samsung")) {
1625            mCameraSettings.setSetting("video-size",
1626                    mProfile.videoFrameWidth + "x" + mProfile.videoFrameHeight);
1627        }
1628        int[] fpsRange =
1629                CameraUtil.getMaxPreviewFpsRange(mCameraCapabilities.getSupportedPreviewFpsRange());
1630        if (fpsRange.length > 0) {
1631            mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
1632        } else {
1633            mCameraSettings.setPreviewFrameRate(mProfile.videoFrameRate);
1634        }
1635
1636        enableTorchMode(Keys.isCameraBackFacing(settingsManager, mAppController.getModuleScope()));
1637
1638        // Set zoom.
1639        if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1640            mCameraSettings.setZoomRatio(mZoomValue);
1641        }
1642        updateFocusParameters();
1643
1644        mCameraSettings.setRecordingHintEnabled(true);
1645
1646        if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
1647            mCameraSettings.setVideoStabilization(true);
1648        }
1649
1650        // Set picture size.
1651        // The logic here is different from the logic in still-mode camera.
1652        // There we determine the preview size based on the picture size, but
1653        // here we determine the picture size based on the preview size.
1654        List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
1655        Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
1656                mDesiredPreviewWidth, mDesiredPreviewHeight);
1657        Size original = new Size(mCameraSettings.getCurrentPhotoSize());
1658        if (!original.equals(optimalSize)) {
1659            mCameraSettings.setPhotoSize(optimalSize);
1660        }
1661        Log.d(TAG, "Video snapshot size is " + optimalSize);
1662
1663        // Set JPEG quality.
1664        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1665                CameraProfile.QUALITY_HIGH);
1666        mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
1667
1668        if (mCameraDevice != null) {
1669            mCameraDevice.applySettings(mCameraSettings);
1670            // Nexus 5 through KitKat 4.4.2 requires a second call to
1671            // .setParameters() for frame rate settings to take effect.
1672            mCameraDevice.applySettings(mCameraSettings);
1673        }
1674
1675        // Update UI based on the new parameters.
1676        mUI.updateOnScreenIndicators(mCameraSettings);
1677    }
1678
1679    private void updateFocusParameters() {
1680        // Set continuous autofocus. During recording, we use "continuous-video"
1681        // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
1682        // before recording starts) we use "continuous-picture" auto focus mode
1683        // for faster but slightly jittery focusing.
1684        Set<CameraCapabilities.FocusMode> supportedFocus = mCameraCapabilities
1685                .getSupportedFocusModes();
1686        if (mMediaRecorderRecording) {
1687            if (mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO)) {
1688                mCameraSettings.setFocusMode(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO);
1689                mFocusManager.overrideFocusMode(CameraCapabilities.FocusMode.CONTINUOUS_VIDEO);
1690            } else {
1691                mFocusManager.overrideFocusMode(null);
1692            }
1693        } else {
1694            // FIXME(b/16984793): This is broken. For some reasons, CONTINUOUS_PICTURE is not on
1695            // when preview starts.
1696            mFocusManager.overrideFocusMode(null);
1697            if (mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE)) {
1698                mCameraSettings.setFocusMode(
1699                        mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
1700                if (mFocusAreaSupported) {
1701                    mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
1702                }
1703            }
1704        }
1705        updateAutoFocusMoveCallback();
1706    }
1707
1708    @Override
1709    public void resume() {
1710        if (isVideoCaptureIntent()) {
1711            mDontResetIntentUiOnResume = mPaused;
1712        }
1713
1714        mPaused = false;
1715        installIntentFilter();
1716        mAppController.setShutterEnabled(false);
1717        mZoomValue = 1.0f;
1718
1719        showVideoSnapshotUI(false);
1720
1721        if (!mPreviewing) {
1722            requestCamera(mCameraId);
1723        } else {
1724            // preview already started
1725            mAppController.setShutterEnabled(true);
1726        }
1727
1728        if (mFocusManager != null) {
1729            // If camera is not open when resume is called, focus manager will not
1730            // be initialized yet, in which case it will start listening to
1731            // preview area size change later in the initialization.
1732            mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1733        }
1734
1735        if (mPreviewing) {
1736            mOnResumeTime = SystemClock.uptimeMillis();
1737            mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
1738        }
1739        getServices().getMemoryManager().addListener(this);
1740    }
1741
1742    @Override
1743    public void pause() {
1744        mPaused = true;
1745
1746        if (mFocusManager != null) {
1747            // If camera is not open when resume is called, focus manager will not
1748            // be initialized yet, in which case it will start listening to
1749            // preview area size change later in the initialization.
1750            mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1751            mFocusManager.removeMessages();
1752        }
1753        if (mMediaRecorderRecording) {
1754            // Camera will be released in onStopVideoRecording.
1755            onStopVideoRecording();
1756        } else {
1757            stopPreview();
1758            closeCamera();
1759            releaseMediaRecorder();
1760        }
1761
1762        closeVideoFileDescriptor();
1763
1764        if (mReceiver != null) {
1765            mActivity.unregisterReceiver(mReceiver);
1766            mReceiver = null;
1767        }
1768
1769        mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1770        mHandler.removeMessages(MSG_SWITCH_CAMERA);
1771        mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
1772        mPendingSwitchCameraId = -1;
1773        mSwitchingCamera = false;
1774        mPreferenceRead = false;
1775        getServices().getMemoryManager().removeListener(this);
1776        mUI.onPause();
1777    }
1778
1779    @Override
1780    public void destroy() {
1781
1782    }
1783
1784    @Override
1785    public void onLayoutOrientationChanged(boolean isLandscape) {
1786        setDisplayOrientation();
1787    }
1788
1789    // TODO: integrate this into the SettingsManager listeners.
1790    public void onSharedPreferenceChanged() {
1791
1792    }
1793
1794    private void switchCamera() {
1795        if (mPaused)  {
1796            return;
1797        }
1798        SettingsManager settingsManager = mActivity.getSettingsManager();
1799
1800        Log.d(TAG, "Start to switch camera.");
1801        mCameraId = mPendingSwitchCameraId;
1802        mPendingSwitchCameraId = -1;
1803        settingsManager.set(mAppController.getModuleScope(),
1804                            Keys.KEY_CAMERA_ID, mCameraId);
1805
1806        if (mFocusManager != null) {
1807            mFocusManager.removeMessages();
1808        }
1809        closeCamera();
1810        requestCamera(mCameraId);
1811
1812        mMirror = isCameraFrontFacing();
1813        if (mFocusManager != null) {
1814            mFocusManager.setMirror(mMirror);
1815        }
1816
1817        // From onResume
1818        mZoomValue = 1.0f;
1819        mUI.setOrientationIndicator(0, false);
1820
1821        // Start switch camera animation. Post a message because
1822        // onFrameAvailable from the old camera may already exist.
1823        mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
1824        mUI.updateOnScreenIndicators(mCameraSettings);
1825    }
1826
1827    private void initializeVideoSnapshot() {
1828        if (mCameraSettings == null) {
1829            return;
1830        }
1831    }
1832
1833    void showVideoSnapshotUI(boolean enabled) {
1834        if (mCameraSettings == null) {
1835            return;
1836        }
1837        if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_SNAPSHOT) &&
1838                !mIsVideoCaptureIntent) {
1839            if (enabled) {
1840                mUI.animateFlash();
1841            } else {
1842                mUI.showPreviewBorder(enabled);
1843            }
1844            mAppController.setShutterEnabled(!enabled);
1845        }
1846    }
1847
1848    /**
1849     * Used to update the flash mode. Video mode can turn on the flash as torch
1850     * mode, which we would like to turn on and off when we switching in and
1851     * out to the preview.
1852     *
1853     * @param enable Whether torch mode can be enabled.
1854     */
1855    private void enableTorchMode(boolean enable) {
1856        if (mCameraSettings.getCurrentFlashMode() == null) {
1857            return;
1858        }
1859
1860        SettingsManager settingsManager = mActivity.getSettingsManager();
1861
1862        CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1863        CameraCapabilities.FlashMode flashMode;
1864        if (enable) {
1865            flashMode = stringifier
1866                .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
1867                                                               Keys.KEY_VIDEOCAMERA_FLASH_MODE));
1868        } else {
1869            flashMode = CameraCapabilities.FlashMode.OFF;
1870        }
1871        if (mCameraCapabilities.supports(flashMode)) {
1872            mCameraSettings.setFlashMode(flashMode);
1873        }
1874        /* TODO: Find out how to deal with the following code piece:
1875        else {
1876            flashMode = mCameraSettings.getCurrentFlashMode();
1877            if (flashMode == null) {
1878                flashMode = mActivity.getString(
1879                        R.string.pref_camera_flashmode_no_flash);
1880                mParameters.setFlashMode(flashMode);
1881            }
1882        }*/
1883        if (mCameraDevice != null) {
1884            mCameraDevice.applySettings(mCameraSettings);
1885        }
1886        mUI.updateOnScreenIndicators(mCameraSettings);
1887    }
1888
1889    @Override
1890    public void onPreviewVisibilityChanged(int visibility) {
1891        if (mPreviewing) {
1892            enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
1893        }
1894    }
1895
1896    private final class JpegPictureCallback implements CameraPictureCallback {
1897        Location mLocation;
1898
1899        public JpegPictureCallback(Location loc) {
1900            mLocation = loc;
1901        }
1902
1903        @Override
1904        public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
1905            Log.i(TAG, "Video snapshot taken.");
1906            mSnapshotInProgress = false;
1907            showVideoSnapshotUI(false);
1908            storeImage(jpegData, mLocation);
1909        }
1910    }
1911
1912    private void storeImage(final byte[] data, Location loc) {
1913        long dateTaken = System.currentTimeMillis();
1914        String title = CameraUtil.createJpegName(dateTaken);
1915        ExifInterface exif = Exif.getExif(data);
1916        int orientation = Exif.getOrientation(exif);
1917
1918        String flashSetting = mActivity.getSettingsManager()
1919            .getString(mAppController.getCameraScope(), Keys.KEY_VIDEOCAMERA_FLASH_MODE);
1920        Boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
1921        UsageStatistics.instance().photoCaptureDoneEvent(
1922                eventprotos.NavigationChange.Mode.VIDEO_STILL, title + ".jpeg", exif,
1923                isCameraFrontFacing(), false, currentZoomValue(), flashSetting, gridLinesOn,
1924                null, null, null);
1925
1926        getServices().getMediaSaver().addImage(
1927                data, title, dateTaken, loc, orientation,
1928                exif, mOnPhotoSavedListener, mContentResolver);
1929    }
1930
1931    private String convertOutputFormatToMimeType(int outputFileFormat) {
1932        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1933            return "video/mp4";
1934        }
1935        return "video/3gpp";
1936    }
1937
1938    private String convertOutputFormatToFileExt(int outputFileFormat) {
1939        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1940            return ".mp4";
1941        }
1942        return ".3gp";
1943    }
1944
1945    private void closeVideoFileDescriptor() {
1946        if (mVideoFileDescriptor != null) {
1947            try {
1948                mVideoFileDescriptor.close();
1949            } catch (IOException e) {
1950                Log.e(TAG, "Fail to close fd", e);
1951            }
1952            mVideoFileDescriptor = null;
1953        }
1954    }
1955
1956    @Override
1957    public void onPreviewUIReady() {
1958        startPreview();
1959    }
1960
1961    @Override
1962    public void onPreviewUIDestroyed() {
1963        stopPreview();
1964    }
1965
1966    @Override
1967    public void startPreCaptureAnimation() {
1968        mAppController.startPreCaptureAnimation();
1969    }
1970
1971    private void requestCamera(int id) {
1972        mActivity.getCameraProvider().requestCamera(id);
1973    }
1974
1975    @Override
1976    public void onMemoryStateChanged(int state) {
1977        mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
1978    }
1979
1980    @Override
1981    public void onLowMemory() {
1982        // Not much we can do in the video module.
1983    }
1984
1985    /***********************FocusOverlayManager Listener****************************/
1986    @Override
1987    public void autoFocus() {
1988        if (mCameraDevice != null) {
1989            mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1990        }
1991    }
1992
1993    @Override
1994    public void cancelAutoFocus() {
1995        if (mCameraDevice != null) {
1996            mCameraDevice.cancelAutoFocus();
1997            setFocusParameters();
1998        }
1999    }
2000
2001    @Override
2002    public boolean capture() {
2003        return false;
2004    }
2005
2006    @Override
2007    public void startFaceDetection() {
2008
2009    }
2010
2011    @Override
2012    public void stopFaceDetection() {
2013
2014    }
2015
2016    @Override
2017    public void setFocusParameters() {
2018        if (mCameraDevice != null) {
2019            updateFocusParameters();
2020            mCameraDevice.applySettings(mCameraSettings);
2021        }
2022    }
2023}
2024