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