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