OSDN Git Service

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