OSDN Git Service

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