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