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