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 ShutterButton.OnShutterButtonListener,
88 MediaRecorder.OnErrorListener,
89 MediaRecorder.OnInfoListener, FocusOverlayManager.Listener {
91 private static final String TAG = "VideoModule";
93 // Messages defined for the UI thread handler.
94 private static final int MSG_CHECK_DISPLAY_ROTATION = 4;
95 private static final int MSG_UPDATE_RECORD_TIME = 5;
96 private static final int MSG_ENABLE_SHUTTER_BUTTON = 6;
97 private static final int MSG_SWITCH_CAMERA = 8;
98 private static final int MSG_SWITCH_CAMERA_START_ANIMATION = 9;
100 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
103 * An unpublished intent flag requesting to start recording straight away
104 * and return as soon as recording is stopped.
105 * TODO: consider publishing by moving into MediaStore.
107 private static final String EXTRA_QUICK_CAPTURE =
108 "android.intent.extra.quickCapture";
111 private CameraActivity mActivity;
112 private boolean mPaused;
113 private int mCameraId;
114 private Parameters mParameters;
116 private boolean mIsInReviewMode;
117 private boolean mSnapshotInProgress = false;
119 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
121 // Preference must be read before starting preview. We check this before starting
123 private boolean mPreferenceRead;
125 private boolean mIsVideoCaptureIntent;
126 private boolean mQuickCapture;
128 private MediaRecorder mMediaRecorder;
130 private boolean mSwitchingCamera;
131 private boolean mMediaRecorderRecording = false;
132 private long mRecordingStartTime;
133 private boolean mRecordingTimeCountsDown = false;
134 private long mOnResumeTime;
135 // The video file that the hardware camera is about to record into
136 // (or is recording into.
137 private String mVideoFilename;
138 private ParcelFileDescriptor mVideoFileDescriptor;
140 // The video file that has already been recorded, and that is being
141 // examined by the user.
142 private String mCurrentVideoFilename;
143 private Uri mCurrentVideoUri;
144 private boolean mCurrentVideoUriFromMediaSaved;
145 private ContentValues mCurrentVideoValues;
147 private CamcorderProfile mProfile;
149 // The video duration limit. 0 means no limit.
150 private int mMaxVideoDurationInMs;
152 // Time Lapse parameters.
153 private boolean mCaptureTimeLapse = false;
154 // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
155 private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
157 boolean mPreviewing = false; // True if preview is started.
158 // The display rotation in degrees. This is only valid when mPreviewing is
160 private int mDisplayRotation;
161 private int mCameraDisplayOrientation;
162 private AppController mAppController;
164 private int mDesiredPreviewWidth;
165 private int mDesiredPreviewHeight;
166 private ContentResolver mContentResolver;
168 private LocationManager mLocationManager;
170 private int mPendingSwitchCameraId;
171 private final Handler mHandler = new MainHandler();
173 private CameraProxy mCameraDevice;
175 // The degrees of the device rotated clockwise from its natural orientation.
176 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
178 private int mZoomValue; // The current zoom value.
180 private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
181 new MediaSaver.OnMediaSavedListener() {
183 public void onMediaSaved(Uri uri) {
185 mCurrentVideoUri = uri;
186 mCurrentVideoUriFromMediaSaved = true;
188 mActivity.notifyNewMedia(uri);
193 private final MediaSaver.OnMediaSavedListener mOnPhotoSavedListener =
194 new MediaSaver.OnMediaSavedListener() {
196 public void onMediaSaved(Uri uri) {
198 mActivity.notifyNewMedia(uri);
202 private FocusOverlayManager mFocusManager;
203 private boolean mMirror;
204 private Parameters mInitialParams;
205 private boolean mFocusAreaSupported;
206 private boolean mMeteringAreaSupported;
208 private final CameraManager.CameraAFCallback mAutoFocusCallback =
209 new CameraManager.CameraAFCallback() {
211 public void onAutoFocus(boolean focused, CameraProxy camera) {
215 mFocusManager.onAutoFocus(focused, false);
219 private final Object mAutoFocusMoveCallback =
220 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
221 ? new CameraManager.CameraAFMoveCallback() {
223 public void onAutoFocusMoving(boolean moving, CameraProxy camera) {
224 mFocusManager.onAutoFocusMoving(moving);
229 * This Handler is used to post message back onto the main thread of the
232 private class MainHandler extends Handler {
234 public void handleMessage(Message msg) {
237 case MSG_ENABLE_SHUTTER_BUTTON:
238 mUI.enableShutter(true);
241 case MSG_UPDATE_RECORD_TIME: {
242 updateRecordingTime();
246 case MSG_CHECK_DISPLAY_ROTATION: {
247 // Restart the preview if display rotation has changed.
248 // Sometimes this happens when the device is held upside
249 // down and camera app is opened. Rotation animation will
250 // take some time and the rotation value we have got may be
251 // wrong. Framework does not have a callback for this now.
252 if ((CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation)
253 && !mMediaRecorderRecording && !mSwitchingCamera) {
256 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
257 mHandler.sendEmptyMessageDelayed(MSG_CHECK_DISPLAY_ROTATION, 100);
262 case MSG_SWITCH_CAMERA: {
267 case MSG_SWITCH_CAMERA_START_ANIMATION: {
269 //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
271 // Enable all camera controls.
272 mSwitchingCamera = false;
277 Log.v(TAG, "Unhandled message: " + msg.what);
283 private BroadcastReceiver mReceiver = null;
285 /** Whether shutter is enabled. */
286 private boolean mShutterEnabled;
288 private class MyBroadcastReceiver extends BroadcastReceiver {
290 public void onReceive(Context context, Intent intent) {
291 String action = intent.getAction();
292 if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
293 stopVideoRecording();
294 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
295 Toast.makeText(mActivity,
296 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
301 private int mShutterIconId;
305 * Construct a new video module.
307 public VideoModule(AppController app) {
311 private String createName(long dateTaken) {
312 Date date = new Date(dateTaken);
313 SimpleDateFormat dateFormat = new SimpleDateFormat(
314 mActivity.getString(R.string.video_file_name_format));
316 return dateFormat.format(date);
320 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
321 mActivity = activity;
322 // TODO: Need to look at the controller interface to see if we can get
323 // rid of passing in the activity directly.
324 mAppController = mActivity;
325 mUI = new VideoUI(mActivity, this, mActivity.getModuleLayoutRoot());
326 mActivity.setPreviewStatusListener(mUI);
327 mActivity.getCameraAppUI().setBottomBarShutterListener(this);
329 SettingsManager settingsManager = mActivity.getSettingsManager();
330 mCameraId = Integer.parseInt(settingsManager.get(SettingsManager.SETTING_CAMERA_ID));
333 * To reduce startup time, we start the preview in another thread.
334 * We make sure the preview is started at the end of onCreate.
336 requestCamera(mCameraId);
338 mContentResolver = mActivity.getContentResolver();
340 // Surface texture is from camera screen nail and startPreview needs it.
341 // This must be done before startPreview.
342 mIsVideoCaptureIntent = isVideoCaptureIntent();
344 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
345 mLocationManager = mActivity.getLocationManager();
347 mUI.setOrientationIndicator(0, false);
348 setDisplayOrientation();
350 mUI.showTimeLapseUI(mCaptureTimeLapse);
351 mPendingSwitchCameraId = -1;
353 mShutterIconId = CameraUtil.getCameraShutterIconId(
354 mAppController.getCurrentModuleIndex(), mAppController.getAndroidContext());
359 public boolean isUsingBottomBar() {
363 private void initializeControlByIntent() {
364 if (isVideoCaptureIntent()) {
365 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
370 public void onSingleTapUp(View view, int x, int y) {
371 if (mPaused || mCameraDevice == null) {
374 // Check if metering area or focus area is supported.
375 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
379 mFocusManager.onSingleTapUp(x, y);
382 private void takeASnapshot() {
383 // Only take snapshots if video snapshot is supported by device
384 if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
385 if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress || mShutterEnabled) {
389 // Set rotation and gps data.
390 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
391 int rotation = CameraUtil.getJpegRotation(info, mOrientation);
392 mParameters.setRotation(rotation);
393 Location loc = mLocationManager.getCurrentLocation();
394 CameraUtil.setGpsParameters(mParameters, loc);
395 mCameraDevice.setParameters(mParameters);
397 Log.v(TAG, "Video snapshot start");
398 mCameraDevice.takePicture(mHandler,
399 null, null, null, new JpegPictureCallback(loc));
400 showVideoSnapshotUI(true);
401 mSnapshotInProgress = true;
402 UsageStatistics.captureEvent(eventprotos.NavigationChange.Mode.VIDEO_STILL,
407 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
408 private void updateAutoFocusMoveCallback() {
413 if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
414 mCameraDevice.setAutoFocusMoveCallback(mHandler,
415 (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
417 mCameraDevice.setAutoFocusMoveCallback(null, null);
422 * The focus manager gets initialized after camera is available.
424 private void initializeFocusManager() {
425 // Create FocusManager object. startPreview needs it.
426 // if mFocusManager not null, reuse it
427 // otherwise create a new instance
428 if (mFocusManager != null) {
429 mFocusManager.removeMessages();
431 CameraInfo info = mAppController.getCameraProvider().getCameraInfo()[mCameraId];
432 mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
433 String[] defaultFocusModes = mActivity.getResources().getStringArray(
434 R.array.pref_camera_focusmode_default_array);
435 mFocusManager = new FocusOverlayManager(mActivity.getSettingsManager(),
437 mInitialParams, this, mMirror,
438 mActivity.getMainLooper(), mUI.getFocusUI());
440 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
444 public void onOrientationChanged(int orientation) {
445 // We keep the last known orientation. So if the user first orient
446 // the camera then point the camera to floor or sky, we still have
447 // the correct orientation.
448 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
451 int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
453 if (mOrientation != newOrientation) {
454 mOrientation = newOrientation;
459 private final ButtonManager.ButtonCallback mFlashCallback =
460 new ButtonManager.ButtonCallback() {
462 public void onStateChanged(int state) {
463 // Update flash parameters.
464 enableTorchMode(true);
468 private final ButtonManager.ButtonCallback mCameraCallback =
469 new ButtonManager.ButtonCallback() {
471 public void onStateChanged(int state) {
472 if (mPaused || mPendingSwitchCameraId != -1) {
475 mPendingSwitchCameraId = state;
476 Log.d(TAG, "Start to copy texture.");
478 // Disable all camera controls.
479 mSwitchingCamera = true;
484 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
486 public void onClick(View v) {
487 onReviewCancelClicked(v);
491 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
493 public void onClick(View v) {
494 onReviewDoneClicked(v);
497 private final View.OnClickListener mReviewCallback = new View.OnClickListener() {
499 public void onClick(View v) {
500 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
501 onReviewPlayClicked(v);
506 public HardwareSpec getHardwareSpec() {
507 return (mParameters != null ? new HardwareSpecImpl(mParameters) : null);
511 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
512 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
514 bottomBarSpec.enableCamera = true;
515 bottomBarSpec.cameraCallback = mCameraCallback;
516 bottomBarSpec.enableTorchFlash = true;
517 bottomBarSpec.flashCallback = mFlashCallback;
518 bottomBarSpec.hideHdr = true;
519 bottomBarSpec.hideGridLines = true;
521 if (isVideoCaptureIntent()) {
522 bottomBarSpec.showCancel = true;
523 bottomBarSpec.cancelCallback = mCancelCallback;
524 bottomBarSpec.showDone = true;
525 bottomBarSpec.doneCallback = mDoneCallback;
526 bottomBarSpec.showReview = true;
527 bottomBarSpec.reviewCallback = mReviewCallback;
530 return bottomBarSpec;
534 public void onCameraAvailable(CameraProxy cameraProxy) {
535 mCameraDevice = cameraProxy;
536 mInitialParams = mCameraDevice.getParameters();
537 mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
538 mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
539 readVideoPreferences();
540 resizeForPreviewAspectRatio();
541 initializeFocusManager();
544 initializeVideoSnapshot();
545 mUI.initializeZoom(mParameters);
546 initializeControlByIntent();
549 private void startPlayVideoActivity() {
550 Intent intent = new Intent(Intent.ACTION_VIEW);
551 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
554 .startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
555 } catch (ActivityNotFoundException ex) {
556 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
562 public void onReviewPlayClicked(View v) {
563 startPlayVideoActivity();
568 public void onReviewDoneClicked(View v) {
569 mIsInReviewMode = false;
570 doReturnToCaller(true);
575 public void onReviewCancelClicked(View v) {
576 // TODO: It should be better to not even insert the URI at all before we
577 // confirm done in review, which means we need to handle temporary video
578 // files in a quite different way than we currently had.
579 // Make sure we don't delete the Uri sent from the video capture intent.
580 if (mCurrentVideoUriFromMediaSaved) {
581 mContentResolver.delete(mCurrentVideoUri, null, null);
583 mIsInReviewMode = false;
584 doReturnToCaller(false);
588 public boolean isInReviewMode() {
589 return mIsInReviewMode;
592 private void onStopVideoRecording() {
593 mAppController.getCameraAppUI().setSwipeEnabled(true);
594 boolean recordFail = stopVideoRecording();
595 if (mIsVideoCaptureIntent) {
597 doReturnToCaller(!recordFail);
598 } else if (!recordFail) {
601 } else if (!recordFail){
602 // Start capture animation.
603 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
604 // The capture animation is disabled on ICS because we use SurfaceView
605 // for preview during recording. When the recording is done, we switch
606 // back to use SurfaceTexture for preview and we need to stop then start
607 // the preview. This will cause the preview flicker since the preview
608 // will not be continuous for a short period of time.
615 public void onVideoSaved() {
616 if (mIsVideoCaptureIntent) {
621 public void onProtectiveCurtainClick(View v) {
626 public void onShutterButtonClick() {
627 if (mSwitchingCamera) {
630 boolean stop = mMediaRecorderRecording;
633 onStopVideoRecording();
635 startVideoRecording();
637 mUI.enableShutter(false);
638 mFocusManager.onShutterUp();
640 // Keep the shutter button disabled when in video capture intent
641 // mode and recording is stopped. It'll be re-enabled when
642 // re-take button is clicked.
643 if (!(mIsVideoCaptureIntent && stop)) {
644 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
649 public void onShutterButtonFocus(boolean pressed) {
650 // TODO: Remove this when old camera controls are removed from the UI.
653 private void readVideoPreferences() {
654 // The preference stores values from ListPreference and is thus string type for all values.
655 // We need to convert it to int manually.
656 SettingsManager settingsManager = mActivity.getSettingsManager();
657 if (!settingsManager.isSet(SettingsManager.SETTING_VIDEO_QUALITY)) {
658 settingsManager.setDefault(SettingsManager.SETTING_VIDEO_QUALITY);
660 String videoQuality = settingsManager.get(SettingsManager.SETTING_VIDEO_QUALITY);
661 int quality = SettingsUtil.getVideoQuality(videoQuality, mCameraId);
662 Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality);
664 // Set video quality.
665 Intent intent = mActivity.getIntent();
666 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
667 int extraVideoQuality =
668 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
669 if (extraVideoQuality > 0) {
670 quality = CamcorderProfile.QUALITY_HIGH;
671 } else { // 0 is mms.
672 quality = CamcorderProfile.QUALITY_LOW;
676 // Set video duration limit. The limit is read from the preference,
677 // unless it is specified in the intent.
678 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
680 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
681 mMaxVideoDurationInMs = 1000 * seconds;
683 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
686 // Read time lapse recording interval.
687 String frameIntervalStr = settingsManager.get(
688 SettingsManager.SETTING_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
689 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
690 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
691 // TODO: This should be checked instead directly +1000.
692 if (mCaptureTimeLapse) {
696 // If quality is not supported, request QUALITY_HIGH which is always supported.
697 if (CamcorderProfile.hasProfile(mCameraId, quality) == false) {
698 quality = CamcorderProfile.QUALITY_HIGH;
700 mProfile = CamcorderProfile.get(mCameraId, quality);
701 getDesiredPreviewSize();
702 mPreferenceRead = true;
705 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
706 private void getDesiredPreviewSize() {
707 if (mCameraDevice == null) {
710 mParameters = mCameraDevice.getParameters();
711 if (mParameters.getSupportedVideoSizes() == null) {
712 mDesiredPreviewWidth = mProfile.videoFrameWidth;
713 mDesiredPreviewHeight = mProfile.videoFrameHeight;
714 } else { // Driver supports separates outputs for preview and video.
715 List<Size> sizes = mParameters.getSupportedPreviewSizes();
716 Size preferred = mParameters.getPreferredPreviewSizeForVideo();
717 int product = preferred.width * preferred.height;
718 Iterator<Size> it = sizes.iterator();
719 // Remove the preview sizes that are not preferred.
720 while (it.hasNext()) {
721 Size size = it.next();
722 if (size.width * size.height > product) {
726 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
727 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
728 mDesiredPreviewWidth = optimalSize.width;
729 mDesiredPreviewHeight = optimalSize.height;
731 mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
732 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
733 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
736 private void resizeForPreviewAspectRatio() {
737 mUI.setAspectRatio((float) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
740 private void installIntentFilter() {
741 // install an intent filter to receive SD card related events.
742 IntentFilter intentFilter =
743 new IntentFilter(Intent.ACTION_MEDIA_EJECT);
744 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
745 intentFilter.addDataScheme("file");
746 mReceiver = new MyBroadcastReceiver();
747 mActivity.registerReceiver(mReceiver, intentFilter);
750 private void setDisplayOrientation() {
751 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
752 mCameraDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
753 // Change the camera display orientation
754 if (mCameraDevice != null) {
755 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
757 if (mFocusManager != null) {
758 mFocusManager.setDisplayOrientation(mCameraDisplayOrientation);
763 public void updateCameraOrientation() {
764 if (mMediaRecorderRecording) {
767 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
768 setDisplayOrientation();
773 public void updatePreviewAspectRatio(float aspectRatio) {
774 mAppController.updatePreviewAspectRatio(aspectRatio);
778 public int onZoomChanged(int index) {
779 // Not useful to change zoom value when the activity is paused.
784 if (mParameters == null || mCameraDevice == null) {
787 // Set zoom parameters asynchronously
788 mParameters.setZoom(mZoomValue);
789 mCameraDevice.setParameters(mParameters);
790 Parameters p = mCameraDevice.getParameters();
797 private void startPreview() {
798 Log.v(TAG, "startPreview");
800 SurfaceTexture surfaceTexture = mActivity.getCameraAppUI().getSurfaceTexture();
801 if (!mPreferenceRead || surfaceTexture == null || mPaused == true ||
802 mCameraDevice == null) {
806 mCameraDevice.setErrorCallback(mErrorCallback);
807 if (mPreviewing == true) {
811 setDisplayOrientation();
812 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
813 setCameraParameters();
815 if (mFocusManager != null) {
816 // If the focus mode is continuous autofocus, call cancelAutoFocus
817 // to resume it because it may have been paused by autoFocus call.
818 String focusMode = mFocusManager.getFocusMode();
819 if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
820 mCameraDevice.cancelAutoFocus();
824 // This is to notify app controller that preview will start next, so app
825 // controller can set preview callbacks if needed. This has to happen before
826 // preview is started as a workaround of the framework issue related to preview
827 // callbacks that causes preview stretch and crash. (More details see b/12210027
829 mAppController.onPreviewReadyToStart();
831 mCameraDevice.setPreviewTexture(surfaceTexture);
832 mCameraDevice.startPreview();
835 } catch (Throwable ex) {
837 throw new RuntimeException("startPreview failed", ex);
841 private void onPreviewStarted() {
842 mUI.enableShutter(true);
843 mAppController.onPreviewStarted();
844 if (mFocusManager != null) {
845 mFocusManager.onPreviewStarted();
850 public void stopPreview() {
854 mCameraDevice.stopPreview();
855 if (mFocusManager != null) {
856 mFocusManager.onPreviewStopped();
861 private void closeCamera() {
862 Log.v(TAG, "closeCamera");
863 if (mCameraDevice == null) {
864 Log.d(TAG, "already stopped.");
867 mCameraDevice.setZoomChangeListener(null);
868 mCameraDevice.setErrorCallback(null);
869 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
870 mCameraDevice = null;
872 mSnapshotInProgress = false;
873 if (mFocusManager != null) {
874 mFocusManager.onCameraReleased();
879 public boolean onBackPressed() {
883 if (mMediaRecorderRecording) {
884 onStopVideoRecording();
892 public boolean onKeyDown(int keyCode, KeyEvent event) {
893 // Do not handle any key if the activity is paused.
899 case KeyEvent.KEYCODE_CAMERA:
900 if (event.getRepeatCount() == 0) {
905 case KeyEvent.KEYCODE_DPAD_CENTER:
906 if (event.getRepeatCount() == 0) {
911 case KeyEvent.KEYCODE_MENU:
912 if (mMediaRecorderRecording) {
921 public boolean onKeyUp(int keyCode, KeyEvent event) {
923 case KeyEvent.KEYCODE_CAMERA:
924 mUI.pressShutter(false);
931 public boolean isVideoCaptureIntent() {
932 String action = mActivity.getIntent().getAction();
933 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
936 private void doReturnToCaller(boolean valid) {
937 Intent resultIntent = new Intent();
940 resultCode = Activity.RESULT_OK;
941 resultIntent.setData(mCurrentVideoUri);
943 resultCode = Activity.RESULT_CANCELED;
945 mActivity.setResultEx(resultCode, resultIntent);
949 private void cleanupEmptyFile() {
950 if (mVideoFilename != null) {
951 File f = new File(mVideoFilename);
952 if (f.length() == 0 && f.delete()) {
953 Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
954 mVideoFilename = null;
959 // Prepares media recorder.
960 private void initializeRecorder() {
961 Log.v(TAG, "initializeRecorder");
962 // If the mCameraDevice is null, then this activity is going to finish
963 if (mCameraDevice == null) {
967 Intent intent = mActivity.getIntent();
968 Bundle myExtras = intent.getExtras();
970 long requestedSizeLimit = 0;
971 closeVideoFileDescriptor();
972 mCurrentVideoUriFromMediaSaved = false;
973 if (mIsVideoCaptureIntent && myExtras != null) {
974 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
975 if (saveUri != null) {
977 mVideoFileDescriptor =
978 mContentResolver.openFileDescriptor(saveUri, "rw");
979 mCurrentVideoUri = saveUri;
980 } catch (java.io.FileNotFoundException ex) {
982 Log.e(TAG, ex.toString());
985 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
987 mMediaRecorder = new MediaRecorder();
989 // Unlock the camera object before passing it to media recorder.
990 mCameraDevice.unlock();
991 mMediaRecorder.setCamera(mCameraDevice.getCamera());
992 if (!mCaptureTimeLapse) {
993 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
995 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
996 mMediaRecorder.setProfile(mProfile);
997 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
998 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
999 if (mCaptureTimeLapse) {
1000 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
1001 setCaptureRate(mMediaRecorder, fps);
1004 setRecordLocation();
1007 // Try Uri in the intent first. If it doesn't exist, use our own
1009 if (mVideoFileDescriptor != null) {
1010 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1012 generateVideoFilename(mProfile.fileFormat);
1013 mMediaRecorder.setOutputFile(mVideoFilename);
1016 // Set maximum file size.
1017 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
1018 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1019 maxFileSize = requestedSizeLimit;
1023 mMediaRecorder.setMaxFileSize(maxFileSize);
1024 } catch (RuntimeException exception) {
1025 // We are going to ignore failure of setMaxFileSize here, as
1026 // a) The composer selected may simply not support it, or
1027 // b) The underlying media framework may not handle 64-bit range
1028 // on the size restriction.
1031 // See android.hardware.Camera.Parameters.setRotation for
1033 // Note that mOrientation here is the device orientation, which is the opposite of
1034 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
1035 // which is the orientation the graphics need to rotate in order to render correctly.
1037 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
1038 CameraInfo info = mActivity.getCameraProvider().getCameraInfo()[mCameraId];
1039 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
1040 rotation = (info.orientation - mOrientation + 360) % 360;
1041 } else { // back-facing camera
1042 rotation = (info.orientation + mOrientation) % 360;
1045 mMediaRecorder.setOrientationHint(rotation);
1048 mMediaRecorder.prepare();
1049 } catch (IOException e) {
1050 Log.e(TAG, "prepare failed for " + mVideoFilename, e);
1051 releaseMediaRecorder();
1052 throw new RuntimeException(e);
1055 mMediaRecorder.setOnErrorListener(this);
1056 mMediaRecorder.setOnInfoListener(this);
1059 private static void setCaptureRate(MediaRecorder recorder, double fps) {
1060 recorder.setCaptureRate(fps);
1063 private void setRecordLocation() {
1064 Location loc = mLocationManager.getCurrentLocation();
1066 mMediaRecorder.setLocation((float) loc.getLatitude(),
1067 (float) loc.getLongitude());
1071 private void releaseMediaRecorder() {
1072 Log.v(TAG, "Releasing media recorder.");
1073 if (mMediaRecorder != null) {
1075 mMediaRecorder.reset();
1076 mMediaRecorder.release();
1077 mMediaRecorder = null;
1079 mVideoFilename = null;
1082 private void generateVideoFilename(int outputFileFormat) {
1083 long dateTaken = System.currentTimeMillis();
1084 String title = createName(dateTaken);
1085 // Used when emailing.
1086 String filename = title + convertOutputFormatToFileExt(outputFileFormat);
1087 String mime = convertOutputFormatToMimeType(outputFileFormat);
1088 String path = Storage.DIRECTORY + '/' + filename;
1089 String tmpPath = path + ".tmp";
1090 mCurrentVideoValues = new ContentValues(9);
1091 mCurrentVideoValues.put(Video.Media.TITLE, title);
1092 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
1093 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
1094 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
1095 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
1096 mCurrentVideoValues.put(Video.Media.DATA, path);
1097 mCurrentVideoValues.put(Video.Media.RESOLUTION,
1098 Integer.toString(mProfile.videoFrameWidth) + "x" +
1099 Integer.toString(mProfile.videoFrameHeight));
1100 Location loc = mLocationManager.getCurrentLocation();
1102 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
1103 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
1105 mVideoFilename = tmpPath;
1106 Log.v(TAG, "New video filename: " + mVideoFilename);
1109 private void saveVideo() {
1110 if (mVideoFileDescriptor == null) {
1111 long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
1113 if (mCaptureTimeLapse) {
1114 duration = getTimeLapseVideoLength(duration);
1117 Log.w(TAG, "Video duration <= 0 : " + duration);
1119 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
1120 duration, mCurrentVideoValues,
1121 mOnVideoSavedListener, mContentResolver);
1123 mCurrentVideoValues = null;
1126 private void deleteVideoFile(String fileName) {
1127 Log.v(TAG, "Deleting video " + fileName);
1128 File f = new File(fileName);
1130 Log.v(TAG, "Could not delete " + fileName);
1134 private PreferenceGroup filterPreferenceScreenByIntent(
1135 PreferenceGroup screen) {
1136 Intent intent = mActivity.getIntent();
1137 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
1138 CameraSettings.removePreferenceFromScreen(screen, CameraSettings.KEY_VIDEO_QUALITY);
1141 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
1142 CameraSettings.removePreferenceFromScreen(screen,
1143 CameraSettings.KEY_VIDEO_QUALITY);
1148 // from MediaRecorder.OnErrorListener
1150 public void onError(MediaRecorder mr, int what, int extra) {
1151 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
1152 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
1153 // We may have run out of space on the sdcard.
1154 stopVideoRecording();
1155 mActivity.updateStorageSpaceAndHint();
1159 // from MediaRecorder.OnInfoListener
1161 public void onInfo(MediaRecorder mr, int what, int extra) {
1162 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
1163 if (mMediaRecorderRecording) {
1164 onStopVideoRecording();
1166 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
1167 if (mMediaRecorderRecording) {
1168 onStopVideoRecording();
1172 Toast.makeText(mActivity, R.string.video_reach_size_limit,
1173 Toast.LENGTH_LONG).show();
1178 * Make sure we're not recording music playing in the background, ask the
1179 * MediaPlaybackService to pause playback.
1181 private void pauseAudioPlayback() {
1182 AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
1183 am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
1187 public boolean isRecording() {
1188 return mMediaRecorderRecording;
1191 private void startVideoRecording() {
1192 Log.v(TAG, "startVideoRecording");
1193 mUI.cancelAnimations();
1194 mUI.setSwipingEnabled(false);
1195 mUI.showFocusUI(false);
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);