OSDN Git Service

Generalize shutter button listeners and add one for mode options.
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / VideoModule.java
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.camera;
18
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.ActivityNotFoundException;
22 import android.content.BroadcastReceiver;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.graphics.Bitmap;
29 import android.graphics.SurfaceTexture;
30 import android.hardware.Camera.CameraInfo;
31 import android.hardware.Camera.Parameters;
32 import android.hardware.Camera.Size;
33 import android.location.Location;
34 import android.media.AudioManager;
35 import android.media.CamcorderProfile;
36 import android.media.CameraProfile;
37 import android.media.MediaRecorder;
38 import android.net.Uri;
39 import android.os.Build;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.Message;
43 import android.os.ParcelFileDescriptor;
44 import android.os.SystemClock;
45 import android.provider.MediaStore;
46 import android.provider.MediaStore.MediaColumns;
47 import android.provider.MediaStore.Video;
48 import android.util.Log;
49 import android.view.KeyEvent;
50 import android.view.OrientationEventListener;
51 import android.view.View;
52 import android.widget.Toast;
53
54 import com.android.camera.app.AppController;
55 import com.android.camera.app.CameraAppUI;
56 import com.android.camera.app.CameraManager;
57 import com.android.camera.app.CameraManager.CameraPictureCallback;
58 import com.android.camera.app.CameraManager.CameraProxy;
59 import com.android.camera.app.LocationManager;
60 import com.android.camera.app.MediaSaver;
61 import com.android.camera.app.MemoryManager;
62 import com.android.camera.app.MemoryManager.MemoryListener;
63 import com.android.camera.exif.ExifInterface;
64 import com.android.camera.hardware.HardwareSpec;
65 import com.android.camera.hardware.HardwareSpecImpl;
66 import com.android.camera.module.ModuleController;
67 import com.android.camera.settings.SettingsManager;
68 import com.android.camera.settings.SettingsUtil;
69 import com.android.camera.util.AccessibilityUtils;
70 import com.android.camera.util.ApiHelper;
71 import com.android.camera.util.CameraUtil;
72 import com.android.camera.util.UsageStatistics;
73 import com.android.camera2.R;
74 import com.google.common.logging.eventprotos;
75
76 import java.io.File;
77 import java.io.IOException;
78 import java.text.SimpleDateFormat;
79 import java.util.Date;
80 import java.util.Iterator;
81 import java.util.List;
82
83 public class VideoModule extends CameraModule
84     implements ModuleController,
85     VideoController,
86     MemoryListener,
87     MediaRecorder.OnErrorListener,
88     MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
89
90     private static final String TAG = "VideoModule";
91
92     // Messages defined for the UI thread handler.
93     private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
94     private static final int MSG_UPDATE_RECORD_TIME = 5;
95     private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
96     private static final int MSG_SWITCH_CAMERA = 8;
97     private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
98
99     private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
100
101     /**
102      * An unpublished intent flag requesting to start recording straight away
103      * and return as soon as recording is stopped.
104      * TODO: consider publishing by moving into MediaStore.
105      */
106     private static final String EXTRA_QUICK_CAPTURE =
107             "android.intent.extra.quickCapture";
108
109     // module fields
110     private CameraActivity mActivity;
111     private boolean mPaused;
112     private int mCameraId;
113     private Parameters mParameters;
114
115     private boolean mIsInReviewMode;
116     private boolean mSnapshotInProgress = false;
117
118     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
119
120     // Preference must be read before starting preview. We check this before starting
121     // preview.
122     private boolean mPreferenceRead;
123
124     private boolean mIsVideoCaptureIntent;
125     private boolean mQuickCapture;
126
127     private MediaRecorder mMediaRecorder;
128
129     private boolean mSwitchingCamera;
130     private boolean mMediaRecorderRecording = false;
131     private long mRecordingStartTime;
132     private boolean mRecordingTimeCountsDown = false;
133     private long mOnResumeTime;
134     // The video file that the hardware camera is about to record into
135     // (or is recording into.
136     private String mVideoFilename;
137     private ParcelFileDescriptor mVideoFileDescriptor;
138
139     // The video file that has already been recorded, and that is being
140     // examined by the user.
141     private String mCurrentVideoFilename;
142     private Uri mCurrentVideoUri;
143     private boolean mCurrentVideoUriFromMediaSaved;
144     private ContentValues mCurrentVideoValues;
145
146     private CamcorderProfile mProfile;
147
148     // The video duration limit. 0 means no limit.
149     private int mMaxVideoDurationInMs;
150
151     // Time Lapse parameters.
152     private boolean mCaptureTimeLapse = false;
153     // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
154     private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
155
156     boolean mPreviewing = false; // True if preview is started.
157     // The display rotation in degrees. This is only valid when mPreviewing is
158     // true.
159     private int mDisplayRotation;
160     private int mCameraDisplayOrientation;
161     private AppController mAppController;
162
163     private int mDesiredPreviewWidth;
164     private int mDesiredPreviewHeight;
165     private ContentResolver mContentResolver;
166
167     private LocationManager mLocationManager;
168
169     private int mPendingSwitchCameraId;
170     private final Handler mHandler = new MainHandler();
171     private VideoUI mUI;
172     private CameraProxy mCameraDevice;
173
174     // The degrees of the device rotated clockwise from its natural orientation.
175     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
176
177     private int mZoomValue;  // The current zoom value.
178
179     private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
180             new MediaSaver.OnMediaSavedListener() {
181                 @Override
182                 public void onMediaSaved(Uri uri) {
183                     if (uri != null) {
184                         mCurrentVideoUri = uri;
185                         mCurrentVideoUriFromMediaSaved = true;
186                         onVideoSaved();
187                         mActivity.notifyNewMedia(uri);
188                     }
189                 }
190             };
191
192     private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
193             new MediaSaver.OnMediaSavedListener() {
194                 @Override
195                 public void onMediaSaved(Uri uri) {
196                     if (uri != null) {
197                         mActivity.notifyNewMedia(uri);
198                     }
199                 }
200             };
201     private FocusOverlayManager mFocusManager;
202     private boolean mMirror;
203     private Parameters mInitialParams;
204     private boolean mFocusAreaSupported;
205     private boolean mMeteringAreaSupported;
206
207     private final CameraManager.CameraAFCallback mAutoFocusCallback =
208             new CameraManager.CameraAFCallback() {
209         @Override
210         public void onAutoFocus(boolean focused, CameraProxy camera) {
211             if (mPaused) {
212                 return;
213             }
214             mFocusManager.onAutoFocus(focused, false);
215         }
216     };
217
218     private final Object mAutoFocusMoveCallback =
219             ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
220                     ? new CameraManager.CameraAFMoveCallback() {
221                 @Override
222                 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
223                     mFocusManager.onAutoFocusMoving(moving);
224                 }
225             } : null;
226
227     /**
228      * This Handler is used to post message back onto the main thread of the
229      * application.
230      */
231     private class MainHandler extends Handler {
232         @Override
233         public void handleMessage(Message msg) {
234             switch (msg.what) {
235
236                 case MSG_ENABLE_SHUTTER_BUTTON:
237                     mUI.enableShutter(true);
238                     break;
239
240                 case MSG_UPDATE_RECORD_TIME: {
241                     updateRecordingTime();
242                     break;
243                 }
244
245                 case MSG_CHECK_DISPLAY_ROTATION: {
246                     // Restart the preview if display rotation has changed.
247                     // Sometimes this happens when the device is held upside
248                     // down and camera app is opened. Rotation animation will
249                     // take some time and the rotation value we have got may be
250                     // wrong. Framework does not have a callback for this now.
251                     if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
252                             && !mMediaRecorderRecording && !mSwitchingCamera) {
253                         startPreview();
254                     }
255                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
256                         mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
257                     }
258                     break;
259                 }
260
261                 case MSG_SWITCH_CAMERA: {
262                     switchCamera();
263                     break;
264                 }
265
266                 case MSG_SWITCH_CAMERA_START_ANIMATION: {
267                     //TODO:
268                     //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
269
270                     // Enable all camera controls.
271                     mSwitchingCamera = false;
272                     break;
273                 }
274
275                 default:
276                     Log.v(TAG, "Unhandled message: " + msg.what);
277                     break;
278             }
279         }
280     }
281
282     private BroadcastReceiver mReceiver = null;
283
284     /** Whether shutter is enabled. */
285     private boolean mShutterEnabled;
286
287     private class MyBroadcastReceiver extends BroadcastReceiver {
288         @Override
289         public void onReceive(Context context, Intent intent) {
290             String action = intent.getAction();
291             if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
292                 stopVideoRecording();
293             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
294                 Toast.makeText(mActivity,
295                         mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
296             }
297         }
298     }
299
300     private int mShutterIconId;
301
302
303     /**
304      * Construct a new video module.
305      */
306     public VideoModule(AppController app) {
307         super(app);
308     }
309
310     private String createName(long dateTaken) {
311         Date date = new Date(dateTaken);
312         SimpleDateFormat dateFormat = new SimpleDateFormat(
313                 mActivity.getString(R.string.video_file_name_format));
314
315         return dateFormat.format(date);
316     }
317
318     @Override
319     public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
320         mActivity = activity;
321         // TODO: Need to look at the controller interface to see if we can get
322         // rid of passing in the activity directly.
323         mAppController = mActivity;
324         mUI = new VideoUI(mActivity, this,  mActivity.getModuleLayoutRoot());
325         mActivity.setPreviewStatusListener(mUI);
326
327         SettingsManager settingsManager = mActivity.getSettingsManager();
328         mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
329
330         /*
331          * To reduce startup time, we start the preview in another thread.
332          * We make sure the preview is started at the end of onCreate.
333          */
334         requestCamera(mCameraId);
335
336         mContentResolver = mActivity.getContentResolver();
337
338         // Surface texture is from camera screen nail and startPreview needs it.
339         // This must be done before startPreview.
340         mIsVideoCaptureIntent = isVideoCaptureIntent();
341
342         mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
343         mLocationManager = mActivity.getLocationManager();
344
345         mUI.setOrientationIndicator(0, false);
346         setDisplayOrientation();
347
348         mUI.showTimeLapseUI(mCaptureTimeLapse);
349         mPendingSwitchCameraId = -1;
350
351         mShutterIconId = CameraUtil.getCameraShutterIconId(
352                 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
353
354     }
355
356     @Override
357     public boolean isUsingBottomBar() {
358         return true;
359     }
360
361     private void initializeControlByIntent() {
362         if (isVideoCaptureIntent()) {
363             mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
364         }
365     }
366
367     @Override
368     public void onSingleTapUp(View view, int x, int y) {
369         if (mPaused || mCameraDevice == null) {
370             return;
371         }
372         // Check if metering area or focus area is supported.
373         if (!mFocusAreaSupported && !mMeteringAreaSupported) {
374             return;
375         }
376         // Tap to focus.
377         mFocusManager.onSingleTapUp(x, y);
378     }
379
380     private void takeASnapshot() {
381         // Only take snapshots if video snapshot is supported by device
382         if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
383             if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress || mShutterEnabled) {
384                 return;
385             }
386
387             // Set rotation and gps data.
388             CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
389             int rotation = CameraUtil.getJpegRotation(info, mOrientation);
390             mParameters.setRotation(rotation);
391             Location loc = mLocationManager.getCurrentLocation();
392             CameraUtil.setGpsParameters(mParameters, loc);
393             mCameraDevice.setParameters(mParameters);
394
395             Log.v(TAG, "Video snapshot start");
396             mCameraDevice.takePicture(mHandler,
397                     null, null, null, new JpegPictureCallback(loc));
398             showVideoSnapshotUI(true);
399             mSnapshotInProgress = true;
400             UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.VIDEO_STILL,
401                     null, null);
402         }
403     }
404
405     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
406      private void updateAutoFocusMoveCallback() {
407         if (mPaused) {
408             return;
409         }
410
411         if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
412             mCameraDevice.setAutoFocusMoveCallback(mHandler,
413                     (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
414         } else {
415             mCameraDevice.setAutoFocusMoveCallback(null, null);
416         }
417     }
418
419     /**
420      * The focus manager gets initialized after camera is available.
421      */
422     private void initializeFocusManager() {
423         // Create FocusManager object. startPreview needs it.
424         // if mFocusManager not null, reuse it
425         // otherwise create a new instance
426         if (mFocusManager != null) {
427             mFocusManager.removeMessages();
428         } else {
429             CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
430             mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
431             String[] defaultFocusModes = mActivity.getResources().getStringArray(
432                     R.array.pref_camera_focusmode_default_array);
433             mFocusManager = new FocusOverlayManager(mActivity.getSettingsManager(),
434                     defaultFocusModes,
435                     mInitialParams, this, mMirror,
436                     mActivity.getMainLooper(), mUI.getFocusUI());
437         }
438         mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
439     }
440
441     @Override
442     public void onOrientationChanged(int orientation) {
443         // We keep the last known orientation. So if the user first orient
444         // the camera then point the camera to floor or sky, we still have
445         // the correct orientation.
446         if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
447             return;
448         }
449         int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
450
451         if (mOrientation != newOrientation) {
452             mOrientation = newOrientation;
453         }
454
455     }
456
457     private final ButtonManager.ButtonCallback mFlashCallback =
458         new ButtonManager.ButtonCallback() {
459             @Override
460             public void onStateChanged(int state) {
461                 // Update flash parameters.
462                 enableTorchMode(true);
463             }
464         };
465
466     private final ButtonManager.ButtonCallback mCameraCallback =
467         new ButtonManager.ButtonCallback() {
468             @Override
469             public void onStateChanged(int state) {
470                 if (mPaused || mPendingSwitchCameraId != -1) {
471                     return;
472                 }
473                 mPendingSwitchCameraId = state;
474                 Log.d(TAG, "Start to copy texture.");
475
476                 // Disable all camera controls.
477                 mSwitchingCamera = true;
478                 switchCamera();
479             }
480         };
481
482     private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
483         @Override
484         public void onClick(View v) {
485             onReviewCancelClicked(v);
486         }
487     };
488
489     private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
490         @Override
491         public void onClick(View v) {
492             onReviewDoneClicked(v);
493         }
494     };
495     private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
496         @Override
497         public void onClick(View v) {
498             mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
499             onReviewPlayClicked(v);
500         }
501     };
502
503     @Override
504     public HardwareSpec getHardwareSpec() {
505         return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
506     }
507
508     @Override
509     public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
510         CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
511
512         bottomBarSpec.enableCamera = true;
513         bottomBarSpec.cameraCallback = mCameraCallback;
514         bottomBarSpec.enableTorchFlash = true;
515         bottomBarSpec.flashCallback = mFlashCallback;
516         bottomBarSpec.hideHdr = true;
517         bottomBarSpec.hideGridLines = true;
518
519         if (isVideoCaptureIntent()) {
520             bottomBarSpec.showCancel = true;
521             bottomBarSpec.cancelCallback = mCancelCallback;
522             bottomBarSpec.showDone = true;
523             bottomBarSpec.doneCallback = mDoneCallback;
524             bottomBarSpec.showReview = true;
525             bottomBarSpec.reviewCallback = mReviewCallback;
526         }
527
528         return bottomBarSpec;
529     }
530
531     @Override
532     public void onCameraAvailable(CameraProxy cameraProxy) {
533         mCameraDevice = cameraProxy;
534         mInitialParams = mCameraDevice.getParameters();
535         mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
536         mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
537         readVideoPreferences();
538         resizeForPreviewAspectRatio();
539         initializeFocusManager();
540
541         startPreview();
542         initializeVideoSnapshot();
543         mUI.initializeZoom(mParameters);
544         initializeControlByIntent();
545     }
546
547     private void startPlayVideoActivity() {
548         Intent intent = new Intent(Intent.ACTION_VIEW);
549         intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
550         try {
551             mActivity
552                     .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
553         } catch (ActivityNotFoundException ex) {
554             Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
555         }
556     }
557
558     @Override
559     @OnClickAttr
560     public void onReviewPlayClicked(View v) {
561         startPlayVideoActivity();
562     }
563
564     @Override
565     @OnClickAttr
566     public void onReviewDoneClicked(View v) {
567         mIsInReviewMode = false;
568         doReturnToCaller(true);
569     }
570
571     @Override
572     @OnClickAttr
573     public void onReviewCancelClicked(View v) {
574         // TODO: It should be better to not even insert the URI at all before we
575         // confirm done in review, which means we need to handle temporary video
576         // files in a quite different way than we currently had.
577         // Make sure we don't delete the Uri sent from the video capture intent.
578         if (mCurrentVideoUriFromMediaSaved) {
579             mContentResolver.delete(mCurrentVideoUri, null, null);
580         }
581         mIsInReviewMode = false;
582         doReturnToCaller(false);
583     }
584
585     @Override
586     public boolean isInReviewMode() {
587         return mIsInReviewMode;
588     }
589
590     private void onStopVideoRecording() {
591         mAppController.getCameraAppUI().setSwipeEnabled(true);
592         boolean recordFail = stopVideoRecording();
593         if (mIsVideoCaptureIntent) {
594             if (mQuickCapture) {
595                 doReturnToCaller(!recordFail);
596             } else if (!recordFail) {
597                 showCaptureResult();
598             }
599         } else if (!recordFail){
600             // Start capture animation.
601             if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
602                 // The capture animation is disabled on ICS because we use SurfaceView
603                 // for preview during recording. When the recording is done, we switch
604                 // back to use SurfaceTexture for preview and we need to stop then start
605                 // the preview. This will cause the preview flicker since the preview
606                 // will not be continuous for a short period of time.
607
608                 mUI.animateFlash();
609             }
610         }
611     }
612
613     public void onVideoSaved() {
614         if (mIsVideoCaptureIntent) {
615             showCaptureResult();
616         }
617     }
618
619     public void onProtectiveCurtainClick(View v) {
620         // Consume clicks
621     }
622
623     @Override
624     public void onShutterButtonClick() {
625         if (mSwitchingCamera) {
626             return;
627         }
628         boolean stop = mMediaRecorderRecording;
629
630         if (stop) {
631             onStopVideoRecording();
632         } else {
633             startVideoRecording();
634         }
635         mUI.enableShutter(false);
636         mFocusManager.onShutterUp();
637
638         // Keep the shutter button disabled when in video capture intent
639         // mode and recording is stopped. It'll be re-enabled when
640         // re-take button is clicked.
641         if (!(mIsVideoCaptureIntent && stop)) {
642             mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
643         }
644     }
645
646     @Override
647     public void onShutterButtonFocus(boolean pressed) {
648         // TODO: Remove this when old camera controls are removed from the UI.
649     }
650
651     private void readVideoPreferences() {
652         // The preference stores values from ListPreference and is thus string type for all values.
653         // We need to convert it to int manually.
654         SettingsManager settingsManager = mActivity.getSettingsManager();
655         if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY)) {
656             settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY);
657         }
658         String videoQuality = settingsManager.get(SettingsManager.SETTING_VIDEO_QUALITY);
659         int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
660         Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
661
662         // Set video quality.
663         Intent intent = mActivity.getIntent();
664         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
665             int extraVideoQuality =
666                     intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
667             if (extraVideoQuality > 0) {
668                 quality = CamcorderProfile.QUALITY_HIGH;
669             } else {  // 0 is mms.
670                 quality = CamcorderProfile.QUALITY_LOW;
671             }
672         }
673
674         // Set video duration limit. The limit is read from the preference,
675         // unless it is specified in the intent.
676         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
677             int seconds =
678                     intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
679             mMaxVideoDurationInMs = 1000 * seconds;
680         } else {
681             mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
682         }
683
684         // Read time lapse recording interval.
685         String frameIntervalStr = settingsManager.get(
686             SettingsManager.SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
687         mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
688         mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
689         // TODO: This should be checked instead directly +1000.
690         if (mCaptureTimeLapse) {
691             quality += 1000;
692         }
693
694         // If quality is not supported, request QUALITY_HIGH which is always supported.
695         if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
696             quality = CamcorderProfile.QUALITY_HIGH;
697         }
698         mProfile = CamcorderProfile.get(mCameraId, quality);
699         getDesiredPreviewSize();
700         mPreferenceRead = true;
701     }
702
703     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
704     private void getDesiredPreviewSize() {
705         if (mCameraDevice == null) {
706             return;
707         }
708         mParameters = mCameraDevice.getParameters();
709         if (mParameters.getSupportedVideoSizes() == null) {
710             mDesiredPreviewWidth = mProfile.videoFrameWidth;
711             mDesiredPreviewHeight = mProfile.videoFrameHeight;
712         } else { // Driver supports separates outputs for preview and video.
713             List<Size> sizes = mParameters.getSupportedPreviewSizes();
714             Size preferred = mParameters.getPreferredPreviewSizeForVideo();
715             int product = preferred.width * preferred.height;
716             Iterator<Size> it = sizes.iterator();
717             // Remove the preview sizes that are not preferred.
718             while (it.hasNext()) {
719                 Size size = it.next();
720                 if (size.width * size.height > product) {
721                     it.remove();
722                 }
723             }
724             Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
725                     (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
726             mDesiredPreviewWidth = optimalSize.width;
727             mDesiredPreviewHeight = optimalSize.height;
728         }
729         mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
730         Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
731                 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
732     }
733
734     private void resizeForPreviewAspectRatio() {
735         mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
736     }
737
738     private void installIntentFilter() {
739         // install an intent filter to receive SD card related events.
740         IntentFilter intentFilter =
741                 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
742         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
743         intentFilter.addDataScheme("file");
744         mReceiver = new MyBroadcastReceiver();
745         mActivity.registerReceiver(mReceiver, intentFilter);
746     }
747
748     private void setDisplayOrientation() {
749         mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
750         mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
751         // Change the camera display orientation
752         if (mCameraDevice != null) {
753             mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
754         }
755         if (mFocusManager != null) {
756             mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
757         }
758     }
759
760     @Override
761     public void updateCameraOrientation() {
762         if (mMediaRecorderRecording) {
763             return;
764         }
765         if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
766             setDisplayOrientation();
767         }
768     }
769
770     @Override
771     public void updatePreviewAspectRatio(float aspectRatio) {
772         mAppController.updatePreviewAspectRatio(aspectRatio);
773     }
774
775     @Override
776     public int onZoomChanged(int index) {
777         // Not useful to change zoom value when the activity is paused.
778         if (mPaused) {
779             return index;
780         }
781         mZoomValue = index;
782         if (mParameters == null || mCameraDevice == null) {
783             return index;
784         }
785         // Set zoom parameters asynchronously
786         mParameters.setZoom(mZoomValue);
787         mCameraDevice.setParameters(mParameters);
788         Parameters p = mCameraDevice.getParameters();
789         if (p != null) {
790             return p.getZoom();
791         }
792         return index;
793     }
794
795     private void startPreview() {
796         Log.v(TAG, "startPreview");
797
798         SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
799         if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
800                 mCameraDevice == null) {
801             return;
802         }
803
804         mCameraDevice.setErrorCallback(mErrorCallback);
805         if (mPreviewing == true) {
806             stopPreview();
807         }
808
809         setDisplayOrientation();
810         mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
811         setCameraParameters();
812
813         if (mFocusManager != null) {
814             // If the focus mode is continuous autofocus, call cancelAutoFocus
815             // to resume it because it may have been paused by autoFocus call.
816             String focusMode = mFocusManager.getFocusMode();
817             if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
818                 mCameraDevice.cancelAutoFocus();
819             }
820         }
821
822         // This is to notify app controller that preview will start next, so app
823         // controller can set preview callbacks if needed. This has to happen before
824         // preview is started as a workaround of the framework issue related to preview
825         // callbacks that causes preview stretch and crash. (More details see b/12210027
826         // and b/12591410
827         mAppController.onPreviewReadyToStart();
828         try {
829             mCameraDevice.setPreviewTexture(surfaceTexture);
830             mCameraDevice.startPreview();
831             mPreviewing = true;
832             onPreviewStarted();
833         } catch (Throwable ex) {
834             closeCamera();
835             throw new RuntimeException("startPreview failed", ex);
836         }
837     }
838
839     private void onPreviewStarted() {
840         mUI.enableShutter(true);
841         mAppController.onPreviewStarted();
842         if (mFocusManager != null) {
843             mFocusManager.onPreviewStarted();
844         }
845     }
846
847     @Override
848     public void stopPreview() {
849         if (!mPreviewing) {
850             return;
851         }
852         mCameraDevice.stopPreview();
853         if (mFocusManager != null) {
854             mFocusManager.onPreviewStopped();
855         }
856         mPreviewing = false;
857     }
858
859     private void closeCamera() {
860         Log.v(TAG, "closeCamera");
861         if (mCameraDevice == null) {
862             Log.d(TAG, "already stopped.");
863             return;
864         }
865         mCameraDevice.setZoomChangeListener(null);
866         mCameraDevice.setErrorCallback(null);
867         mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
868         mCameraDevice = null;
869         mPreviewing = false;
870         mSnapshotInProgress = false;
871         if (mFocusManager != null) {
872             mFocusManager.onCameraReleased();
873         }
874     }
875
876     @Override
877     public boolean onBackPressed() {
878         if (mPaused) {
879             return true;
880         }
881         if (mMediaRecorderRecording) {
882             onStopVideoRecording();
883             return true;
884         } else {
885             return false;
886         }
887     }
888
889     @Override
890     public boolean onKeyDown(int keyCode, KeyEvent event) {
891         // Do not handle any key if the activity is paused.
892         if (mPaused) {
893             return true;
894         }
895
896         switch (keyCode) {
897             case KeyEvent.KEYCODE_CAMERA:
898                 if (event.getRepeatCount() == 0) {
899                     mUI.clickShutter();
900                     return true;
901                 }
902                 break;
903             case KeyEvent.KEYCODE_DPAD_CENTER:
904                 if (event.getRepeatCount() == 0) {
905                     mUI.clickShutter();
906                     return true;
907                 }
908                 break;
909             case KeyEvent.KEYCODE_MENU:
910                 if (mMediaRecorderRecording) {
911                     return true;
912                 }
913                 break;
914         }
915         return false;
916     }
917
918     @Override
919     public boolean onKeyUp(int keyCode, KeyEvent event) {
920         switch (keyCode) {
921             case KeyEvent.KEYCODE_CAMERA:
922                 mUI.pressShutter(false);
923                 return true;
924         }
925         return false;
926     }
927
928     @Override
929     public boolean isVideoCaptureIntent() {
930         String action = mActivity.getIntent().getAction();
931         return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
932     }
933
934     private void doReturnToCaller(boolean valid) {
935         Intent resultIntent = new Intent();
936         int resultCode;
937         if (valid) {
938             resultCode = Activity.RESULT_OK;
939             resultIntent.setData(mCurrentVideoUri);
940         } else {
941             resultCode = Activity.RESULT_CANCELED;
942         }
943         mActivity.setResultEx(resultCode, resultIntent);
944         mActivity.finish();
945     }
946
947     private void cleanupEmptyFile() {
948         if (mVideoFilename != null) {
949             File f = new File(mVideoFilename);
950             if (f.length() == 0 && f.delete()) {
951                 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
952                 mVideoFilename = null;
953             }
954         }
955     }
956
957     // Prepares media recorder.
958     private void initializeRecorder() {
959         Log.v(TAG, "initializeRecorder");
960         // If the mCameraDevice is null, then this activity is going to finish
961         if (mCameraDevice == null) {
962             return;
963         }
964
965         Intent intent = mActivity.getIntent();
966         Bundle myExtras = intent.getExtras();
967
968         long requestedSizeLimit = 0;
969         closeVideoFileDescriptor();
970         mCurrentVideoUriFromMediaSaved = false;
971         if (mIsVideoCaptureIntent && myExtras != null) {
972             Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
973             if (saveUri != null) {
974                 try {
975                     mVideoFileDescriptor =
976                             mContentResolver.openFileDescriptor(saveUri, "rw");
977                     mCurrentVideoUri = saveUri;
978                 } catch (java.io.FileNotFoundException ex) {
979                     // invalid uri
980                     Log.e(TAG, ex.toString());
981                 }
982             }
983             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
984         }
985         mMediaRecorder = new MediaRecorder();
986
987         // Unlock the camera object before passing it to media recorder.
988         mCameraDevice.unlock();
989         mMediaRecorder.setCamera(mCameraDevice.getCamera());
990         if (!mCaptureTimeLapse) {
991             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
992         }
993         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
994         mMediaRecorder.setProfile(mProfile);
995         mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
996         mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
997         if (mCaptureTimeLapse) {
998             double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
999             setCaptureRate(mMediaRecorder, fps);
1000         }
1001
1002         setRecordLocation();
1003
1004         // Set output file.
1005         // Try Uri in the intent first. If it doesn't exist, use our own
1006         // instead.
1007         if (mVideoFileDescriptor != null) {
1008             mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1009         } else {
1010             generateVideoFilename(mProfile.fileFormat);
1011             mMediaRecorder.setOutputFile(mVideoFilename);
1012         }
1013
1014         // Set maximum file size.
1015         long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
1016         if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1017             maxFileSize = requestedSizeLimit;
1018         }
1019
1020         try {
1021             mMediaRecorder.setMaxFileSize(maxFileSize);
1022         } catch (RuntimeException exception) {
1023             // We are going to ignore failure of setMaxFileSize here, as
1024             // a) The composer selected may simply not support it, or
1025             // b) The underlying media framework may not handle 64-bit range
1026             // on the size restriction.
1027         }
1028
1029         // See android.hardware.Camera.Parameters.setRotation for
1030         // documentation.
1031         // Note that mOrientation here is the device orientation, which is the opposite of
1032         // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1033         // which is the orientation the graphics need to rotate in order to render correctly.
1034         int rotation = 0;
1035         if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1036             CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
1037             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1038                 rotation = (info.orientation - mOrientation + 360) % 360;
1039             } else {  // back-facing camera
1040                 rotation = (info.orientation + mOrientation) % 360;
1041             }
1042         }
1043         mMediaRecorder.setOrientationHint(rotation);
1044
1045         try {
1046             mMediaRecorder.prepare();
1047         } catch (IOException e) {
1048             Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1049             releaseMediaRecorder();
1050             throw new RuntimeException(e);
1051         }
1052
1053         mMediaRecorder.setOnErrorListener(this);
1054         mMediaRecorder.setOnInfoListener(this);
1055     }
1056
1057     private static void setCaptureRate(MediaRecorder recorder, double fps) {
1058         recorder.setCaptureRate(fps);
1059     }
1060
1061     private void setRecordLocation() {
1062         Location loc = mLocationManager.getCurrentLocation();
1063         if (loc != null) {
1064             mMediaRecorder.setLocation((float) loc.getLatitude(),
1065                     (float) loc.getLongitude());
1066         }
1067     }
1068
1069     private void releaseMediaRecorder() {
1070         Log.v(TAG, "Releasing media recorder.");
1071         if (mMediaRecorder != null) {
1072             cleanupEmptyFile();
1073             mMediaRecorder.reset();
1074             mMediaRecorder.release();
1075             mMediaRecorder = null;
1076         }
1077         mVideoFilename = null;
1078     }
1079
1080     private void generateVideoFilename(int outputFileFormat) {
1081         long dateTaken = System.currentTimeMillis();
1082         String title = createName(dateTaken);
1083         // Used when emailing.
1084         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1085         String mime = convertOutputFormatToMimeType(outputFileFormat);
1086         String path = Storage.DIRECTORY + '/' + filename;
1087         String tmpPath = path + ".tmp";
1088         mCurrentVideoValues = new ContentValues(9);
1089         mCurrentVideoValues.put(Video.Media.TITLE, title);
1090         mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1091         mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1092         mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
1093         mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1094         mCurrentVideoValues.put(Video.Media.DATA, path);
1095         mCurrentVideoValues.put(Video.Media.RESOLUTION,
1096                 Integer.toString(mProfile.videoFrameWidth) + "x" +
1097                 Integer.toString(mProfile.videoFrameHeight));
1098         Location loc = mLocationManager.getCurrentLocation();
1099         if (loc != null) {
1100             mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1101             mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1102         }
1103         mVideoFilename = tmpPath;
1104         Log.v(TAG, "New video filename: " + mVideoFilename);
1105     }
1106
1107     private void saveVideo() {
1108         if (mVideoFileDescriptor == null) {
1109             long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1110             if (duration > 0) {
1111                 if (mCaptureTimeLapse) {
1112                     duration = getTimeLapseVideoLength(duration);
1113                 }
1114             } else {
1115                 Log.w(TAG, "Video duration <= 0 : " + duration);
1116             }
1117             getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
1118                     duration, mCurrentVideoValues,
1119                     mOnVideoSavedListener, mContentResolver);
1120         }
1121         mCurrentVideoValues = null;
1122     }
1123
1124     private void deleteVideoFile(String fileName) {
1125         Log.v(TAG, "Deleting video " + fileName);
1126         File f = new File(fileName);
1127         if (!f.delete()) {
1128             Log.v(TAG, "Could not delete " + fileName);
1129         }
1130     }
1131
1132     private PreferenceGroup filterPreferenceScreenByIntent(
1133             PreferenceGroup screen) {
1134         Intent intent = mActivity.getIntent();
1135         if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1136             CameraSettings.removePreferenceFromScreen(screen, CameraSettings.KEY_VIDEO_QUALITY);
1137         }
1138
1139         if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1140             CameraSettings.removePreferenceFromScreen(screen,
1141                     CameraSettings.KEY_VIDEO_QUALITY);
1142         }
1143         return screen;
1144     }
1145
1146     // from MediaRecorder.OnErrorListener
1147     @Override
1148     public void onError(MediaRecorder mr, int what, int extra) {
1149         Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1150         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1151             // We may have run out of space on the sdcard.
1152             stopVideoRecording();
1153             mActivity.updateStorageSpaceAndHint();
1154         }
1155     }
1156
1157     // from MediaRecorder.OnInfoListener
1158     @Override
1159     public void onInfo(MediaRecorder mr, int what, int extra) {
1160         if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1161             if (mMediaRecorderRecording) {
1162                 onStopVideoRecording();
1163             }
1164         } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1165             if (mMediaRecorderRecording) {
1166                 onStopVideoRecording();
1167             }
1168
1169             // Show the toast.
1170             Toast.makeText(mActivity, R.string.video_reach_size_limit,
1171                     Toast.LENGTH_LONG).show();
1172         }
1173     }
1174
1175     /*
1176      * Make sure we're not recording music playing in the background, ask the
1177      * MediaPlaybackService to pause playback.
1178      */
1179     private void pauseAudioPlayback() {
1180         AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1181         am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1182     }
1183
1184     // For testing.
1185     public boolean isRecording() {
1186         return mMediaRecorderRecording;
1187     }
1188
1189     private void startVideoRecording() {
1190         Log.v(TAG, "startVideoRecording");
1191         mUI.cancelAnimations();
1192         mUI.setSwipingEnabled(false);
1193         mUI.showFocusUI(false);
1194
1195         // A special case of mode options closing: during capture it should
1196         // not be possible to change mode state.
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 }