OSDN Git Service

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