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 17package com.android.camera; 18 19import android.annotation.TargetApi; 20import android.app.Activity; 21import android.content.ActivityNotFoundException; 22import android.content.BroadcastReceiver; 23import android.content.ContentResolver; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.SharedPreferences.Editor; 29import android.content.res.Configuration; 30import android.graphics.Bitmap; 31import android.graphics.SurfaceTexture; 32import android.hardware.Camera.CameraInfo; 33import android.hardware.Camera.Parameters; 34import android.hardware.Camera.PictureCallback; 35import android.hardware.Camera.Size; 36import android.location.Location; 37import android.media.CamcorderProfile; 38import android.media.CameraProfile; 39import android.media.MediaRecorder; 40import android.net.Uri; 41import android.os.Build; 42import android.os.Bundle; 43import android.os.Handler; 44import android.os.Message; 45import android.os.ParcelFileDescriptor; 46import android.os.SystemClock; 47import android.provider.MediaStore; 48import android.provider.MediaStore.MediaColumns; 49import android.provider.MediaStore.Video; 50import android.util.Log; 51import android.view.KeyEvent; 52import android.view.MotionEvent; 53import android.view.OrientationEventListener; 54import android.view.View; 55import android.view.WindowManager; 56import android.widget.Toast; 57 58import com.android.camera.ui.PopupManager; 59import com.android.camera.ui.RotateTextToast; 60import com.android.gallery3d.R; 61import com.android.gallery3d.common.ApiHelper; 62import com.android.gallery3d.exif.ExifInterface; 63import com.android.gallery3d.util.AccessibilityUtils; 64import com.android.gallery3d.util.UsageStatistics; 65 66import java.io.File; 67import java.io.IOException; 68import java.text.SimpleDateFormat; 69import java.util.Date; 70import java.util.Iterator; 71import java.util.List; 72 73public class VideoModule implements CameraModule, 74 VideoController, 75 CameraPreference.OnPreferenceChangedListener, 76 ShutterButton.OnShutterButtonListener, 77 MediaRecorder.OnErrorListener, 78 MediaRecorder.OnInfoListener, 79 EffectsRecorder.EffectsListener { 80 81 private static final String TAG = "CAM_VideoModule"; 82 83 // We number the request code from 1000 to avoid collision with Gallery. 84 private static final int REQUEST_EFFECT_BACKDROPPER = 1000; 85 86 private static final int CHECK_DISPLAY_ROTATION = 3; 87 private static final int CLEAR_SCREEN_DELAY = 4; 88 private static final int UPDATE_RECORD_TIME = 5; 89 private static final int ENABLE_SHUTTER_BUTTON = 6; 90 private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7; 91 private static final int SWITCH_CAMERA = 8; 92 private static final int SWITCH_CAMERA_START_ANIMATION = 9; 93 private static final int HIDE_SURFACE_VIEW = 10; 94 private static final int CAPTURE_ANIMATION_DONE = 11; 95 96 private static final int SCREEN_DELAY = 2 * 60 * 1000; 97 98 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms 99 100 /** 101 * An unpublished intent flag requesting to start recording straight away 102 * and return as soon as recording is stopped. 103 * TODO: consider publishing by moving into MediaStore. 104 */ 105 private static final String EXTRA_QUICK_CAPTURE = 106 "android.intent.extra.quickCapture"; 107 108 private static final int MIN_THUMB_SIZE = 64; 109 // module fields 110 private CameraActivity mActivity; 111 private boolean mPaused; 112 private int mCameraId; 113 private Parameters mParameters; 114 115 private Boolean mCameraOpened = false; 116 117 private boolean mSnapshotInProgress = false; 118 119 private static final String EFFECT_BG_FROM_GALLERY = "gallery"; 120 121 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback(); 122 123 private ComboPreferences mPreferences; 124 private PreferenceGroup mPreferenceGroup; 125 126 private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener; 127 128 private boolean mIsVideoCaptureIntent; 129 private boolean mQuickCapture; 130 private boolean mIsInReviewMode = false; 131 132 private MediaRecorder mMediaRecorder; 133 private EffectsRecorder mEffectsRecorder; 134 private boolean mEffectsDisplayResult; 135 136 private int mEffectType = EffectsRecorder.EFFECT_NONE; 137 private Object mEffectParameter = null; 138 private String mEffectUriFromGallery = null; 139 private String mPrefVideoEffectDefault; 140 private boolean mResetEffect = true; 141 142 private boolean mSwitchingCamera; 143 private boolean mMediaRecorderRecording = false; 144 private long mRecordingStartTime; 145 private boolean mRecordingTimeCountsDown = false; 146 private long mOnResumeTime; 147 // The video file that the hardware camera is about to record into 148 // (or is recording into.) 149 private String mVideoFilename; 150 private ParcelFileDescriptor mVideoFileDescriptor; 151 152 // The video file that has already been recorded, and that is being 153 // examined by the user. 154 private String mCurrentVideoFilename; 155 private Uri mCurrentVideoUri; 156 private ContentValues mCurrentVideoValues; 157 158 private CamcorderProfile mProfile; 159 160 // The video duration limit. 0 menas no limit. 161 private int mMaxVideoDurationInMs; 162 163 // Time Lapse parameters. 164 private boolean mCaptureTimeLapse = false; 165 // Default 0. If it is larger than 0, the camcorder is in time lapse mode. 166 private int mTimeBetweenTimeLapseFrameCaptureMs = 0; 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 174 private int mDesiredPreviewWidth; 175 private int mDesiredPreviewHeight; 176 private ContentResolver mContentResolver; 177 178 private LocationManager mLocationManager; 179 180 private int mPendingSwitchCameraId; 181 182 private final Handler mHandler = new MainHandler(); 183 private VideoUI mUI; 184 // The degrees of the device rotated clockwise from its natural orientation. 185 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; 186 187 private int mZoomValue; // The current zoom value. 188 189 private boolean mRestoreFlash; // This is used to check if we need to restore the flash 190 // status when going back from gallery. 191 192 private final MediaSaveService.OnMediaSavedListener mOnVideoSavedListener = 193 new MediaSaveService.OnMediaSavedListener() { 194 @Override 195 public void onMediaSaved(Uri uri) { 196 if (uri != null) { 197 mActivity.addSecureAlbumItemIfNeeded(true, uri); 198 mActivity.sendBroadcast( 199 new Intent(Util.ACTION_NEW_VIDEO, uri)); 200 Util.broadcastNewPicture(mActivity, uri); 201 } 202 } 203 }; 204 205 private final MediaSaveService.OnMediaSavedListener mOnPhotoSavedListener = 206 new MediaSaveService.OnMediaSavedListener() { 207 @Override 208 public void onMediaSaved(Uri uri) { 209 if (uri != null) { 210 Util.broadcastNewPicture(mActivity, uri); 211 } 212 } 213 }; 214 215 216 protected class CameraOpenThread extends Thread { 217 @Override 218 public void run() { 219 openCamera(); 220 } 221 } 222 223 private void openCamera() { 224 try { 225 synchronized(mCameraOpened) { 226 if (!mCameraOpened) { 227 mActivity.mCameraDevice = Util.openCamera(mActivity, mCameraId); 228 mCameraOpened = true; 229 } 230 } 231 mParameters = mActivity.mCameraDevice.getParameters(); 232 } catch (CameraHardwareException e) { 233 mActivity.mOpenCameraFail = true; 234 } catch (CameraDisabledException e) { 235 mActivity.mCameraDisabled = true; 236 } 237 } 238 239 // This Handler is used to post message back onto the main thread of the 240 // application 241 private class MainHandler extends Handler { 242 @Override 243 public void handleMessage(Message msg) { 244 switch (msg.what) { 245 246 case ENABLE_SHUTTER_BUTTON: 247 mUI.enableShutter(true); 248 break; 249 250 case CLEAR_SCREEN_DELAY: { 251 mActivity.getWindow().clearFlags( 252 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 253 break; 254 } 255 256 case UPDATE_RECORD_TIME: { 257 updateRecordingTime(); 258 break; 259 } 260 261 case CHECK_DISPLAY_ROTATION: { 262 // Restart the preview if display rotation has changed. 263 // Sometimes this happens when the device is held upside 264 // down and camera app is opened. Rotation animation will 265 // take some time and the rotation value we have got may be 266 // wrong. Framework does not have a callback for this now. 267 if ((Util.getDisplayRotation(mActivity) != mDisplayRotation) 268 && !mMediaRecorderRecording && !mSwitchingCamera) { 269 startPreview(); 270 } 271 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) { 272 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); 273 } 274 break; 275 } 276 277 case SHOW_TAP_TO_SNAPSHOT_TOAST: { 278 showTapToSnapshotToast(); 279 break; 280 } 281 282 case SWITCH_CAMERA: { 283 switchCamera(); 284 break; 285 } 286 287 case SWITCH_CAMERA_START_ANIMATION: { 288 ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera(); 289 290 // Enable all camera controls. 291 mSwitchingCamera = false; 292 break; 293 } 294 295 case HIDE_SURFACE_VIEW: { 296 mUI.hideSurfaceView(); 297 break; 298 } 299 300 case CAPTURE_ANIMATION_DONE: { 301 mUI.enablePreviewThumb(false); 302 break; 303 } 304 305 default: 306 Log.v(TAG, "Unhandled message: " + msg.what); 307 break; 308 } 309 } 310 } 311 312 private BroadcastReceiver mReceiver = null; 313 314 private class MyBroadcastReceiver extends BroadcastReceiver { 315 @Override 316 public void onReceive(Context context, Intent intent) { 317 String action = intent.getAction(); 318 if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 319 stopVideoRecording(); 320 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 321 Toast.makeText(mActivity, 322 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show(); 323 } 324 } 325 } 326 327 private String createName(long dateTaken) { 328 Date date = new Date(dateTaken); 329 SimpleDateFormat dateFormat = new SimpleDateFormat( 330 mActivity.getString(R.string.video_file_name_format)); 331 332 return dateFormat.format(date); 333 } 334 335 private int getPreferredCameraId(ComboPreferences preferences) { 336 int intentCameraId = Util.getCameraFacingIntentExtras(mActivity); 337 if (intentCameraId != -1) { 338 // Testing purpose. Launch a specific camera through the intent 339 // extras. 340 return intentCameraId; 341 } else { 342 return CameraSettings.readPreferredCameraId(preferences); 343 } 344 } 345 346 private void initializeSurfaceView() { 347 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 348 mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() { 349 @Override 350 public void onFrameDrawn(CameraScreenNail c) { 351 mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW); 352 } 353 }; 354 mUI.getSurfaceHolder().addCallback(mUI); 355 } 356 } 357 358 @Override 359 public void init(CameraActivity activity, View root, boolean reuseScreenNail) { 360 mActivity = activity; 361 mUI = new VideoUI(activity, this, root); 362 mPreferences = new ComboPreferences(mActivity); 363 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); 364 mCameraId = getPreferredCameraId(mPreferences); 365 366 mPreferences.setLocalId(mActivity, mCameraId); 367 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); 368 369 mActivity.mNumberOfCameras = CameraHolder.instance().getNumberOfCameras(); 370 mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default); 371 resetEffect(); 372 373 /* 374 * To reduce startup time, we start the preview in another thread. 375 * We make sure the preview is started at the end of onCreate. 376 */ 377 CameraOpenThread cameraOpenThread = new CameraOpenThread(); 378 cameraOpenThread.start(); 379 380 mContentResolver = mActivity.getContentResolver(); 381 382 // Surface texture is from camera screen nail and startPreview needs it. 383 // This must be done before startPreview. 384 mIsVideoCaptureIntent = isVideoCaptureIntent(); 385 if (reuseScreenNail) { 386 mActivity.reuseCameraScreenNail(!mIsVideoCaptureIntent); 387 } else { 388 mActivity.createCameraScreenNail(!mIsVideoCaptureIntent); 389 } 390 initializeSurfaceView(); 391 392 // Make sure camera device is opened. 393 try { 394 cameraOpenThread.join(); 395 if (mActivity.mOpenCameraFail) { 396 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); 397 return; 398 } else if (mActivity.mCameraDisabled) { 399 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 400 return; 401 } 402 } catch (InterruptedException ex) { 403 // ignore 404 } 405 406 readVideoPreferences(); 407 mUI.setPrefChangedListener(this); 408 new Thread(new Runnable() { 409 @Override 410 public void run() { 411 startPreview(); 412 } 413 }).start(); 414 415 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); 416 mLocationManager = new LocationManager(mActivity, null); 417 418 mUI.setOrientationIndicator(0, false); 419 setDisplayOrientation(); 420 421 mUI.showTimeLapseUI(mCaptureTimeLapse); 422 initializeVideoSnapshot(); 423 resizeForPreviewAspectRatio(); 424 425 initializeVideoControl(); 426 mPendingSwitchCameraId = -1; 427 mUI.updateOnScreenIndicators(mParameters, mPreferences); 428 429 // Disable the shutter button if effects are ON since it might take 430 // a little more time for the effects preview to be ready. We do not 431 // want to allow recording before that happens. The shutter button 432 // will be enabled when we get the message from effectsrecorder that 433 // the preview is running. This becomes critical when the camera is 434 // swapped. 435 if (effectsActive()) { 436 mUI.enableShutter(false); 437 } 438 } 439 440 // SingleTapListener 441 // Preview area is touched. Take a picture. 442 @Override 443 public void onSingleTapUp(View view, int x, int y) { 444 if (mMediaRecorderRecording && effectsActive()) { 445 new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint, 446 mOrientation).show(); 447 return; 448 } 449 450 MediaSaveService s = mActivity.getMediaSaveService(); 451 if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) { 452 return; 453 } 454 455 if (!mMediaRecorderRecording) { 456 // check for dismissing popup 457 mUI.dismissPopup(true); 458 return; 459 } 460 461 // Set rotation and gps data. 462 int rotation = Util.getJpegRotation(mCameraId, mOrientation); 463 mParameters.setRotation(rotation); 464 Location loc = mLocationManager.getCurrentLocation(); 465 Util.setGpsParameters(mParameters, loc); 466 mActivity.mCameraDevice.setParameters(mParameters); 467 468 Log.v(TAG, "Video snapshot start"); 469 mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); 470 showVideoSnapshotUI(true); 471 mSnapshotInProgress = true; 472 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 473 UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot"); 474 } 475 476 @Override 477 public void onStop() {} 478 479 private void loadCameraPreferences() { 480 CameraSettings settings = new CameraSettings(mActivity, mParameters, 481 mCameraId, CameraHolder.instance().getCameraInfo()); 482 // Remove the video quality preference setting when the quality is given in the intent. 483 mPreferenceGroup = filterPreferenceScreenByIntent( 484 settings.getPreferenceGroup(R.xml.video_preferences)); 485 } 486 487 private void initializeVideoControl() { 488 loadCameraPreferences(); 489 mUI.initializePopup(mPreferenceGroup); 490 if (effectsActive()) { 491 mUI.overrideSettings( 492 CameraSettings.KEY_VIDEO_QUALITY, 493 Integer.toString(getLowVideoQuality())); 494 } 495 } 496 497 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 498 private static int getLowVideoQuality() { 499 if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) { 500 return CamcorderProfile.QUALITY_480P; 501 } else { 502 return CamcorderProfile.QUALITY_LOW; 503 } 504 } 505 506 507 @Override 508 public void onOrientationChanged(int orientation) { 509 // We keep the last known orientation. So if the user first orient 510 // the camera then point the camera to floor or sky, we still have 511 // the correct orientation. 512 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return; 513 int newOrientation = Util.roundOrientation(orientation, mOrientation); 514 515 if (mOrientation != newOrientation) { 516 mOrientation = newOrientation; 517 // The input of effects recorder is affected by 518 // android.hardware.Camera.setDisplayOrientation. Its value only 519 // compensates the camera orientation (no Display.getRotation). 520 // So the orientation hint here should only consider sensor 521 // orientation. 522 if (effectsActive()) { 523 mEffectsRecorder.setOrientationHint(mOrientation); 524 } 525 } 526 527 // Show the toast after getting the first orientation changed. 528 if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) { 529 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST); 530 showTapToSnapshotToast(); 531 } 532 } 533 534 private void startPlayVideoActivity() { 535 Intent intent = new Intent(Intent.ACTION_VIEW); 536 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat)); 537 try { 538 mActivity.startActivity(intent); 539 } catch (ActivityNotFoundException ex) { 540 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 541 } 542 } 543 544 @OnClickAttr 545 public void onReviewPlayClicked(View v) { 546 startPlayVideoActivity(); 547 } 548 549 @OnClickAttr 550 public void onReviewDoneClicked(View v) { 551 mIsInReviewMode = false; 552 doReturnToCaller(true); 553 } 554 555 @OnClickAttr 556 public void onReviewCancelClicked(View v) { 557 mIsInReviewMode = false; 558 stopVideoRecording(); 559 doReturnToCaller(false); 560 } 561 562 @Override 563 public boolean isInReviewMode() { 564 return mIsInReviewMode; 565 } 566 567 private void onStopVideoRecording() { 568 mEffectsDisplayResult = true; 569 boolean recordFail = stopVideoRecording(); 570 if (mIsVideoCaptureIntent) { 571 if (!effectsActive()) { 572 if (mQuickCapture) { 573 doReturnToCaller(!recordFail); 574 } else if (!recordFail) { 575 showCaptureResult(); 576 } 577 } 578 } else if (!recordFail){ 579 // Start capture animation. 580 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 581 // The capture animation is disabled on ICS because we use SurfaceView 582 // for preview during recording. When the recording is done, we switch 583 // back to use SurfaceTexture for preview and we need to stop then start 584 // the preview. This will cause the preview flicker since the preview 585 // will not be continuous for a short period of time. 586 587 // Get orientation directly from display rotation to make sure it's up 588 // to date. OnConfigurationChanged callback usually kicks in a bit later, if 589 // device is rotated during recording. 590 mDisplayRotation = Util.getDisplayRotation(mActivity); 591 ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); 592 593 mUI.enablePreviewThumb(true); 594 595 // Make sure to disable the thumbnail preview after the 596 // animation is done to disable the click target. 597 mHandler.removeMessages(CAPTURE_ANIMATION_DONE); 598 mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, 599 CaptureAnimManager.getAnimationDuration()); 600 } 601 } 602 } 603 604 public void onProtectiveCurtainClick(View v) { 605 // Consume clicks 606 } 607 608 @Override 609 public void onShutterButtonClick() { 610 if (mUI.collapseCameraControls() || mSwitchingCamera) return; 611 612 boolean stop = mMediaRecorderRecording; 613 614 if (stop) { 615 onStopVideoRecording(); 616 } else { 617 startVideoRecording(); 618 } 619 mUI.enableShutter(false); 620 621 // Keep the shutter button disabled when in video capture intent 622 // mode and recording is stopped. It'll be re-enabled when 623 // re-take button is clicked. 624 if (!(mIsVideoCaptureIntent && stop)) { 625 mHandler.sendEmptyMessageDelayed( 626 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT); 627 } 628 } 629 630 @Override 631 public void onShutterButtonFocus(boolean pressed) { 632 mUI.setShutterPressed(pressed); 633 } 634 635 private void readVideoPreferences() { 636 // The preference stores values from ListPreference and is thus string type for all values. 637 // We need to convert it to int manually. 638 String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId, 639 mActivity.getResources().getString(R.string.pref_video_quality_default)); 640 String videoQuality = 641 mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY, 642 defaultQuality); 643 int quality = Integer.valueOf(videoQuality); 644 645 // Set video quality. 646 Intent intent = mActivity.getIntent(); 647 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 648 int extraVideoQuality = 649 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 650 if (extraVideoQuality > 0) { 651 quality = CamcorderProfile.QUALITY_HIGH; 652 } else { // 0 is mms. 653 quality = CamcorderProfile.QUALITY_LOW; 654 } 655 } 656 657 // Set video duration limit. The limit is read from the preference, 658 // unless it is specified in the intent. 659 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 660 int seconds = 661 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0); 662 mMaxVideoDurationInMs = 1000 * seconds; 663 } else { 664 mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity); 665 } 666 667 // Set effect 668 mEffectType = CameraSettings.readEffectType(mPreferences); 669 if (mEffectType != EffectsRecorder.EFFECT_NONE) { 670 mEffectParameter = CameraSettings.readEffectParameter(mPreferences); 671 // Set quality to be no higher than 480p. 672 CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality); 673 if (profile.videoFrameHeight > 480) { 674 quality = getLowVideoQuality(); 675 } 676 } else { 677 mEffectParameter = null; 678 } 679 // Read time lapse recording interval. 680 if (ApiHelper.HAS_TIME_LAPSE_RECORDING) { 681 String frameIntervalStr = mPreferences.getString( 682 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, 683 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default)); 684 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr); 685 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0); 686 } 687 // TODO: This should be checked instead directly +1000. 688 if (mCaptureTimeLapse) quality += 1000; 689 mProfile = CamcorderProfile.get(mCameraId, quality); 690 getDesiredPreviewSize(); 691 } 692 693 private void writeDefaultEffectToPrefs() { 694 ComboPreferences.Editor editor = mPreferences.edit(); 695 editor.putString(CameraSettings.KEY_VIDEO_EFFECT, 696 mActivity.getString(R.string.pref_video_effect_default)); 697 editor.apply(); 698 } 699 700 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 701 private void getDesiredPreviewSize() { 702 mParameters = mActivity.mCameraDevice.getParameters(); 703 if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) { 704 if (mParameters.getSupportedVideoSizes() == null || effectsActive()) { 705 mDesiredPreviewWidth = mProfile.videoFrameWidth; 706 mDesiredPreviewHeight = mProfile.videoFrameHeight; 707 } else { // Driver supports separates outputs for preview and video. 708 List<Size> sizes = mParameters.getSupportedPreviewSizes(); 709 Size preferred = mParameters.getPreferredPreviewSizeForVideo(); 710 int product = preferred.width * preferred.height; 711 Iterator<Size> it = sizes.iterator(); 712 // Remove the preview sizes that are not preferred. 713 while (it.hasNext()) { 714 Size size = it.next(); 715 if (size.width * size.height > product) { 716 it.remove(); 717 } 718 } 719 Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes, 720 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 721 mDesiredPreviewWidth = optimalSize.width; 722 mDesiredPreviewHeight = optimalSize.height; 723 } 724 } else { 725 mDesiredPreviewWidth = mProfile.videoFrameWidth; 726 mDesiredPreviewHeight = mProfile.videoFrameHeight; 727 } 728 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth + 729 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight); 730 } 731 732 private void resizeForPreviewAspectRatio() { 733 mUI.setAspectRatio( 734 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 735 } 736 737 @Override 738 public void installIntentFilter() { 739 // install an intent filter to receive SD card related events. 740 IntentFilter intentFilter = 741 new IntentFilter(Intent.ACTION_MEDIA_EJECT); 742 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 743 intentFilter.addDataScheme("file"); 744 mReceiver = new MyBroadcastReceiver(); 745 mActivity.registerReceiver(mReceiver, intentFilter); 746 } 747 748 @Override 749 public void onResumeBeforeSuper() { 750 mPaused = false; 751 } 752 753 @Override 754 public void onResumeAfterSuper() { 755 if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled) 756 return; 757 mUI.enableShutter(false); 758 mZoomValue = 0; 759 760 showVideoSnapshotUI(false); 761 762 if (!mPreviewing) { 763 resetEffect(); 764 openCamera(); 765 if (mActivity.mOpenCameraFail) { 766 Util.showErrorAndFinish(mActivity, 767 R.string.cannot_connect_camera); 768 return; 769 } else if (mActivity.mCameraDisabled) { 770 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 771 return; 772 } 773 readVideoPreferences(); 774 resizeForPreviewAspectRatio(); 775 new Thread(new Runnable() { 776 @Override 777 public void run() { 778 startPreview(); 779 } 780 }).start(); 781 } else { 782 // preview already started 783 mUI.enableShutter(true); 784 } 785 786 // Initializing it here after the preview is started. 787 mUI.initializeZoom(mParameters); 788 789 keepScreenOnAwhile(); 790 791 // Initialize location service. 792 boolean recordLocation = RecordLocationPreference.get(mPreferences, 793 mContentResolver); 794 mLocationManager.recordLocation(recordLocation); 795 796 if (mPreviewing) { 797 mOnResumeTime = SystemClock.uptimeMillis(); 798 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); 799 } 800 // Dismiss open menu if exists. 801 PopupManager.getInstance(mActivity).notifyShowPopup(null); 802 803 UsageStatistics.onContentViewChanged( 804 UsageStatistics.COMPONENT_CAMERA, "VideoModule"); 805 } 806 807 private void setDisplayOrientation() { 808 mDisplayRotation = Util.getDisplayRotation(mActivity); 809 if (ApiHelper.HAS_SURFACE_TEXTURE) { 810 // The display rotation is handled by gallery. 811 mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId); 812 } else { 813 // We need to consider display rotation ourselves. 814 mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId); 815 } 816 // GLRoot also uses the DisplayRotation, and needs to be told to layout to update 817 mActivity.getGLRoot().requestLayoutContentPane(); 818 } 819 820 @Override 821 public int onZoomChanged(int index) { 822 // Not useful to change zoom value when the activity is paused. 823 if (mPaused) return index; 824 mZoomValue = index; 825 if (mParameters == null || mActivity.mCameraDevice == null) return index; 826 // Set zoom parameters asynchronously 827 mParameters.setZoom(mZoomValue); 828 mActivity.mCameraDevice.setParameters(mParameters); 829 Parameters p = mActivity.mCameraDevice.getParameters(); 830 if (p != null) return p.getZoom(); 831 return index; 832 } 833 private void startPreview() { 834 Log.v(TAG, "startPreview"); 835 836 mActivity.mCameraDevice.setErrorCallback(mErrorCallback); 837 if (mPreviewing == true) { 838 stopPreview(); 839 if (effectsActive() && mEffectsRecorder != null) { 840 mEffectsRecorder.release(); 841 mEffectsRecorder = null; 842 } 843 } 844 845 setDisplayOrientation(); 846 mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); 847 setCameraParameters(); 848 849 try { 850 if (!effectsActive()) { 851 if (ApiHelper.HAS_SURFACE_TEXTURE) { 852 SurfaceTexture surfaceTexture = ((CameraScreenNail) mActivity.mCameraScreenNail) 853 .getSurfaceTexture(); 854 if (surfaceTexture == null) { 855 return; // The texture has been destroyed (pause, etc) 856 } 857 mActivity.mCameraDevice.setPreviewTextureAsync(surfaceTexture); 858 } else { 859 mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); 860 } 861 mActivity.mCameraDevice.startPreviewAsync(); 862 mPreviewing = true; 863 onPreviewStarted(); 864 } else { 865 initializeEffectsPreview(); 866 mEffectsRecorder.startPreview(); 867 mPreviewing = true; 868 onPreviewStarted(); 869 } 870 } catch (Throwable ex) { 871 closeCamera(); 872 throw new RuntimeException("startPreview failed", ex); 873 } finally { 874 mActivity.runOnUiThread(new Runnable() { 875 @Override 876 public void run() { 877 if (mActivity.mOpenCameraFail) { 878 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); 879 } else if (mActivity.mCameraDisabled) { 880 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 881 } 882 } 883 }); 884 } 885 886 } 887 888 private void onPreviewStarted() { 889 mUI.enableShutter(true); 890 } 891 892 @Override 893 public void stopPreview() { 894 mActivity.mCameraDevice.stopPreview(); 895 mPreviewing = false; 896 } 897 898 // Closing the effects out. Will shut down the effects graph. 899 private void closeEffects() { 900 Log.v(TAG, "Closing effects"); 901 mEffectType = EffectsRecorder.EFFECT_NONE; 902 if (mEffectsRecorder == null) { 903 Log.d(TAG, "Effects are already closed. Nothing to do"); 904 return; 905 } 906 // This call can handle the case where the camera is already released 907 // after the recording has been stopped. 908 mEffectsRecorder.release(); 909 mEffectsRecorder = null; 910 } 911 912 // By default, we want to close the effects as well with the camera. 913 private void closeCamera() { 914 closeCamera(true); 915 } 916 917 // In certain cases, when the effects are active, we may want to shutdown 918 // only the camera related parts, and handle closing the effects in the 919 // effectsUpdate callback. 920 // For example, in onPause, we want to make the camera available to 921 // outside world immediately, however, want to wait till the effects 922 // callback to shut down the effects. In such a case, we just disconnect 923 // the effects from the camera by calling disconnectCamera. That way 924 // the effects can handle that when shutting down. 925 // 926 // @param closeEffectsAlso - indicates whether we want to close the 927 // effects also along with the camera. 928 private void closeCamera(boolean closeEffectsAlso) { 929 Log.v(TAG, "closeCamera"); 930 if (mActivity.mCameraDevice == null) { 931 Log.d(TAG, "already stopped."); 932 return; 933 } 934 935 if (mEffectsRecorder != null) { 936 // Disconnect the camera from effects so that camera is ready to 937 // be released to the outside world. 938 mEffectsRecorder.disconnectCamera(); 939 } 940 if (closeEffectsAlso) closeEffects(); 941 mActivity.mCameraDevice.setZoomChangeListener(null); 942 mActivity.mCameraDevice.setErrorCallback(null); 943 synchronized(mCameraOpened) { 944 if (mCameraOpened) { 945 CameraHolder.instance().release(); 946 } 947 mCameraOpened = false; 948 } 949 mActivity.mCameraDevice = null; 950 mPreviewing = false; 951 mSnapshotInProgress = false; 952 } 953 954 private void releasePreviewResources() { 955 if (ApiHelper.HAS_SURFACE_TEXTURE) { 956 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 957 screenNail.releaseSurfaceTexture(); 958 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 959 mHandler.removeMessages(HIDE_SURFACE_VIEW); 960 mUI.hideSurfaceView(); 961 } 962 } 963 } 964 965 @Override 966 public void onPauseBeforeSuper() { 967 mPaused = true; 968 969 if (mMediaRecorderRecording) { 970 // Camera will be released in onStopVideoRecording. 971 onStopVideoRecording(); 972 } else { 973 closeCamera(); 974 if (!effectsActive()) releaseMediaRecorder(); 975 } 976 if (effectsActive()) { 977 // If the effects are active, make sure we tell the graph that the 978 // surfacetexture is not valid anymore. Disconnect the graph from 979 // the display. This should be done before releasing the surface 980 // texture. 981 mEffectsRecorder.disconnectDisplay(); 982 } else { 983 // Close the file descriptor and clear the video namer only if the 984 // effects are not active. If effects are active, we need to wait 985 // till we get the callback from the Effects that the graph is done 986 // recording. That also needs a change in the stopVideoRecording() 987 // call to not call closeCamera if the effects are active, because 988 // that will close down the effects are well, thus making this if 989 // condition invalid. 990 closeVideoFileDescriptor(); 991 } 992 993 releasePreviewResources(); 994 995 if (mReceiver != null) { 996 mActivity.unregisterReceiver(mReceiver); 997 mReceiver = null; 998 } 999 resetScreenOn(); 1000 1001 if (mLocationManager != null) mLocationManager.recordLocation(false); 1002 1003 mHandler.removeMessages(CHECK_DISPLAY_ROTATION); 1004 mHandler.removeMessages(SWITCH_CAMERA); 1005 mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION); 1006 mPendingSwitchCameraId = -1; 1007 mSwitchingCamera = false; 1008 // Call onPause after stopping video recording. So the camera can be 1009 // released as soon as possible. 1010 } 1011 1012 @Override 1013 public void onPauseAfterSuper() { 1014 } 1015 1016 @Override 1017 public void onUserInteraction() { 1018 if (!mMediaRecorderRecording && !mActivity.isFinishing()) { 1019 keepScreenOnAwhile(); 1020 } 1021 } 1022 1023 @Override 1024 public boolean onBackPressed() { 1025 if (mPaused) return true; 1026 if (mMediaRecorderRecording) { 1027 onStopVideoRecording(); 1028 return true; 1029 } else if (mUI.hidePieRenderer()) { 1030 return true; 1031 } else { 1032 return mUI.removeTopLevelPopup(); 1033 } 1034 } 1035 1036 @Override 1037 public boolean onKeyDown(int keyCode, KeyEvent event) { 1038 // Do not handle any key if the activity is paused. 1039 if (mPaused) { 1040 return true; 1041 } 1042 1043 switch (keyCode) { 1044 case KeyEvent.KEYCODE_CAMERA: 1045 if (event.getRepeatCount() == 0) { 1046 mUI.clickShutter(); 1047 return true; 1048 } 1049 break; 1050 case KeyEvent.KEYCODE_DPAD_CENTER: 1051 if (event.getRepeatCount() == 0) { 1052 mUI.clickShutter(); 1053 return true; 1054 } 1055 break; 1056 case KeyEvent.KEYCODE_MENU: 1057 if (mMediaRecorderRecording) return true; 1058 break; 1059 } 1060 return false; 1061 } 1062 1063 @Override 1064 public boolean onKeyUp(int keyCode, KeyEvent event) { 1065 switch (keyCode) { 1066 case KeyEvent.KEYCODE_CAMERA: 1067 mUI.pressShutter(false); 1068 return true; 1069 } 1070 return false; 1071 } 1072 1073 @Override 1074 public boolean isVideoCaptureIntent() { 1075 String action = mActivity.getIntent().getAction(); 1076 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 1077 } 1078 1079 private void doReturnToCaller(boolean valid) { 1080 Intent resultIntent = new Intent(); 1081 int resultCode; 1082 if (valid) { 1083 resultCode = Activity.RESULT_OK; 1084 resultIntent.setData(mCurrentVideoUri); 1085 } else { 1086 resultCode = Activity.RESULT_CANCELED; 1087 } 1088 mActivity.setResultEx(resultCode, resultIntent); 1089 mActivity.finish(); 1090 } 1091 1092 private void cleanupEmptyFile() { 1093 if (mVideoFilename != null) { 1094 File f = new File(mVideoFilename); 1095 if (f.length() == 0 && f.delete()) { 1096 Log.v(TAG, "Empty video file deleted: " + mVideoFilename); 1097 mVideoFilename = null; 1098 } 1099 } 1100 } 1101 1102 private void setupMediaRecorderPreviewDisplay() { 1103 // Nothing to do here if using SurfaceTexture. 1104 if (!ApiHelper.HAS_SURFACE_TEXTURE) { 1105 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); 1106 } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 1107 // We stop the preview here before unlocking the device because we 1108 // need to change the SurfaceTexture to SurfaceView for preview. 1109 stopPreview(); 1110 mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); 1111 // The orientation for SurfaceTexture is different from that for 1112 // SurfaceView. For SurfaceTexture we don't need to consider the 1113 // display rotation. Just consider the sensor's orientation and we 1114 // will set the orientation correctly when showing the texture. 1115 // Gallery will handle the orientation for the preview. For 1116 // SurfaceView we will have to take everything into account so the 1117 // display rotation is considered. 1118 mActivity.mCameraDevice.setDisplayOrientation( 1119 Util.getDisplayOrientation(mDisplayRotation, mCameraId)); 1120 mActivity.mCameraDevice.startPreviewAsync(); 1121 mPreviewing = true; 1122 mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); 1123 } 1124 } 1125 1126 // Prepares media recorder. 1127 private void initializeRecorder() { 1128 Log.v(TAG, "initializeRecorder"); 1129 // If the mCameraDevice is null, then this activity is going to finish 1130 if (mActivity.mCameraDevice == null) return; 1131 1132 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING && ApiHelper.HAS_SURFACE_TEXTURE) { 1133 // Set the SurfaceView to visible so the surface gets created. 1134 // surfaceCreated() is called immediately when the visibility is 1135 // changed to visible. Thus, mSurfaceViewReady should become true 1136 // right after calling setVisibility(). 1137 mUI.showSurfaceView(); 1138 if (!mUI.isSurfaceViewReady()) return; 1139 } 1140 1141 Intent intent = mActivity.getIntent(); 1142 Bundle myExtras = intent.getExtras(); 1143 1144 long requestedSizeLimit = 0; 1145 closeVideoFileDescriptor(); 1146 if (mIsVideoCaptureIntent && myExtras != null) { 1147 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 1148 if (saveUri != null) { 1149 try { 1150 mVideoFileDescriptor = 1151 mContentResolver.openFileDescriptor(saveUri, "rw"); 1152 mCurrentVideoUri = saveUri; 1153 } catch (java.io.FileNotFoundException ex) { 1154 // invalid uri 1155 Log.e(TAG, ex.toString()); 1156 } 1157 } 1158 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 1159 } 1160 mMediaRecorder = new MediaRecorder(); 1161 1162 setupMediaRecorderPreviewDisplay(); 1163 // Unlock the camera object before passing it to media recorder. 1164 mActivity.mCameraDevice.unlock(); 1165 mActivity.mCameraDevice.waitDone(); 1166 mMediaRecorder.setCamera(mActivity.mCameraDevice.getCamera()); 1167 if (!mCaptureTimeLapse) { 1168 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1169 } 1170 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1171 mMediaRecorder.setProfile(mProfile); 1172 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs); 1173 if (mCaptureTimeLapse) { 1174 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs; 1175 setCaptureRate(mMediaRecorder, fps); 1176 } 1177 1178 setRecordLocation(); 1179 1180 // Set output file. 1181 // Try Uri in the intent first. If it doesn't exist, use our own 1182 // instead. 1183 if (mVideoFileDescriptor != null) { 1184 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); 1185 } else { 1186 generateVideoFilename(mProfile.fileFormat); 1187 mMediaRecorder.setOutputFile(mVideoFilename); 1188 } 1189 1190 // Set maximum file size. 1191 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD; 1192 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 1193 maxFileSize = requestedSizeLimit; 1194 } 1195 1196 try { 1197 mMediaRecorder.setMaxFileSize(maxFileSize); 1198 } catch (RuntimeException exception) { 1199 // We are going to ignore failure of setMaxFileSize here, as 1200 // a) The composer selected may simply not support it, or 1201 // b) The underlying media framework may not handle 64-bit range 1202 // on the size restriction. 1203 } 1204 1205 // See android.hardware.Camera.Parameters.setRotation for 1206 // documentation. 1207 // Note that mOrientation here is the device orientation, which is the opposite of 1208 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return, 1209 // which is the orientation the graphics need to rotate in order to render correctly. 1210 int rotation = 0; 1211 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 1212 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; 1213 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { 1214 rotation = (info.orientation - mOrientation + 360) % 360; 1215 } else { // back-facing camera 1216 rotation = (info.orientation + mOrientation) % 360; 1217 } 1218 } 1219 mMediaRecorder.setOrientationHint(rotation); 1220 1221 try { 1222 mMediaRecorder.prepare(); 1223 } catch (IOException e) { 1224 Log.e(TAG, "prepare failed for " + mVideoFilename, e); 1225 releaseMediaRecorder(); 1226 throw new RuntimeException(e); 1227 } 1228 1229 mMediaRecorder.setOnErrorListener(this); 1230 mMediaRecorder.setOnInfoListener(this); 1231 } 1232 1233 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 1234 private static void setCaptureRate(MediaRecorder recorder, double fps) { 1235 recorder.setCaptureRate(fps); 1236 } 1237 1238 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) 1239 private void setRecordLocation() { 1240 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 1241 Location loc = mLocationManager.getCurrentLocation(); 1242 if (loc != null) { 1243 mMediaRecorder.setLocation((float) loc.getLatitude(), 1244 (float) loc.getLongitude()); 1245 } 1246 } 1247 } 1248 1249 private void initializeEffectsPreview() { 1250 Log.v(TAG, "initializeEffectsPreview"); 1251 // If the mCameraDevice is null, then this activity is going to finish 1252 if (mActivity.mCameraDevice == null) return; 1253 1254 boolean inLandscape = (mActivity.getResources().getConfiguration().orientation 1255 == Configuration.ORIENTATION_LANDSCAPE); 1256 1257 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; 1258 1259 mEffectsDisplayResult = false; 1260 mEffectsRecorder = new EffectsRecorder(mActivity); 1261 1262 // TODO: Confirm none of the following need to go to initializeEffectsRecording() 1263 // and none of these change even when the preview is not refreshed. 1264 mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation); 1265 mEffectsRecorder.setCamera(mActivity.mCameraDevice); 1266 mEffectsRecorder.setCameraFacing(info.facing); 1267 mEffectsRecorder.setProfile(mProfile); 1268 mEffectsRecorder.setEffectsListener(this); 1269 mEffectsRecorder.setOnInfoListener(this); 1270 mEffectsRecorder.setOnErrorListener(this); 1271 1272 // The input of effects recorder is affected by 1273 // android.hardware.Camera.setDisplayOrientation. Its value only 1274 // compensates the camera orientation (no Display.getRotation). So the 1275 // orientation hint here should only consider sensor orientation. 1276 int orientation = 0; 1277 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 1278 orientation = mOrientation; 1279 } 1280 mEffectsRecorder.setOrientationHint(orientation); 1281 1282 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 1283 mEffectsRecorder.setPreviewSurfaceTexture(screenNail.getSurfaceTexture(), 1284 screenNail.getWidth(), screenNail.getHeight()); 1285 1286 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER && 1287 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) { 1288 mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery); 1289 } else { 1290 mEffectsRecorder.setEffect(mEffectType, mEffectParameter); 1291 } 1292 } 1293 1294 private void initializeEffectsRecording() { 1295 Log.v(TAG, "initializeEffectsRecording"); 1296 1297 Intent intent = mActivity.getIntent(); 1298 Bundle myExtras = intent.getExtras(); 1299 1300 long requestedSizeLimit = 0; 1301 closeVideoFileDescriptor(); 1302 if (mIsVideoCaptureIntent && myExtras != null) { 1303 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 1304 if (saveUri != null) { 1305 try { 1306 mVideoFileDescriptor = 1307 mContentResolver.openFileDescriptor(saveUri, "rw"); 1308 mCurrentVideoUri = saveUri; 1309 } catch (java.io.FileNotFoundException ex) { 1310 // invalid uri 1311 Log.e(TAG, ex.toString()); 1312 } 1313 } 1314 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 1315 } 1316 1317 mEffectsRecorder.setProfile(mProfile); 1318 // important to set the capture rate to zero if not timelapsed, since the 1319 // effectsrecorder object does not get created again for each recording 1320 // session 1321 if (mCaptureTimeLapse) { 1322 mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs)); 1323 } else { 1324 mEffectsRecorder.setCaptureRate(0); 1325 } 1326 1327 // Set output file 1328 if (mVideoFileDescriptor != null) { 1329 mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); 1330 } else { 1331 generateVideoFilename(mProfile.fileFormat); 1332 mEffectsRecorder.setOutputFile(mVideoFilename); 1333 } 1334 1335 // Set maximum file size. 1336 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD; 1337 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 1338 maxFileSize = requestedSizeLimit; 1339 } 1340 mEffectsRecorder.setMaxFileSize(maxFileSize); 1341 mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs); 1342 } 1343 1344 1345 private void releaseMediaRecorder() { 1346 Log.v(TAG, "Releasing media recorder."); 1347 if (mMediaRecorder != null) { 1348 cleanupEmptyFile(); 1349 mMediaRecorder.reset(); 1350 mMediaRecorder.release(); 1351 mMediaRecorder = null; 1352 } 1353 mVideoFilename = null; 1354 } 1355 1356 private void releaseEffectsRecorder() { 1357 Log.v(TAG, "Releasing effects recorder."); 1358 if (mEffectsRecorder != null) { 1359 cleanupEmptyFile(); 1360 mEffectsRecorder.release(); 1361 mEffectsRecorder = null; 1362 } 1363 mEffectType = EffectsRecorder.EFFECT_NONE; 1364 mVideoFilename = null; 1365 } 1366 1367 private void generateVideoFilename(int outputFileFormat) { 1368 long dateTaken = System.currentTimeMillis(); 1369 String title = createName(dateTaken); 1370 // Used when emailing. 1371 String filename = title + convertOutputFormatToFileExt(outputFileFormat); 1372 String mime = convertOutputFormatToMimeType(outputFileFormat); 1373 String path = Storage.DIRECTORY + '/' + filename; 1374 String tmpPath = path + ".tmp"; 1375 mCurrentVideoValues = new ContentValues(9); 1376 mCurrentVideoValues.put(Video.Media.TITLE, title); 1377 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename); 1378 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken); 1379 mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000); 1380 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime); 1381 mCurrentVideoValues.put(Video.Media.DATA, path); 1382 mCurrentVideoValues.put(Video.Media.RESOLUTION, 1383 Integer.toString(mProfile.videoFrameWidth) + "x" + 1384 Integer.toString(mProfile.videoFrameHeight)); 1385 Location loc = mLocationManager.getCurrentLocation(); 1386 if (loc != null) { 1387 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude()); 1388 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude()); 1389 } 1390 mVideoFilename = tmpPath; 1391 Log.v(TAG, "New video filename: " + mVideoFilename); 1392 } 1393 1394 private void saveVideo() { 1395 if (mVideoFileDescriptor == null) { 1396 long duration = SystemClock.uptimeMillis() - mRecordingStartTime; 1397 if (duration > 0) { 1398 if (mCaptureTimeLapse) { 1399 duration = getTimeLapseVideoLength(duration); 1400 } 1401 } else { 1402 Log.w(TAG, "Video duration <= 0 : " + duration); 1403 } 1404 mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename, 1405 duration, mCurrentVideoValues, 1406 mOnVideoSavedListener, mContentResolver); 1407 } 1408 mCurrentVideoValues = null; 1409 } 1410 1411 private void deleteVideoFile(String fileName) { 1412 Log.v(TAG, "Deleting video " + fileName); 1413 File f = new File(fileName); 1414 if (!f.delete()) { 1415 Log.v(TAG, "Could not delete " + fileName); 1416 } 1417 } 1418 1419 private PreferenceGroup filterPreferenceScreenByIntent( 1420 PreferenceGroup screen) { 1421 Intent intent = mActivity.getIntent(); 1422 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 1423 CameraSettings.removePreferenceFromScreen(screen, 1424 CameraSettings.KEY_VIDEO_QUALITY); 1425 } 1426 1427 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 1428 CameraSettings.removePreferenceFromScreen(screen, 1429 CameraSettings.KEY_VIDEO_QUALITY); 1430 } 1431 return screen; 1432 } 1433 1434 // from MediaRecorder.OnErrorListener 1435 @Override 1436 public void onError(MediaRecorder mr, int what, int extra) { 1437 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra); 1438 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 1439 // We may have run out of space on the sdcard. 1440 stopVideoRecording(); 1441 mActivity.updateStorageSpaceAndHint(); 1442 } 1443 } 1444 1445 // from MediaRecorder.OnInfoListener 1446 @Override 1447 public void onInfo(MediaRecorder mr, int what, int extra) { 1448 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1449 if (mMediaRecorderRecording) onStopVideoRecording(); 1450 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1451 if (mMediaRecorderRecording) onStopVideoRecording(); 1452 1453 // Show the toast. 1454 Toast.makeText(mActivity, R.string.video_reach_size_limit, 1455 Toast.LENGTH_LONG).show(); 1456 } 1457 } 1458 1459 /* 1460 * Make sure we're not recording music playing in the background, ask the 1461 * MediaPlaybackService to pause playback. 1462 */ 1463 private void pauseAudioPlayback() { 1464 // Shamelessly copied from MediaPlaybackService.java, which 1465 // should be public, but isn't. 1466 Intent i = new Intent("com.android.music.musicservicecommand"); 1467 i.putExtra("command", "pause"); 1468 1469 mActivity.sendBroadcast(i); 1470 } 1471 1472 // For testing. 1473 public boolean isRecording() { 1474 return mMediaRecorderRecording; 1475 } 1476 1477 private void startVideoRecording() { 1478 Log.v(TAG, "startVideoRecording"); 1479 mUI.enablePreviewThumb(false); 1480 mActivity.setSwipingEnabled(false); 1481 1482 mActivity.updateStorageSpaceAndHint(); 1483 if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) { 1484 Log.v(TAG, "Storage issue, ignore the start request"); 1485 return; 1486 } 1487 1488 if (!mActivity.mCameraDevice.waitDone()) return; 1489 mCurrentVideoUri = null; 1490 if (effectsActive()) { 1491 initializeEffectsRecording(); 1492 if (mEffectsRecorder == null) { 1493 Log.e(TAG, "Fail to initialize effect recorder"); 1494 return; 1495 } 1496 } else { 1497 initializeRecorder(); 1498 if (mMediaRecorder == null) { 1499 Log.e(TAG, "Fail to initialize media recorder"); 1500 return; 1501 } 1502 } 1503 1504 pauseAudioPlayback(); 1505 1506 if (effectsActive()) { 1507 try { 1508 mEffectsRecorder.startRecording(); 1509 } catch (RuntimeException e) { 1510 Log.e(TAG, "Could not start effects recorder. ", e); 1511 releaseEffectsRecorder(); 1512 return; 1513 } 1514 } else { 1515 try { 1516 mMediaRecorder.start(); // Recording is now started 1517 } catch (RuntimeException e) { 1518 Log.e(TAG, "Could not start media recorder. ", e); 1519 releaseMediaRecorder(); 1520 // If start fails, frameworks will not lock the camera for us. 1521 mActivity.mCameraDevice.lock(); 1522 return; 1523 } 1524 } 1525 1526 // Make sure the video recording has started before announcing 1527 // this in accessibility. 1528 AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), 1529 mActivity.getString(R.string.video_recording_started)); 1530 1531 // The parameters might have been altered by MediaRecorder already. 1532 // We need to force mCameraDevice to refresh before getting it. 1533 mActivity.mCameraDevice.refreshParameters(); 1534 // The parameters may have been changed by MediaRecorder upon starting 1535 // recording. We need to alter the parameters if we support camcorder 1536 // zoom. To reduce latency when setting the parameters during zoom, we 1537 // update mParameters here once. 1538 if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) { 1539 mParameters = mActivity.mCameraDevice.getParameters(); 1540 } 1541 1542 mUI.enableCameraControls(false); 1543 1544 mMediaRecorderRecording = true; 1545 if (!Util.systemRotationLocked(mActivity)) { 1546 mActivity.getOrientationManager().lockOrientation(); 1547 } 1548 mRecordingStartTime = SystemClock.uptimeMillis(); 1549 mUI.showRecordingUI(true, mParameters.isZoomSupported()); 1550 1551 updateRecordingTime(); 1552 keepScreenOn(); 1553 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 1554 UsageStatistics.ACTION_CAPTURE_START, "Video"); 1555 } 1556 1557 private void showCaptureResult() { 1558 mIsInReviewMode = true; 1559 Bitmap bitmap = null; 1560 if (mVideoFileDescriptor != null) { 1561 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(), 1562 mDesiredPreviewWidth); 1563 } else if (mCurrentVideoFilename != null) { 1564 bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename, 1565 mDesiredPreviewWidth); 1566 } 1567 if (bitmap != null) { 1568 // MetadataRetriever already rotates the thumbnail. We should rotate 1569 // it to match the UI orientation (and mirror if it is front-facing camera). 1570 CameraInfo[] info = CameraHolder.instance().getCameraInfo(); 1571 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT); 1572 bitmap = Util.rotateAndMirror(bitmap, 0, mirror); 1573 mUI.showReviewImage(bitmap); 1574 } 1575 1576 mUI.showReviewControls(); 1577 mUI.enableCameraControls(false); 1578 mUI.showTimeLapseUI(false); 1579 } 1580 1581 private void hideAlert() { 1582 mUI.enableCameraControls(true); 1583 mUI.hideReviewUI(); 1584 if (mCaptureTimeLapse) { 1585 mUI.showTimeLapseUI(true); 1586 } 1587 } 1588 1589 private boolean stopVideoRecording() { 1590 Log.v(TAG, "stopVideoRecording"); 1591 mActivity.setSwipingEnabled(true); 1592 mActivity.showSwitcher(); 1593 1594 boolean fail = false; 1595 if (mMediaRecorderRecording) { 1596 boolean shouldAddToMediaStoreNow = false; 1597 1598 try { 1599 if (effectsActive()) { 1600 // This is asynchronous, so we can't add to media store now because thumbnail 1601 // may not be ready. In such case saveVideo() is called later 1602 // through a callback from the MediaEncoderFilter to EffectsRecorder, 1603 // and then to the VideoModule. 1604 mEffectsRecorder.stopRecording(); 1605 } else { 1606 mMediaRecorder.setOnErrorListener(null); 1607 mMediaRecorder.setOnInfoListener(null); 1608 mMediaRecorder.stop(); 1609 shouldAddToMediaStoreNow = true; 1610 } 1611 mCurrentVideoFilename = mVideoFilename; 1612 Log.v(TAG, "stopVideoRecording: Setting current video filename: " 1613 + mCurrentVideoFilename); 1614 AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), 1615 mActivity.getString(R.string.video_recording_stopped)); 1616 } catch (RuntimeException e) { 1617 Log.e(TAG, "stop fail", e); 1618 if (mVideoFilename != null) deleteVideoFile(mVideoFilename); 1619 fail = true; 1620 } 1621 mMediaRecorderRecording = false; 1622 if (!Util.systemRotationLocked(mActivity)) { 1623 mActivity.getOrientationManager().unlockOrientation(); 1624 } 1625 1626 // If the activity is paused, this means activity is interrupted 1627 // during recording. Release the camera as soon as possible because 1628 // face unlock or other applications may need to use the camera. 1629 // However, if the effects are active, then we can only release the 1630 // camera and cannot release the effects recorder since that will 1631 // stop the graph. It is possible to separate out the Camera release 1632 // part and the effects release part. However, the effects recorder 1633 // does hold on to the camera, hence, it needs to be "disconnected" 1634 // from the camera in the closeCamera call. 1635 if (mPaused) { 1636 // Closing only the camera part if effects active. Effects will 1637 // be closed in the callback from effects. 1638 boolean closeEffects = !effectsActive(); 1639 closeCamera(closeEffects); 1640 } 1641 1642 mUI.showRecordingUI(false, mParameters.isZoomSupported()); 1643 if (!mIsVideoCaptureIntent) { 1644 mUI.enableCameraControls(true); 1645 } 1646 // The orientation was fixed during video recording. Now make it 1647 // reflect the device orientation as video recording is stopped. 1648 mUI.setOrientationIndicator(0, true); 1649 keepScreenOnAwhile(); 1650 if (shouldAddToMediaStoreNow) { 1651 saveVideo(); 1652 } 1653 } 1654 // always release media recorder if no effects running 1655 if (!effectsActive()) { 1656 releaseMediaRecorder(); 1657 if (!mPaused) { 1658 mActivity.mCameraDevice.lock(); 1659 mActivity.mCameraDevice.waitDone(); 1660 if (ApiHelper.HAS_SURFACE_TEXTURE && 1661 !ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 1662 stopPreview(); 1663 // Switch back to use SurfaceTexture for preview. 1664 ((CameraScreenNail) mActivity.mCameraScreenNail).setOneTimeOnFrameDrawnListener( 1665 mFrameDrawnListener); 1666 startPreview(); 1667 } 1668 } 1669 } 1670 // Update the parameters here because the parameters might have been altered 1671 // by MediaRecorder. 1672 if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters(); 1673 UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, 1674 fail ? UsageStatistics.ACTION_CAPTURE_FAIL : 1675 UsageStatistics.ACTION_CAPTURE_DONE, "Video", 1676 SystemClock.uptimeMillis() - mRecordingStartTime); 1677 return fail; 1678 } 1679 1680 private void resetScreenOn() { 1681 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1682 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1683 } 1684 1685 private void keepScreenOnAwhile() { 1686 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1687 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1688 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1689 } 1690 1691 private void keepScreenOn() { 1692 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1693 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1694 } 1695 1696 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) { 1697 long seconds = milliSeconds / 1000; // round down to compute seconds 1698 long minutes = seconds / 60; 1699 long hours = minutes / 60; 1700 long remainderMinutes = minutes - (hours * 60); 1701 long remainderSeconds = seconds - (minutes * 60); 1702 1703 StringBuilder timeStringBuilder = new StringBuilder(); 1704 1705 // Hours 1706 if (hours > 0) { 1707 if (hours < 10) { 1708 timeStringBuilder.append('0'); 1709 } 1710 timeStringBuilder.append(hours); 1711 1712 timeStringBuilder.append(':'); 1713 } 1714 1715 // Minutes 1716 if (remainderMinutes < 10) { 1717 timeStringBuilder.append('0'); 1718 } 1719 timeStringBuilder.append(remainderMinutes); 1720 timeStringBuilder.append(':'); 1721 1722 // Seconds 1723 if (remainderSeconds < 10) { 1724 timeStringBuilder.append('0'); 1725 } 1726 timeStringBuilder.append(remainderSeconds); 1727 1728 // Centi seconds 1729 if (displayCentiSeconds) { 1730 timeStringBuilder.append('.'); 1731 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10; 1732 if (remainderCentiSeconds < 10) { 1733 timeStringBuilder.append('0'); 1734 } 1735 timeStringBuilder.append(remainderCentiSeconds); 1736 } 1737 1738 return timeStringBuilder.toString(); 1739 } 1740 1741 private long getTimeLapseVideoLength(long deltaMs) { 1742 // For better approximation calculate fractional number of frames captured. 1743 // This will update the video time at a higher resolution. 1744 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs; 1745 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000); 1746 } 1747 1748 private void updateRecordingTime() { 1749 if (!mMediaRecorderRecording) { 1750 return; 1751 } 1752 long now = SystemClock.uptimeMillis(); 1753 long delta = now - mRecordingStartTime; 1754 1755 // Starting a minute before reaching the max duration 1756 // limit, we'll countdown the remaining time instead. 1757 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0 1758 && delta >= mMaxVideoDurationInMs - 60000); 1759 1760 long deltaAdjusted = delta; 1761 if (countdownRemainingTime) { 1762 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999; 1763 } 1764 String text; 1765 1766 long targetNextUpdateDelay; 1767 if (!mCaptureTimeLapse) { 1768 text = millisecondToTimeString(deltaAdjusted, false); 1769 targetNextUpdateDelay = 1000; 1770 } else { 1771 // The length of time lapse video is different from the length 1772 // of the actual wall clock time elapsed. Display the video length 1773 // only in format hh:mm:ss.dd, where dd are the centi seconds. 1774 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true); 1775 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs; 1776 } 1777 1778 mUI.setRecordingTime(text); 1779 1780 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1781 // Avoid setting the color on every update, do it only 1782 // when it needs changing. 1783 mRecordingTimeCountsDown = countdownRemainingTime; 1784 1785 int color = mActivity.getResources().getColor(countdownRemainingTime 1786 ? R.color.recording_time_remaining_text 1787 : R.color.recording_time_elapsed_text); 1788 1789 mUI.setRecordingTimeTextColor(color); 1790 } 1791 1792 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); 1793 mHandler.sendEmptyMessageDelayed( 1794 UPDATE_RECORD_TIME, actualNextUpdateDelay); 1795 } 1796 1797 private static boolean isSupported(String value, List<String> supported) { 1798 return supported == null ? false : supported.indexOf(value) >= 0; 1799 } 1800 1801 @SuppressWarnings("deprecation") 1802 private void setCameraParameters() { 1803 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight); 1804 mParameters.setPreviewFrameRate(mProfile.videoFrameRate); 1805 1806 // Set flash mode. 1807 String flashMode; 1808 if (mActivity.mShowCameraAppView) { 1809 flashMode = mPreferences.getString( 1810 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 1811 mActivity.getString(R.string.pref_camera_video_flashmode_default)); 1812 } else { 1813 flashMode = Parameters.FLASH_MODE_OFF; 1814 } 1815 List<String> supportedFlash = mParameters.getSupportedFlashModes(); 1816 if (isSupported(flashMode, supportedFlash)) { 1817 mParameters.setFlashMode(flashMode); 1818 } else { 1819 flashMode = mParameters.getFlashMode(); 1820 if (flashMode == null) { 1821 flashMode = mActivity.getString( 1822 R.string.pref_camera_flashmode_no_flash); 1823 } 1824 } 1825 1826 // Set white balance parameter. 1827 String whiteBalance = mPreferences.getString( 1828 CameraSettings.KEY_WHITE_BALANCE, 1829 mActivity.getString(R.string.pref_camera_whitebalance_default)); 1830 if (isSupported(whiteBalance, 1831 mParameters.getSupportedWhiteBalance())) { 1832 mParameters.setWhiteBalance(whiteBalance); 1833 } else { 1834 whiteBalance = mParameters.getWhiteBalance(); 1835 if (whiteBalance == null) { 1836 whiteBalance = Parameters.WHITE_BALANCE_AUTO; 1837 } 1838 } 1839 1840 // Set zoom. 1841 if (mParameters.isZoomSupported()) { 1842 mParameters.setZoom(mZoomValue); 1843 } 1844 1845 // Set continuous autofocus. 1846 List<String> supportedFocus = mParameters.getSupportedFocusModes(); 1847 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) { 1848 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 1849 } 1850 1851 mParameters.set(Util.RECORDING_HINT, Util.TRUE); 1852 1853 // Enable video stabilization. Convenience methods not available in API 1854 // level <= 14 1855 String vstabSupported = mParameters.get("video-stabilization-supported"); 1856 if ("true".equals(vstabSupported)) { 1857 mParameters.set("video-stabilization", "true"); 1858 } 1859 1860 // Set picture size. 1861 // The logic here is different from the logic in still-mode camera. 1862 // There we determine the preview size based on the picture size, but 1863 // here we determine the picture size based on the preview size. 1864 List<Size> supported = mParameters.getSupportedPictureSizes(); 1865 Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported, 1866 (double) mDesiredPreviewWidth / mDesiredPreviewHeight); 1867 Size original = mParameters.getPictureSize(); 1868 if (!original.equals(optimalSize)) { 1869 mParameters.setPictureSize(optimalSize.width, optimalSize.height); 1870 } 1871 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" + 1872 optimalSize.height); 1873 1874 // Set JPEG quality. 1875 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId, 1876 CameraProfile.QUALITY_HIGH); 1877 mParameters.setJpegQuality(jpegQuality); 1878 1879 mActivity.mCameraDevice.setParameters(mParameters); 1880 // Keep preview size up to date. 1881 mParameters = mActivity.mCameraDevice.getParameters(); 1882 1883 updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight); 1884 } 1885 1886 private void updateCameraScreenNailSize(int width, int height) { 1887 if (!ApiHelper.HAS_SURFACE_TEXTURE) return; 1888 1889 if (mCameraDisplayOrientation % 180 != 0) { 1890 int tmp = width; 1891 width = height; 1892 height = tmp; 1893 } 1894 1895 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 1896 int oldWidth = screenNail.getWidth(); 1897 int oldHeight = screenNail.getHeight(); 1898 1899 if (oldWidth != width || oldHeight != height) { 1900 screenNail.setSize(width, height); 1901 screenNail.enableAspectRatioClamping(); 1902 mActivity.notifyScreenNailChanged(); 1903 } 1904 1905 if (screenNail.getSurfaceTexture() == null) { 1906 screenNail.acquireSurfaceTexture(); 1907 } 1908 } 1909 1910 @Override 1911 public void onActivityResult(int requestCode, int resultCode, Intent data) { 1912 switch (requestCode) { 1913 case REQUEST_EFFECT_BACKDROPPER: 1914 if (resultCode == Activity.RESULT_OK) { 1915 // onActivityResult() runs before onResume(), so this parameter will be 1916 // seen by startPreview from onResume() 1917 mEffectUriFromGallery = data.getData().toString(); 1918 Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery); 1919 mResetEffect = false; 1920 } else { 1921 mEffectUriFromGallery = null; 1922 Log.w(TAG, "No URI from gallery"); 1923 mResetEffect = true; 1924 } 1925 break; 1926 } 1927 } 1928 1929 @Override 1930 public void onEffectsUpdate(int effectId, int effectMsg) { 1931 Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg); 1932 if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) { 1933 // Effects have shut down. Hide learning message if any, 1934 // and restart regular preview. 1935 checkQualityAndStartPreview(); 1936 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) { 1937 // This follows the codepath from onStopVideoRecording. 1938 if (mEffectsDisplayResult) { 1939 saveVideo(); 1940 if (mIsVideoCaptureIntent) { 1941 if (mQuickCapture) { 1942 doReturnToCaller(true); 1943 } else { 1944 showCaptureResult(); 1945 } 1946 } 1947 } 1948 mEffectsDisplayResult = false; 1949 // In onPause, these were not called if the effects were active. We 1950 // had to wait till the effects recording is complete to do this. 1951 if (mPaused) { 1952 closeVideoFileDescriptor(); 1953 } 1954 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) { 1955 // Enable the shutter button once the preview is complete. 1956 mUI.enableShutter(true); 1957 } 1958 // In onPause, this was not called if the effects were active. We had to 1959 // wait till the effects completed to do this. 1960 if (mPaused) { 1961 Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused"); 1962 closeEffects(); 1963 } 1964 } 1965 1966 public void onCancelBgTraining(View v) { 1967 // Write default effect out to shared prefs 1968 writeDefaultEffectToPrefs(); 1969 // Tell VideoCamer to re-init based on new shared pref values. 1970 onSharedPreferenceChanged(); 1971 } 1972 1973 @Override 1974 public synchronized void onEffectsError(Exception exception, String fileName) { 1975 // TODO: Eventually we may want to show the user an error dialog, and then restart the 1976 // camera and encoder gracefully. For now, we just delete the file and bail out. 1977 if (fileName != null && new File(fileName).exists()) { 1978 deleteVideoFile(fileName); 1979 } 1980 try { 1981 if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException") 1982 .isInstance(exception)) { 1983 Log.w(TAG, "Problem recoding video file. Removing incomplete file."); 1984 return; 1985 } 1986 } catch (ClassNotFoundException ex) { 1987 Log.w(TAG, ex); 1988 } 1989 throw new RuntimeException("Error during recording!", exception); 1990 } 1991 1992 @Override 1993 public void onConfigurationChanged(Configuration newConfig) { 1994 Log.v(TAG, "onConfigurationChanged"); 1995 setDisplayOrientation(); 1996 } 1997 1998 @Override 1999 public void onOverriddenPreferencesClicked() { 2000 } 2001 2002 @Override 2003 // TODO: Delete this after old camera code is removed 2004 public void onRestorePreferencesClicked() { 2005 } 2006 2007 private boolean effectsActive() { 2008 return (mEffectType != EffectsRecorder.EFFECT_NONE); 2009 } 2010 2011 @Override 2012 public void onSharedPreferenceChanged() { 2013 // ignore the events after "onPause()" or preview has not started yet 2014 if (mPaused) return; 2015 synchronized (mPreferences) { 2016 // If mCameraDevice is not ready then we can set the parameter in 2017 // startPreview(). 2018 if (mActivity.mCameraDevice == null) return; 2019 2020 boolean recordLocation = RecordLocationPreference.get( 2021 mPreferences, mContentResolver); 2022 mLocationManager.recordLocation(recordLocation); 2023 2024 // Check if the current effects selection has changed 2025 if (updateEffectSelection()) return; 2026 2027 readVideoPreferences(); 2028 mUI.showTimeLapseUI(mCaptureTimeLapse); 2029 // We need to restart the preview if preview size is changed. 2030 Size size = mParameters.getPreviewSize(); 2031 if (size.width != mDesiredPreviewWidth 2032 || size.height != mDesiredPreviewHeight) { 2033 if (!effectsActive()) { 2034 stopPreview(); 2035 } else { 2036 mEffectsRecorder.release(); 2037 mEffectsRecorder = null; 2038 } 2039 resizeForPreviewAspectRatio(); 2040 startPreview(); // Parameters will be set in startPreview(). 2041 } else { 2042 setCameraParameters(); 2043 } 2044 mUI.updateOnScreenIndicators(mParameters, mPreferences); 2045 } 2046 } 2047 2048 protected void setCameraId(int cameraId) { 2049 ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID); 2050 pref.setValue("" + cameraId); 2051 } 2052 2053 private void switchCamera() { 2054 if (mPaused) return; 2055 2056 Log.d(TAG, "Start to switch camera."); 2057 mCameraId = mPendingSwitchCameraId; 2058 mPendingSwitchCameraId = -1; 2059 setCameraId(mCameraId); 2060 2061 closeCamera(); 2062 mUI.collapseCameraControls(); 2063 // Restart the camera and initialize the UI. From onCreate. 2064 mPreferences.setLocalId(mActivity, mCameraId); 2065 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); 2066 openCamera(); 2067 readVideoPreferences(); 2068 startPreview(); 2069 initializeVideoSnapshot(); 2070 resizeForPreviewAspectRatio(); 2071 initializeVideoControl(); 2072 2073 // From onResume 2074 mUI.initializeZoom(mParameters); 2075 mUI.setOrientationIndicator(0, false); 2076 2077 if (ApiHelper.HAS_SURFACE_TEXTURE) { 2078 // Start switch camera animation. Post a message because 2079 // onFrameAvailable from the old camera may already exist. 2080 mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION); 2081 } 2082 mUI.updateOnScreenIndicators(mParameters, mPreferences); 2083 } 2084 2085 // Preview texture has been copied. Now camera can be released and the 2086 // animation can be started. 2087 @Override 2088 public void onPreviewTextureCopied() { 2089 mHandler.sendEmptyMessage(SWITCH_CAMERA); 2090 } 2091 2092 @Override 2093 public void onCaptureTextureCopied() { 2094 } 2095 2096 private boolean updateEffectSelection() { 2097 int previousEffectType = mEffectType; 2098 Object previousEffectParameter = mEffectParameter; 2099 mEffectType = CameraSettings.readEffectType(mPreferences); 2100 mEffectParameter = CameraSettings.readEffectParameter(mPreferences); 2101 2102 if (mEffectType == previousEffectType) { 2103 if (mEffectType == EffectsRecorder.EFFECT_NONE) return false; 2104 if (mEffectParameter.equals(previousEffectParameter)) return false; 2105 } 2106 Log.v(TAG, "New effect selection: " + mPreferences.getString( 2107 CameraSettings.KEY_VIDEO_EFFECT, "none")); 2108 2109 if (mEffectType == EffectsRecorder.EFFECT_NONE) { 2110 // Stop effects and return to normal preview 2111 mEffectsRecorder.stopPreview(); 2112 mPreviewing = false; 2113 return true; 2114 } 2115 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER && 2116 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) { 2117 // Request video from gallery to use for background 2118 Intent i = new Intent(Intent.ACTION_PICK); 2119 i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI, 2120 "video/*"); 2121 i.putExtra(Intent.EXTRA_LOCAL_ONLY, true); 2122 mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER); 2123 return true; 2124 } 2125 if (previousEffectType == EffectsRecorder.EFFECT_NONE) { 2126 // Stop regular preview and start effects. 2127 stopPreview(); 2128 checkQualityAndStartPreview(); 2129 } else { 2130 // Switch currently running effect 2131 mEffectsRecorder.setEffect(mEffectType, mEffectParameter); 2132 } 2133 return true; 2134 } 2135 2136 // Verifies that the current preview view size is correct before starting 2137 // preview. If not, resets the surface texture and resizes the view. 2138 private void checkQualityAndStartPreview() { 2139 readVideoPreferences(); 2140 mUI.showTimeLapseUI(mCaptureTimeLapse); 2141 Size size = mParameters.getPreviewSize(); 2142 if (size.width != mDesiredPreviewWidth 2143 || size.height != mDesiredPreviewHeight) { 2144 resizeForPreviewAspectRatio(); 2145 } 2146 // Start up preview again 2147 startPreview(); 2148 } 2149 2150 @Override 2151 public boolean dispatchTouchEvent(MotionEvent m) { 2152 if (mSwitchingCamera) return true; 2153 return mUI.dispatchTouchEvent(m); 2154 } 2155 2156 private void initializeVideoSnapshot() { 2157 if (mParameters == null) return; 2158 if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { 2159 mActivity.setSingleTapUpListener(mUI.getPreview()); 2160 // Show the tap to focus toast if this is the first start. 2161 if (mPreferences.getBoolean( 2162 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) { 2163 // Delay the toast for one second to wait for orientation. 2164 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000); 2165 } 2166 } else { 2167 mActivity.setSingleTapUpListener(null); 2168 } 2169 } 2170 2171 void showVideoSnapshotUI(boolean enabled) { 2172 if (mParameters == null) return; 2173 if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { 2174 if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) { 2175 ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); 2176 } else { 2177 mUI.showPreviewBorder(enabled); 2178 } 2179 mUI.enableShutter(!enabled); 2180 } 2181 } 2182 2183 @Override 2184 public void updateCameraAppView() { 2185 if (!mPreviewing || mParameters.getFlashMode() == null) return; 2186 2187 // When going to and back from gallery, we need to turn off/on the flash. 2188 if (!mActivity.mShowCameraAppView) { 2189 if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) { 2190 mRestoreFlash = false; 2191 return; 2192 } 2193 mRestoreFlash = true; 2194 setCameraParameters(); 2195 } else if (mRestoreFlash) { 2196 mRestoreFlash = false; 2197 setCameraParameters(); 2198 } 2199 } 2200 2201 @Override 2202 public void onFullScreenChanged(boolean full) { 2203 mUI.onFullScreenChanged(full); 2204 if (ApiHelper.HAS_SURFACE_TEXTURE) { 2205 if (mActivity.mCameraScreenNail != null) { 2206 ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full); 2207 } 2208 return; 2209 } 2210 } 2211 2212 private final class JpegPictureCallback implements PictureCallback { 2213 Location mLocation; 2214 2215 public JpegPictureCallback(Location loc) { 2216 mLocation = loc; 2217 } 2218 2219 @Override 2220 public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) { 2221 Log.v(TAG, "onPictureTaken"); 2222 mSnapshotInProgress = false; 2223 showVideoSnapshotUI(false); 2224 storeImage(jpegData, mLocation); 2225 } 2226 } 2227 2228 private void storeImage(final byte[] data, Location loc) { 2229 long dateTaken = System.currentTimeMillis(); 2230 String title = Util.createJpegName(dateTaken); 2231 ExifInterface exif = Exif.getExif(data); 2232 int orientation = Exif.getOrientation(exif); 2233 Size s = mParameters.getPictureSize(); 2234 mActivity.getMediaSaveService().addImage( 2235 data, title, dateTaken, loc, s.width, s.height, orientation, 2236 exif, mOnPhotoSavedListener, mContentResolver); 2237 } 2238 2239 private boolean resetEffect() { 2240 if (mResetEffect) { 2241 String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT, 2242 mPrefVideoEffectDefault); 2243 if (!mPrefVideoEffectDefault.equals(value)) { 2244 writeDefaultEffectToPrefs(); 2245 return true; 2246 } 2247 } 2248 mResetEffect = true; 2249 return false; 2250 } 2251 2252 private String convertOutputFormatToMimeType(int outputFileFormat) { 2253 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) { 2254 return "video/mp4"; 2255 } 2256 return "video/3gpp"; 2257 } 2258 2259 private String convertOutputFormatToFileExt(int outputFileFormat) { 2260 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) { 2261 return ".mp4"; 2262 } 2263 return ".3gp"; 2264 } 2265 2266 private void closeVideoFileDescriptor() { 2267 if (mVideoFileDescriptor != null) { 2268 try { 2269 mVideoFileDescriptor.close(); 2270 } catch (IOException e) { 2271 Log.e(TAG, "Fail to close fd", e); 2272 } 2273 mVideoFileDescriptor = null; 2274 } 2275 } 2276 2277 private void showTapToSnapshotToast() { 2278 new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0) 2279 .show(); 2280 // Clear the preference. 2281 Editor editor = mPreferences.edit(); 2282 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false); 2283 editor.apply(); 2284 } 2285 2286 @Override 2287 public boolean updateStorageHintOnResume() { 2288 return true; 2289 } 2290 2291 // required by OnPreferenceChangedListener 2292 @Override 2293 public void onCameraPickerClicked(int cameraId) { 2294 if (mPaused || mPendingSwitchCameraId != -1) return; 2295 2296 mPendingSwitchCameraId = cameraId; 2297 if (ApiHelper.HAS_SURFACE_TEXTURE) { 2298 Log.d(TAG, "Start to copy texture."); 2299 // We need to keep a preview frame for the animation before 2300 // releasing the camera. This will trigger onPreviewTextureCopied. 2301 ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture(); 2302 // Disable all camera controls. 2303 mSwitchingCamera = true; 2304 } else { 2305 switchCamera(); 2306 } 2307 } 2308 2309 @Override 2310 public boolean needsSwitcher() { 2311 return !mIsVideoCaptureIntent; 2312 } 2313 2314 @Override 2315 public boolean needsPieMenu() { 2316 return true; 2317 } 2318 2319 @Override 2320 public void onShowSwitcherPopup() { 2321 mUI.onShowSwitcherPopup(); 2322 } 2323 2324 @Override 2325 public void onMediaSaveServiceConnected(MediaSaveService s) { 2326 // do nothing. 2327 } 2328} 2329