OSDN Git Service

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