OSDN Git Service

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