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