2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera;
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.ActivityNotFoundException;
22 import android.content.BroadcastReceiver;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.graphics.Bitmap;
29 import android.graphics.SurfaceTexture;
30 import android.hardware.Camera.CameraInfo;
31 import android.hardware.Camera.Parameters;
32 import android.hardware.Camera.Size;
33 import android.location.Location;
34 import android.media.AudioManager;
35 import android.media.CamcorderProfile;
36 import android.media.CameraProfile;
37 import android.media.MediaRecorder;
38 import android.net.Uri;
39 import android.os.Build;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.Message;
43 import android.os.ParcelFileDescriptor;
44 import android.os.SystemClock;
45 import android.provider.MediaStore;
46 import android.provider.MediaStore.MediaColumns;
47 import android.provider.MediaStore.Video;
48 import android.util.Log;
49 import android.view.KeyEvent;
50 import android.view.OrientationEventListener;
51 import android.view.View;
52 import android.widget.Toast;
54 import com.android.camera.app.AppController;
55 import com.android.camera.app.CameraAppUI;
56 import com.android.camera.app.CameraManager;
57 import com.android.camera.app.CameraManager.CameraPictureCallback;
58 import com.android.camera.app.CameraManager.CameraProxy;
59 import com.android.camera.app.LocationManager;
60 import com.android.camera.app.MediaSaver;
61 import com.android.camera.app.MemoryManager;
62 import com.android.camera.app.MemoryManager.MemoryListener;
63 import com.android.camera.exif.ExifInterface;
64 import com.android.camera.hardware.HardwareSpec;
65 import com.android.camera.hardware.HardwareSpecImpl;
66 import com.android.camera.module.ModuleController;
67 import com.android.camera.settings.SettingsManager;
68 import com.android.camera.settings.SettingsUtil;
69 import com.android.camera.util.AccessibilityUtils;
70 import com.android.camera.util.ApiHelper;
71 import com.android.camera.util.CameraUtil;
72 import com.android.camera.util.UsageStatistics;
73 import com.android.camera2.R;
74 import com.google.common.logging.eventprotos;
77 import java.io.IOException;
78 import java.text.SimpleDateFormat;
79 import java.util.Date;
80 import java.util.Iterator;
81 import java.util.List;
83 public class VideoModule extends CameraModule
84 implements ModuleController,
87 MediaRecorder.OnErrorListener,
88 MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
90 private static final String TAG = "VideoModule";
92 // Messages defined for the UI thread handler.
93 private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
94 private static final int MSG_UPDATE_RECORD_TIME = 5;
95 private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
96 private static final int MSG_SWITCH_CAMERA = 8;
97 private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
99 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
102 * An unpublished intent flag requesting to start recording straight away
103 * and return as soon as recording is stopped.
104 * TODO: consider publishing by moving into MediaStore.
106 private static final String EXTRA_QUICK_CAPTURE =
107 "android.intent.extra.quickCapture";
110 private CameraActivity mActivity;
111 private boolean mPaused;
112 private int mCameraId;
113 private Parameters mParameters;
115 private boolean mIsInReviewMode;
116 private boolean mSnapshotInProgress = false;
118 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
120 // Preference must be read before starting preview. We check this before starting
122 private boolean mPreferenceRead;
124 private boolean mIsVideoCaptureIntent;
125 private boolean mQuickCapture;
127 private MediaRecorder mMediaRecorder;
129 private boolean mSwitchingCamera;
130 private boolean mMediaRecorderRecording = false;
131 private long mRecordingStartTime;
132 private boolean mRecordingTimeCountsDown = false;
133 private long mOnResumeTime;
134 // The video file that the hardware camera is about to record into
135 // (or is recording into.
136 private String mVideoFilename;
137 private ParcelFileDescriptor mVideoFileDescriptor;
139 // The video file that has already been recorded, and that is being
140 // examined by the user.
141 private String mCurrentVideoFilename;
142 private Uri mCurrentVideoUri;
143 private boolean mCurrentVideoUriFromMediaSaved;
144 private ContentValues mCurrentVideoValues;
146 private CamcorderProfile mProfile;
148 // The video duration limit. 0 means no limit.
149 private int mMaxVideoDurationInMs;
151 // Time Lapse parameters.
152 private boolean mCaptureTimeLapse = false;
153 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
154 private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
156 boolean mPreviewing = false; // True if preview is started.
157 // The display rotation in degrees. This is only valid when mPreviewing is
159 private int mDisplayRotation;
160 private int mCameraDisplayOrientation;
161 private AppController mAppController;
163 private int mDesiredPreviewWidth;
164 private int mDesiredPreviewHeight;
165 private ContentResolver mContentResolver;
167 private LocationManager mLocationManager;
169 private int mPendingSwitchCameraId;
170 private final Handler mHandler = new MainHandler();
172 private CameraProxy mCameraDevice;
174 // The degrees of the device rotated clockwise from its natural orientation.
175 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
177 private int mZoomValue; // The current zoom value.
179 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
180 new MediaSaver.OnMediaSavedListener() {
182 public void onMediaSaved(Uri uri) {
184 mCurrentVideoUri = uri;
185 mCurrentVideoUriFromMediaSaved = true;
187 mActivity.notifyNewMedia(uri);
192 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
193 new MediaSaver.OnMediaSavedListener() {
195 public void onMediaSaved(Uri uri) {
197 mActivity.notifyNewMedia(uri);
201 private FocusOverlayManager mFocusManager;
202 private boolean mMirror;
203 private Parameters mInitialParams;
204 private boolean mFocusAreaSupported;
205 private boolean mMeteringAreaSupported;
207 private final CameraManager.CameraAFCallback mAutoFocusCallback =
208 new CameraManager.CameraAFCallback() {
210 public void onAutoFocus(boolean focused, CameraProxy camera) {
214 mFocusManager.onAutoFocus(focused, false);
218 private final Object mAutoFocusMoveCallback =
219 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
220 ? new CameraManager.CameraAFMoveCallback() {
222 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
223 mFocusManager.onAutoFocusMoving(moving);
228 * This Handler is used to post message back onto the main thread of the
231 private class MainHandler extends Handler {
233 public void handleMessage(Message msg) {
236 case MSG_ENABLE_SHUTTER_BUTTON:
237 mUI.enableShutter(true);
240 case MSG_UPDATE_RECORD_TIME: {
241 updateRecordingTime();
245 case MSG_CHECK_DISPLAY_ROTATION: {
246 // Restart the preview if display rotation has changed.
247 // Sometimes this happens when the device is held upside
248 // down and camera app is opened. Rotation animation will
249 // take some time and the rotation value we have got may be
250 // wrong. Framework does not have a callback for this now.
251 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
252 && !mMediaRecorderRecording && !mSwitchingCamera) {
255 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
256 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
261 case MSG_SWITCH_CAMERA: {
266 case MSG_SWITCH_CAMERA_START_ANIMATION: {
268 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
270 // Enable all camera controls.
271 mSwitchingCamera = false;
276 Log.v(TAG, "Unhandled message: " + msg.what);
282 private BroadcastReceiver mReceiver = null;
284 /** Whether shutter is enabled. */
285 private boolean mShutterEnabled;
287 private class MyBroadcastReceiver extends BroadcastReceiver {
289 public void onReceive(Context context, Intent intent) {
290 String action = intent.getAction();
291 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
292 stopVideoRecording();
293 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
294 Toast.makeText(mActivity,
295 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
300 private int mShutterIconId;
304 * Construct a new video module.
306 public VideoModule(AppController app) {
310 private String createName(long dateTaken) {
311 Date date = new Date(dateTaken);
312 SimpleDateFormat dateFormat = new SimpleDateFormat(
313 mActivity.getString(R.string.video_file_name_format));
315 return dateFormat.format(date);
319 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
320 mActivity = activity;
321 // TODO: Need to look at the controller interface to see if we can get
322 // rid of passing in the activity directly.
323 mAppController = mActivity;
324 mUI = new VideoUI(mActivity, this, mActivity.getModuleLayoutRoot());
325 mActivity.setPreviewStatusListener(mUI);
327 SettingsManager settingsManager = mActivity.getSettingsManager();
328 mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
331 * To reduce startup time, we start the preview in another thread.
332 * We make sure the preview is started at the end of onCreate.
334 requestCamera(mCameraId);
336 mContentResolver = mActivity.getContentResolver();
338 // Surface texture is from camera screen nail and startPreview needs it.
339 // This must be done before startPreview.
340 mIsVideoCaptureIntent = isVideoCaptureIntent();
342 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
343 mLocationManager = mActivity.getLocationManager();
345 mUI.setOrientationIndicator(0, false);
346 setDisplayOrientation();
348 mUI.showTimeLapseUI(mCaptureTimeLapse);
349 mPendingSwitchCameraId = -1;
351 mShutterIconId = CameraUtil.getCameraShutterIconId(
352 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
357 public boolean isUsingBottomBar() {
361 private void initializeControlByIntent() {
362 if (isVideoCaptureIntent()) {
363 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
368 public void onSingleTapUp(View view, int x, int y) {
369 if (mPaused || mCameraDevice == null) {
372 // Check if metering area or focus area is supported.
373 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
377 mFocusManager.onSingleTapUp(x, y);
380 private void takeASnapshot() {
381 // Only take snapshots if video snapshot is supported by device
382 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
383 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress || mShutterEnabled) {
387 // Set rotation and gps data.
388 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
389 int rotation = CameraUtil.getJpegRotation(info, mOrientation);
390 mParameters.setRotation(rotation);
391 Location loc = mLocationManager.getCurrentLocation();
392 CameraUtil.setGpsParameters(mParameters, loc);
393 mCameraDevice.setParameters(mParameters);
395 Log.v(TAG, "Video snapshot start");
396 mCameraDevice.takePicture(mHandler,
397 null, null, null, new JpegPictureCallback(loc));
398 showVideoSnapshotUI(true);
399 mSnapshotInProgress = true;
400 UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.VIDEO_STILL,
405 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
406 private void updateAutoFocusMoveCallback() {
411 if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
412 mCameraDevice.setAutoFocusMoveCallback(mHandler,
413 (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
415 mCameraDevice.setAutoFocusMoveCallback(null, null);
420 * The focus manager gets initialized after camera is available.
422 private void initializeFocusManager() {
423 // Create FocusManager object. startPreview needs it.
424 // if mFocusManager not null, reuse it
425 // otherwise create a new instance
426 if (mFocusManager != null) {
427 mFocusManager.removeMessages();
429 CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
430 mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
431 String[] defaultFocusModes = mActivity.getResources().getStringArray(
432 R.array.pref_camera_focusmode_default_array);
433 mFocusManager = new FocusOverlayManager(mActivity.getSettingsManager(),
435 mInitialParams, this, mMirror,
436 mActivity.getMainLooper(), mUI.getFocusUI());
438 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
442 public void onOrientationChanged(int orientation) {
443 // We keep the last known orientation. So if the user first orient
444 // the camera then point the camera to floor or sky, we still have
445 // the correct orientation.
446 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
449 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
451 if (mOrientation != newOrientation) {
452 mOrientation = newOrientation;
457 private final ButtonManager.ButtonCallback mFlashCallback =
458 new ButtonManager.ButtonCallback() {
460 public void onStateChanged(int state) {
461 // Update flash parameters.
462 enableTorchMode(true);
466 private final ButtonManager.ButtonCallback mCameraCallback =
467 new ButtonManager.ButtonCallback() {
469 public void onStateChanged(int state) {
470 if (mPaused || mPendingSwitchCameraId != -1) {
473 mPendingSwitchCameraId = state;
474 Log.d(TAG, "Start to copy texture.");
476 // Disable all camera controls.
477 mSwitchingCamera = true;
482 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
484 public void onClick(View v) {
485 onReviewCancelClicked(v);
489 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
491 public void onClick(View v) {
492 onReviewDoneClicked(v);
495 private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
497 public void onClick(View v) {
498 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
499 onReviewPlayClicked(v);
504 public HardwareSpec getHardwareSpec() {
505 return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
509 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
510 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
512 bottomBarSpec.enableCamera = true;
513 bottomBarSpec.cameraCallback = mCameraCallback;
514 bottomBarSpec.enableTorchFlash = true;
515 bottomBarSpec.flashCallback = mFlashCallback;
516 bottomBarSpec.hideHdr = true;
517 bottomBarSpec.hideGridLines = true;
519 if (isVideoCaptureIntent()) {
520 bottomBarSpec.showCancel = true;
521 bottomBarSpec.cancelCallback = mCancelCallback;
522 bottomBarSpec.showDone = true;
523 bottomBarSpec.doneCallback = mDoneCallback;
524 bottomBarSpec.showReview = true;
525 bottomBarSpec.reviewCallback = mReviewCallback;
528 return bottomBarSpec;
532 public void onCameraAvailable(CameraProxy cameraProxy) {
533 mCameraDevice = cameraProxy;
534 mInitialParams = mCameraDevice.getParameters();
535 mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
536 mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
537 readVideoPreferences();
538 resizeForPreviewAspectRatio();
539 initializeFocusManager();
542 initializeVideoSnapshot();
543 mUI.initializeZoom(mParameters);
544 initializeControlByIntent();
547 private void startPlayVideoActivity() {
548 Intent intent = new Intent(Intent.ACTION_VIEW);
549 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
552 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
553 } catch (ActivityNotFoundException ex) {
554 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
560 public void onReviewPlayClicked(View v) {
561 startPlayVideoActivity();
566 public void onReviewDoneClicked(View v) {
567 mIsInReviewMode = false;
568 doReturnToCaller(true);
573 public void onReviewCancelClicked(View v) {
574 // TODO: It should be better to not even insert the URI at all before we
575 // confirm done in review, which means we need to handle temporary video
576 // files in a quite different way than we currently had.
577 // Make sure we don't delete the Uri sent from the video capture intent.
578 if (mCurrentVideoUriFromMediaSaved) {
579 mContentResolver.delete(mCurrentVideoUri, null, null);
581 mIsInReviewMode = false;
582 doReturnToCaller(false);
586 public boolean isInReviewMode() {
587 return mIsInReviewMode;
590 private void onStopVideoRecording() {
591 mAppController.getCameraAppUI().setSwipeEnabled(true);
592 boolean recordFail = stopVideoRecording();
593 if (mIsVideoCaptureIntent) {
595 doReturnToCaller(!recordFail);
596 } else if (!recordFail) {
599 } else if (!recordFail){
600 // Start capture animation.
601 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
602 // The capture animation is disabled on ICS because we use SurfaceView
603 // for preview during recording. When the recording is done, we switch
604 // back to use SurfaceTexture for preview and we need to stop then start
605 // the preview. This will cause the preview flicker since the preview
606 // will not be continuous for a short period of time.
613 public void onVideoSaved() {
614 if (mIsVideoCaptureIntent) {
619 public void onProtectiveCurtainClick(View v) {
624 public void onShutterButtonClick() {
625 if (mSwitchingCamera) {
628 boolean stop = mMediaRecorderRecording;
631 onStopVideoRecording();
633 startVideoRecording();
635 mUI.enableShutter(false);
636 mFocusManager.onShutterUp();
638 // Keep the shutter button disabled when in video capture intent
639 // mode and recording is stopped. It'll be re-enabled when
640 // re-take button is clicked.
641 if (!(mIsVideoCaptureIntent && stop)) {
642 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
647 public void onShutterButtonFocus(boolean pressed) {
648 // TODO: Remove this when old camera controls are removed from the UI.
651 private void readVideoPreferences() {
652 // The preference stores values from ListPreference and is thus string type for all values.
653 // We need to convert it to int manually.
654 SettingsManager settingsManager = mActivity.getSettingsManager();
655 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY)) {
656 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY);
658 String videoQuality = settingsManager.get(SettingsManager.SETTING_VIDEO_QUALITY);
659 int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
660 Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
662 // Set video quality.
663 Intent intent = mActivity.getIntent();
664 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
665 int extraVideoQuality =
666 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
667 if (extraVideoQuality > 0) {
668 quality = CamcorderProfile.QUALITY_HIGH;
669 } else { // 0 is mms.
670 quality = CamcorderProfile.QUALITY_LOW;
674 // Set video duration limit. The limit is read from the preference,
675 // unless it is specified in the intent.
676 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
678 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
679 mMaxVideoDurationInMs = 1000 * seconds;
681 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
684 // Read time lapse recording interval.
685 String frameIntervalStr = settingsManager.get(
686 SettingsManager.SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
687 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
688 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
689 // TODO: This should be checked instead directly +1000.
690 if (mCaptureTimeLapse) {
694 // If quality is not supported, request QUALITY_HIGH which is always supported.
695 if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
696 quality = CamcorderProfile.QUALITY_HIGH;
698 mProfile = CamcorderProfile.get(mCameraId, quality);
699 getDesiredPreviewSize();
700 mPreferenceRead = true;
703 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
704 private void getDesiredPreviewSize() {
705 if (mCameraDevice == null) {
708 mParameters = mCameraDevice.getParameters();
709 if (mParameters.getSupportedVideoSizes() == null) {
710 mDesiredPreviewWidth = mProfile.videoFrameWidth;
711 mDesiredPreviewHeight = mProfile.videoFrameHeight;
712 } else { // Driver supports separates outputs for preview and video.
713 List<Size> sizes = mParameters.getSupportedPreviewSizes();
714 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
715 int product = preferred.width * preferred.height;
716 Iterator<Size> it = sizes.iterator();
717 // Remove the preview sizes that are not preferred.
718 while (it.hasNext()) {
719 Size size = it.next();
720 if (size.width * size.height > product) {
724 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
725 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
726 mDesiredPreviewWidth = optimalSize.width;
727 mDesiredPreviewHeight = optimalSize.height;
729 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
730 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
731 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
734 private void resizeForPreviewAspectRatio() {
735 mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
738 private void installIntentFilter() {
739 // install an intent filter to receive SD card related events.
740 IntentFilter intentFilter =
741 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
742 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
743 intentFilter.addDataScheme("file");
744 mReceiver = new MyBroadcastReceiver();
745 mActivity.registerReceiver(mReceiver, intentFilter);
748 private void setDisplayOrientation() {
749 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
750 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
751 // Change the camera display orientation
752 if (mCameraDevice != null) {
753 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
755 if (mFocusManager != null) {
756 mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
761 public void updateCameraOrientation() {
762 if (mMediaRecorderRecording) {
765 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
766 setDisplayOrientation();
771 public void updatePreviewAspectRatio(float aspectRatio) {
772 mAppController.updatePreviewAspectRatio(aspectRatio);
776 public int onZoomChanged(int index) {
777 // Not useful to change zoom value when the activity is paused.
782 if (mParameters == null || mCameraDevice == null) {
785 // Set zoom parameters asynchronously
786 mParameters.setZoom(mZoomValue);
787 mCameraDevice.setParameters(mParameters);
788 Parameters p = mCameraDevice.getParameters();
795 private void startPreview() {
796 Log.v(TAG, "startPreview");
798 SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
799 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
800 mCameraDevice == null) {
804 mCameraDevice.setErrorCallback(mErrorCallback);
805 if (mPreviewing == true) {
809 setDisplayOrientation();
810 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
811 setCameraParameters();
813 if (mFocusManager != null) {
814 // If the focus mode is continuous autofocus, call cancelAutoFocus
815 // to resume it because it may have been paused by autoFocus call.
816 String focusMode = mFocusManager.getFocusMode();
817 if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
818 mCameraDevice.cancelAutoFocus();
822 // This is to notify app controller that preview will start next, so app
823 // controller can set preview callbacks if needed. This has to happen before
824 // preview is started as a workaround of the framework issue related to preview
825 // callbacks that causes preview stretch and crash. (More details see b/12210027
827 mAppController.onPreviewReadyToStart();
829 mCameraDevice.setPreviewTexture(surfaceTexture);
830 mCameraDevice.startPreview();
833 } catch (Throwable ex) {
835 throw new RuntimeException("startPreview failed", ex);
839 private void onPreviewStarted() {
840 mUI.enableShutter(true);
841 mAppController.onPreviewStarted();
842 if (mFocusManager != null) {
843 mFocusManager.onPreviewStarted();
848 public void stopPreview() {
852 mCameraDevice.stopPreview();
853 if (mFocusManager != null) {
854 mFocusManager.onPreviewStopped();
859 private void closeCamera() {
860 Log.v(TAG, "closeCamera");
861 if (mCameraDevice == null) {
862 Log.d(TAG, "already stopped.");
865 mCameraDevice.setZoomChangeListener(null);
866 mCameraDevice.setErrorCallback(null);
867 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
868 mCameraDevice = null;
870 mSnapshotInProgress = false;
871 if (mFocusManager != null) {
872 mFocusManager.onCameraReleased();
877 public boolean onBackPressed() {
881 if (mMediaRecorderRecording) {
882 onStopVideoRecording();
890 public boolean onKeyDown(int keyCode, KeyEvent event) {
891 // Do not handle any key if the activity is paused.
897 case KeyEvent.KEYCODE_CAMERA:
898 if (event.getRepeatCount() == 0) {
903 case KeyEvent.KEYCODE_DPAD_CENTER:
904 if (event.getRepeatCount() == 0) {
909 case KeyEvent.KEYCODE_MENU:
910 if (mMediaRecorderRecording) {
919 public boolean onKeyUp(int keyCode, KeyEvent event) {
921 case KeyEvent.KEYCODE_CAMERA:
922 mUI.pressShutter(false);
929 public boolean isVideoCaptureIntent() {
930 String action = mActivity.getIntent().getAction();
931 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
934 private void doReturnToCaller(boolean valid) {
935 Intent resultIntent = new Intent();
938 resultCode = Activity.RESULT_OK;
939 resultIntent.setData(mCurrentVideoUri);
941 resultCode = Activity.RESULT_CANCELED;
943 mActivity.setResultEx(resultCode, resultIntent);
947 private void cleanupEmptyFile() {
948 if (mVideoFilename != null) {
949 File f = new File(mVideoFilename);
950 if (f.length() == 0 && f.delete()) {
951 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
952 mVideoFilename = null;
957 // Prepares media recorder.
958 private void initializeRecorder() {
959 Log.v(TAG, "initializeRecorder");
960 // If the mCameraDevice is null, then this activity is going to finish
961 if (mCameraDevice == null) {
965 Intent intent = mActivity.getIntent();
966 Bundle myExtras = intent.getExtras();
968 long requestedSizeLimit = 0;
969 closeVideoFileDescriptor();
970 mCurrentVideoUriFromMediaSaved = false;
971 if (mIsVideoCaptureIntent && myExtras != null) {
972 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
973 if (saveUri != null) {
975 mVideoFileDescriptor =
976 mContentResolver.openFileDescriptor(saveUri, "rw");
977 mCurrentVideoUri = saveUri;
978 } catch (java.io.FileNotFoundException ex) {
980 Log.e(TAG, ex.toString());
983 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
985 mMediaRecorder = new MediaRecorder();
987 // Unlock the camera object before passing it to media recorder.
988 mCameraDevice.unlock();
989 mMediaRecorder.setCamera(mCameraDevice.getCamera());
990 if (!mCaptureTimeLapse) {
991 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
993 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
994 mMediaRecorder.setProfile(mProfile);
995 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
996 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
997 if (mCaptureTimeLapse) {
998 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
999 setCaptureRate(mMediaRecorder, fps);
1002 setRecordLocation();
1005 // Try Uri in the intent first. If it doesn't exist, use our own
1007 if (mVideoFileDescriptor != null) {
1008 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1010 generateVideoFilename(mProfile.fileFormat);
1011 mMediaRecorder.setOutputFile(mVideoFilename);
1014 // Set maximum file size.
1015 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
1016 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1017 maxFileSize = requestedSizeLimit;
1021 mMediaRecorder.setMaxFileSize(maxFileSize);
1022 } catch (RuntimeException exception) {
1023 // We are going to ignore failure of setMaxFileSize here, as
1024 // a) The composer selected may simply not support it, or
1025 // b) The underlying media framework may not handle 64-bit range
1026 // on the size restriction.
1029 // See android.hardware.Camera.Parameters.setRotation for
1031 // Note that mOrientation here is the device orientation, which is the opposite of
1032 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1033 // which is the orientation the graphics need to rotate in order to render correctly.
1035 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1036 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
1037 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1038 rotation = (info.orientation - mOrientation + 360) % 360;
1039 } else { // back-facing camera
1040 rotation = (info.orientation + mOrientation) % 360;
1043 mMediaRecorder.setOrientationHint(rotation);
1046 mMediaRecorder.prepare();
1047 } catch (IOException e) {
1048 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1049 releaseMediaRecorder();
1050 throw new RuntimeException(e);
1053 mMediaRecorder.setOnErrorListener(this);
1054 mMediaRecorder.setOnInfoListener(this);
1057 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1058 recorder.setCaptureRate(fps);
1061 private void setRecordLocation() {
1062 Location loc = mLocationManager.getCurrentLocation();
1064 mMediaRecorder.setLocation((float) loc.getLatitude(),
1065 (float) loc.getLongitude());
1069 private void releaseMediaRecorder() {
1070 Log.v(TAG, "Releasing media recorder.");
1071 if (mMediaRecorder != null) {
1073 mMediaRecorder.reset();
1074 mMediaRecorder.release();
1075 mMediaRecorder = null;
1077 mVideoFilename = null;
1080 private void generateVideoFilename(int outputFileFormat) {
1081 long dateTaken = System.currentTimeMillis();
1082 String title = createName(dateTaken);
1083 // Used when emailing.
1084 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1085 String mime = convertOutputFormatToMimeType(outputFileFormat);
1086 String path = Storage.DIRECTORY + '/' + filename;
1087 String tmpPath = path + ".tmp";
1088 mCurrentVideoValues = new ContentValues(9);
1089 mCurrentVideoValues.put(Video.Media.TITLE, title);
1090 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1091 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1092 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
1093 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1094 mCurrentVideoValues.put(Video.Media.DATA, path);
1095 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1096 Integer.toString(mProfile.videoFrameWidth) + "x" +
1097 Integer.toString(mProfile.videoFrameHeight));
1098 Location loc = mLocationManager.getCurrentLocation();
1100 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1101 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1103 mVideoFilename = tmpPath;
1104 Log.v(TAG, "New video filename: " + mVideoFilename);
1107 private void saveVideo() {
1108 if (mVideoFileDescriptor == null) {
1109 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1111 if (mCaptureTimeLapse) {
1112 duration = getTimeLapseVideoLength(duration);
1115 Log.w(TAG, "Video duration <= 0 : " + duration);
1117 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
1118 duration, mCurrentVideoValues,
1119 mOnVideoSavedListener, mContentResolver);
1121 mCurrentVideoValues = null;
1124 private void deleteVideoFile(String fileName) {
1125 Log.v(TAG, "Deleting video " + fileName);
1126 File f = new File(fileName);
1128 Log.v(TAG, "Could not delete " + fileName);
1132 private PreferenceGroup filterPreferenceScreenByIntent(
1133 PreferenceGroup screen) {
1134 Intent intent = mActivity.getIntent();
1135 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1136 CameraSettings.removePreferenceFromScreen(screen, CameraSettings.KEY_VIDEO_QUALITY);
1139 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1140 CameraSettings.removePreferenceFromScreen(screen,
1141 CameraSettings.KEY_VIDEO_QUALITY);
1146 // from MediaRecorder.OnErrorListener
1148 public void onError(MediaRecorder mr, int what, int extra) {
1149 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1150 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1151 // We may have run out of space on the sdcard.
1152 stopVideoRecording();
1153 mActivity.updateStorageSpaceAndHint();
1157 // from MediaRecorder.OnInfoListener
1159 public void onInfo(MediaRecorder mr, int what, int extra) {
1160 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1161 if (mMediaRecorderRecording) {
1162 onStopVideoRecording();
1164 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1165 if (mMediaRecorderRecording) {
1166 onStopVideoRecording();
1170 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1171 Toast.LENGTH_LONG).show();
1176 * Make sure we're not recording music playing in the background, ask the
1177 * MediaPlaybackService to pause playback.
1179 private void pauseAudioPlayback() {
1180 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1181 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1185 public boolean isRecording() {
1186 return mMediaRecorderRecording;
1189 private void startVideoRecording() {
1190 Log.v(TAG, "startVideoRecording");
1191 mUI.cancelAnimations();
1192 mUI.setSwipingEnabled(false);
1193 mUI.showFocusUI(false);
1195 // A special case of mode options closing: during capture it should
1196 // not be possible to change mode state.
1197 mAppController.getCameraAppUI().hideModeOptions();
1198 mAppController.getCameraAppUI().animateBottomBarToCircle(R.drawable.ic_stop);
1200 mActivity.updateStorageSpaceAndHint();
1201 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1202 Log.v(TAG, "Storage issue, ignore the start request");
1207 //if (!mCameraDevice.waitDone()) return;
1208 mCurrentVideoUri = null;
1210 initializeRecorder();
1211 if (mMediaRecorder == null) {
1212 Log.e(TAG, "Fail to initialize media recorder");
1216 pauseAudioPlayback();
1219 mMediaRecorder.start(); // Recording is now started
1220 } catch (RuntimeException e) {
1221 Log.e(TAG, "Could not start media recorder. ", e);
1222 releaseMediaRecorder();
1223 // If start fails, frameworks will not lock the camera for us.
1224 mCameraDevice.lock();
1227 mAppController.getCameraAppUI().setSwipeEnabled(false);
1229 // Make sure the video recording has started before announcing
1230 // this in accessibility.
1231 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
1232 mActivity.getString(R.string.video_recording_started));
1234 // The parameters might have been altered by MediaRecorder already.
1235 // We need to force mCameraDevice to refresh before getting it.
1236 mCameraDevice.refreshParameters();
1237 // The parameters may have been changed by MediaRecorder upon starting
1238 // recording. We need to alter the parameters if we support camcorder
1239 // zoom. To reduce latency when setting the parameters during zoom, we
1240 // update mParameters here once.
1241 mParameters = mCameraDevice.getParameters();
1243 mMediaRecorderRecording = true;
1244 mActivity.lockOrientation();
1245 mRecordingStartTime = SystemClock.uptimeMillis();
1246 mUI.showRecordingUI(true);
1248 setFocusParameters();
1249 updateRecordingTime();
1250 mActivity.enableKeepScreenOn(true);
1253 private Bitmap getVideoThumbnail() {
1254 Bitmap bitmap = null;
1255 if (mVideoFileDescriptor != null) {
1256 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
1257 mDesiredPreviewWidth);
1258 } else if (mCurrentVideoUri != null) {
1260 mVideoFileDescriptor = mContentResolver.openFileDescriptor(mCurrentVideoUri, "r");
1261 bitmap = Thumbnail.createVideoThumbnailBitmap(
1262 mVideoFileDescriptor.getFileDescriptor(), mDesiredPreviewWidth);
1263 } catch (java.io.FileNotFoundException ex) {
1265 Log.e(TAG, ex.toString());
1269 if (bitmap != null) {
1270 // MetadataRetriever already rotates the thumbnail. We should rotate
1271 // it to match the UI orientation (and mirror if it is front-facing camera).
1272 CameraInfo[] info = mActivity.getCameraProvider().getCameraInfo();
1273 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
1274 bitmap = CameraUtil.rotateAndMirror(bitmap, 0, mirror);
1279 private void showCaptureResult() {
1280 mIsInReviewMode = true;
1281 Bitmap bitmap = getVideoThumbnail();
1282 if (bitmap != null) {
1283 mUI.showReviewImage(bitmap);
1285 mUI.showReviewControls();
1286 mUI.showTimeLapseUI(false);
1289 private boolean stopVideoRecording() {
1290 Log.v(TAG, "stopVideoRecording");
1291 mUI.setSwipingEnabled(true);
1292 mUI.showFocusUI(true);
1294 mAppController.getCameraAppUI().showModeOptions();
1295 mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
1297 boolean fail = false;
1298 if (mMediaRecorderRecording) {
1299 boolean shouldAddToMediaStoreNow = false;
1302 mMediaRecorder.setOnErrorListener(null);
1303 mMediaRecorder.setOnInfoListener(null);
1304 mMediaRecorder.stop();
1305 shouldAddToMediaStoreNow = true;
1306 mCurrentVideoFilename = mVideoFilename;
1307 Log.v(TAG, "stopVideoRecording: Setting current video filename: "
1308 + mCurrentVideoFilename);
1309 float duration = (SystemClock.uptimeMillis() - mRecordingStartTime) / 1000;
1310 UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.VIDEO_CAPTURE,
1311 mCurrentVideoFilename, mParameters, duration);
1312 AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
1313 mActivity.getAndroidContext().getString(R.string
1314 .video_recording_stopped));
1315 } catch (RuntimeException e) {
1316 Log.e(TAG, "stop fail", e);
1317 if (mVideoFilename != null) {
1318 deleteVideoFile(mVideoFilename);
1322 mMediaRecorderRecording = false;
1323 mActivity.unlockOrientation();
1325 // If the activity is paused, this means activity is interrupted
1326 // during recording. Release the camera as soon as possible because
1327 // face unlock or other applications may need to use the camera.
1332 mUI.showRecordingUI(false);
1333 // The orientation was fixed during video recording. Now make it
1334 // reflect the device orientation as video recording is stopped.
1335 mUI.setOrientationIndicator(0, true);
1336 mActivity.enableKeepScreenOn(false);
1337 if (shouldAddToMediaStoreNow && !fail) {
1338 if (mVideoFileDescriptor == null) {
1340 } else if (mIsVideoCaptureIntent) {
1341 // if no file save is needed, we can show the post capture UI now
1342 showCaptureResult();
1346 // release media recorder
1347 releaseMediaRecorder();
1349 setFocusParameters();
1350 mCameraDevice.lock();
1351 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1353 // Switch back to use SurfaceTexture for preview.
1357 // Update the parameters here because the parameters might have been altered
1358 // by MediaRecorder.
1360 mParameters = mCameraDevice.getParameters();
1365 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
1366 long seconds = milliSeconds / 1000; // round down to compute seconds
1367 long minutes = seconds / 60;
1368 long hours = minutes / 60;
1369 long remainderMinutes = minutes - (hours * 60);
1370 long remainderSeconds = seconds - (minutes * 60);
1372 StringBuilder timeStringBuilder = new StringBuilder();
1377 timeStringBuilder.append('0');
1379 timeStringBuilder.append(hours);
1381 timeStringBuilder.append(':');
1385 if (remainderMinutes < 10) {
1386 timeStringBuilder.append('0');
1388 timeStringBuilder.append(remainderMinutes);
1389 timeStringBuilder.append(':');
1392 if (remainderSeconds < 10) {
1393 timeStringBuilder.append('0');
1395 timeStringBuilder.append(remainderSeconds);
1398 if (displayCentiSeconds) {
1399 timeStringBuilder.append('.');
1400 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
1401 if (remainderCentiSeconds < 10) {
1402 timeStringBuilder.append('0');
1404 timeStringBuilder.append(remainderCentiSeconds);
1407 return timeStringBuilder.toString();
1410 private long getTimeLapseVideoLength(long deltaMs) {
1411 // For better approximation calculate fractional number of frames captured.
1412 // This will update the video time at a higher resolution.
1413 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
1414 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
1417 private void updateRecordingTime() {
1418 if (!mMediaRecorderRecording) {
1421 long now = SystemClock.uptimeMillis();
1422 long delta = now - mRecordingStartTime;
1424 // Starting a minute before reaching the max duration
1425 // limit, we'll countdown the remaining time instead.
1426 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
1427 && delta >= mMaxVideoDurationInMs - 60000);
1429 long deltaAdjusted = delta;
1430 if (countdownRemainingTime) {
1431 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
1435 long targetNextUpdateDelay;
1436 if (!mCaptureTimeLapse) {
1437 text = millisecondToTimeString(deltaAdjusted, false);
1438 targetNextUpdateDelay = 1000;
1440 // The length of time lapse video is different from the length
1441 // of the actual wall clock time elapsed. Display the video length
1442 // only in format hh:mm:ss.dd, where dd are the centi seconds.
1443 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
1444 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
1447 mUI.setRecordingTime(text);
1449 if (mRecordingTimeCountsDown != countdownRemainingTime) {
1450 // Avoid setting the color on every update, do it only
1451 // when it needs changing.
1452 mRecordingTimeCountsDown = countdownRemainingTime;
1454 int color = mActivity.getResources().getColor(countdownRemainingTime
1455 ? R.color.recording_time_remaining_text
1456 : R.color.recording_time_elapsed_text);
1458 mUI.setRecordingTimeTextColor(color);
1461 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
1462 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay);
1465 private static boolean isSupported(String value, List<String> supported) {
1466 return supported == null ? false : supported.indexOf(value) >= 0;
1469 @SuppressWarnings("deprecation")
1470 private void setCameraParameters() {
1471 SettingsManager settingsManager = mActivity.getSettingsManager();
1473 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
1474 mParameters.set("video-size", mProfile.videoFrameWidth+"x"+mProfile.videoFrameHeight);
1475 int[] fpsRange = CameraUtil.getMaxPreviewFpsRange(mParameters);
1476 if (fpsRange.length > 0) {
1477 mParameters.setPreviewFpsRange(
1478 fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1479 fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1481 mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
1484 enableTorchMode(settingsManager.isCameraBackFacing());
1486 // Set white balance parameter.
1487 String whiteBalance = settingsManager.get(SettingsManager.SETTING_WHITE_BALANCE);
1488 if (isSupported(whiteBalance,
1489 mParameters.getSupportedWhiteBalance())) {
1490 mParameters.setWhiteBalance(whiteBalance);
1492 whiteBalance = mParameters.getWhiteBalance();
1493 if (whiteBalance == null) {
1494 whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1499 if (mParameters.isZoomSupported()) {
1500 mParameters.setZoom(mZoomValue);
1502 updateFocusParameters();
1504 mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.TRUE);
1506 // Enable video stabilization. Convenience methods not available in API
1508 String vstabSupported = mParameters.get("video-stabilization-supported");
1509 if ("true".equals(vstabSupported)) {
1510 mParameters.set("video-stabilization", "true");
1513 // Set picture size.
1514 // The logic here is different from the logic in still-mode camera.
1515 // There we determine the preview size based on the picture size, but
1516 // here we determine the picture size based on the preview size.
1517 List<Size> supported = mParameters.getSupportedPictureSizes();
1518 Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
1519 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
1520 Size original = mParameters.getPictureSize();
1521 if (!original.equals(optimalSize)) {
1522 mParameters.setPictureSize(optimalSize.width, optimalSize.height);
1524 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
1525 optimalSize.height);
1527 // Set JPEG quality.
1528 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1529 CameraProfile.QUALITY_HIGH);
1530 mParameters.setJpegQuality(jpegQuality);
1532 mCameraDevice.setParameters(mParameters);
1533 // Nexus 5 through KitKat 4.4.2 requires a second call to
1534 // .setParameters() for frame rate settings to take effect.
1535 mCameraDevice.setParameters(mParameters);
1537 // Update UI based on the new parameters.
1538 mUI.updateOnScreenIndicators(mParameters);
1541 private void updateFocusParameters() {
1542 // Set continuous autofocus. During recording, we use "continuous-video"
1543 // auto focus mode to ensure smooth focusing. Whereas during preview (i.e.
1544 // before recording starts) we use "continuous-picture" auto focus mode
1545 // for faster but slightly jittery focusing.
1546 List<String> supportedFocus = mParameters.getSupportedFocusModes();
1547 if (mMediaRecorderRecording) {
1548 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
1549 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1550 mFocusManager.overrideFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
1552 mFocusManager.overrideFocusMode(null);
1555 mFocusManager.overrideFocusMode(null);
1556 if (isSupported(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE, supportedFocus)) {
1557 mParameters.setFocusMode(mFocusManager.getFocusMode());
1558 if (mFocusAreaSupported) {
1559 mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1563 updateAutoFocusMoveCallback();
1567 public void resume() {
1569 installIntentFilter();
1570 mUI.enableShutter(false);
1573 showVideoSnapshotUI(false);
1576 requestCamera(mCameraId);
1578 // preview already started
1579 mUI.enableShutter(true);
1582 if (mFocusManager != null) {
1583 // If camera is not open when resume is called, focus manager will not
1584 // be initialized yet, in which case it will start listening to
1585 // preview area size change later in the initialization.
1586 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1589 // Initialize location service.
1590 mActivity.syncLocationManagerSetting();
1593 mOnResumeTime = SystemClock.uptimeMillis();
1594 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
1597 UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.VIDEO_CAPTURE,
1598 eventprotos.CameraEvent.InteractionCause.BUTTON);
1599 getServices().getMemoryManager().addListener(this);
1603 public void pause() {
1606 if (mFocusManager != null) {
1607 // If camera is not open when resume is called, focus manager will not
1608 // be initialized yet, in which case it will start listening to
1609 // preview area size change later in the initialization.
1610 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1611 mFocusManager.removeMessages();
1613 if (mMediaRecorderRecording) {
1614 // Camera will be released in onStopVideoRecording.
1615 onStopVideoRecording();
1619 releaseMediaRecorder();
1622 closeVideoFileDescriptor();
1624 if (mReceiver != null) {
1625 mActivity.unregisterReceiver(mReceiver);
1629 if (mLocationManager != null) {
1630 mLocationManager.recordLocation(false);
1633 mHandler.removeMessages(MSG_CHECK_DISPLAY_ROTATION);
1634 mHandler.removeMessages(MSG_SWITCH_CAMERA);
1635 mHandler.removeMessages(MSG_SWITCH_CAMERA_START_ANIMATION);
1636 mPendingSwitchCameraId = -1;
1637 mSwitchingCamera = false;
1638 mPreferenceRead = false;
1639 getServices().getMemoryManager().removeListener(this);
1643 public void destroy() {
1648 public void onLayoutOrientationChanged(boolean isLandscape) {
1649 setDisplayOrientation();
1652 // TODO: integrate this into the SettingsManager listeners.
1653 public void onSharedPreferenceChanged() {
1657 private void switchCamera() {
1661 SettingsManager settingsManager = mActivity.getSettingsManager();
1663 Log.d(TAG, "Start to switch camera.");
1664 mCameraId = mPendingSwitchCameraId;
1665 mPendingSwitchCameraId = -1;
1666 settingsManager.set(SettingsManager.SETTING_CAMERA_ID, "" + mCameraId);
1668 if (mFocusManager != null) {
1669 mFocusManager.removeMessages();
1672 requestCamera(mCameraId);
1674 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
1675 mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
1676 if (mFocusManager != null) {
1677 mFocusManager.setMirror(mMirror);
1682 mUI.setOrientationIndicator(0, false);
1684 // Start switch camera animation. Post a message because
1685 // onFrameAvailable from the old camera may already exist.
1686 mHandler.sendEmptyMessage(MSG_SWITCH_CAMERA_START_ANIMATION);
1687 mUI.updateOnScreenIndicators(mParameters);
1690 private void initializeVideoSnapshot() {
1691 if (mParameters == null) {
1696 void showVideoSnapshotUI(boolean enabled) {
1697 if (mParameters == null) {
1700 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
1704 mUI.showPreviewBorder(enabled);
1706 mUI.enableShutter(!enabled);
1711 * Used to update the flash mode. Video mode can turn on the flash as torch
1712 * mode, which we would like to turn on and off when we switching in and
1713 * out to the preview.
1715 * @param enable Whether torch mode can be enabled.
1717 private void enableTorchMode(boolean enable) {
1718 if (mParameters.getFlashMode() == null) {
1722 SettingsManager settingsManager = mActivity.getSettingsManager();
1726 flashMode = settingsManager.get(SettingsManager.SETTING_VIDEOCAMERA_FLASH_MODE);
1728 flashMode = Parameters.FLASH_MODE_OFF;
1730 List<String> supportedFlash = mParameters.getSupportedFlashModes();
1731 if (isSupported(flashMode, supportedFlash)) {
1732 mParameters.setFlashMode(flashMode);
1734 flashMode = mParameters.getFlashMode();
1735 if (flashMode == null) {
1736 flashMode = mActivity.getString(
1737 R.string.pref_camera_flashmode_no_flash);
1738 mParameters.setFlashMode(flashMode);
1741 mCameraDevice.setParameters(mParameters);
1742 mUI.updateOnScreenIndicators(mParameters);
1746 public void onPreviewVisibilityChanged(int visibility) {
1748 enableTorchMode(visibility == ModuleController.VISIBILITY_VISIBLE);
1752 private final class JpegPictureCallback implements CameraPictureCallback {
1755 public JpegPictureCallback(Location loc) {
1760 public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
1761 Log.v(TAG, "onPictureTaken");
1762 mSnapshotInProgress = false;
1763 showVideoSnapshotUI(false);
1764 storeImage(jpegData, mLocation);
1768 private void storeImage(final byte[] data, Location loc) {
1769 long dateTaken = System.currentTimeMillis();
1770 String title = CameraUtil.createJpegName(dateTaken);
1771 ExifInterface exif = Exif.getExif(data);
1772 int orientation = Exif.getOrientation(exif);
1774 getServices().getMediaSaver().addImage(
1775 data, title, dateTaken, loc, orientation,
1776 exif, mOnPhotoSavedListener, mContentResolver);
1779 private String convertOutputFormatToMimeType(int outputFileFormat) {
1780 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1783 return "video/3gpp";
1786 private String convertOutputFormatToFileExt(int outputFileFormat) {
1787 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
1793 private void closeVideoFileDescriptor() {
1794 if (mVideoFileDescriptor != null) {
1796 mVideoFileDescriptor.close();
1797 } catch (IOException e) {
1798 Log.e(TAG, "Fail to close fd", e);
1800 mVideoFileDescriptor = null;
1805 public void onPreviewUIReady() {
1810 public void onPreviewUIDestroyed() {
1815 public void startPreCaptureAnimation() {
1816 mAppController.startPreCaptureAnimation();
1819 private void requestCamera(int id) {
1820 mActivity.getCameraProvider().requestCamera(id);
1824 public void onMemoryStateChanged(int state) {
1825 setShutterEnabled(state == MemoryManager.STATE_OK);
1829 public void onLowMemory() {
1830 // Not much we can do in the video module.
1833 private void setShutterEnabled(boolean enabled) {
1834 mShutterEnabled = enabled;
1835 mUI.enableShutter(enabled);
1838 /***********************FocusOverlayManager Listener****************************/
1840 public void autoFocus() {
1841 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1845 public void cancelAutoFocus() {
1846 mCameraDevice.cancelAutoFocus();
1847 setFocusParameters();
1851 public boolean capture() {
1856 public void startFaceDetection() {
1861 public void stopFaceDetection() {
1866 public void setFocusParameters() {
1867 updateFocusParameters();
1868 mCameraDevice.setParameters(mParameters);