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