OSDN Git Service

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