VideoModule.java revision 9713c56dfe1c298ab0c2c75d324dc69395d1c809
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.hardware.Camera.CameraInfo; 32import android.hardware.Camera.Parameters; 33import android.hardware.Camera.PictureCallback; 34import android.hardware.Camera.Size; 35import android.location.Location; 36import android.media.CamcorderProfile; 37import android.media.CameraProfile; 38import android.media.MediaRecorder; 39import android.net.Uri; 40import android.os.Build; 41import android.os.Bundle; 42import android.os.Handler; 43import android.os.Message; 44import android.os.ParcelFileDescriptor; 45import android.os.SystemClock; 46import android.provider.MediaStore; 47import android.provider.MediaStore.Video; 48import android.util.Log; 49import android.view.Gravity; 50import android.view.KeyEvent; 51import android.view.LayoutInflater; 52import android.view.MotionEvent; 53import android.view.OrientationEventListener; 54import android.view.SurfaceHolder; 55import android.view.View; 56import android.view.View.OnClickListener; 57import android.view.ViewGroup; 58import android.view.WindowManager; 59import android.widget.FrameLayout; 60import android.widget.FrameLayout.LayoutParams; 61import android.widget.ImageView; 62import android.widget.LinearLayout; 63import android.widget.TextView; 64import android.widget.Toast; 65 66import com.android.camera.ui.AbstractSettingPopup; 67import com.android.camera.ui.PieRenderer; 68import com.android.camera.ui.PopupManager; 69import com.android.camera.ui.PreviewSurfaceView; 70import com.android.camera.ui.RenderOverlay; 71import com.android.camera.ui.Rotatable; 72import com.android.camera.ui.RotateImageView; 73import com.android.camera.ui.RotateLayout; 74import com.android.camera.ui.RotateTextToast; 75import com.android.camera.ui.TwoStateImageView; 76import com.android.camera.ui.ZoomRenderer; 77import com.android.gallery3d.common.ApiHelper; 78 79import java.io.File; 80import java.io.IOException; 81import java.text.SimpleDateFormat; 82import java.util.Date; 83import java.util.Iterator; 84import java.util.List; 85 86public class VideoModule implements CameraModule, 87 CameraPreference.OnPreferenceChangedListener, 88 ShutterButton.OnShutterButtonListener, 89 MediaRecorder.OnErrorListener, 90 MediaRecorder.OnInfoListener, 91 EffectsRecorder.EffectsListener, 92 PieRenderer.PieListener { 93 94 private static final String TAG = "CAM_VideoModule"; 95 96 // We number the request code from 1000 to avoid collision with Gallery. 97 private static final int REQUEST_EFFECT_BACKDROPPER = 1000; 98 99 private static final int CHECK_DISPLAY_ROTATION = 3; 100 private static final int CLEAR_SCREEN_DELAY = 4; 101 private static final int UPDATE_RECORD_TIME = 5; 102 private static final int ENABLE_SHUTTER_BUTTON = 6; 103 private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7; 104 private static final int SWITCH_CAMERA = 8; 105 private static final int SWITCH_CAMERA_START_ANIMATION = 9; 106 private static final int HIDE_SURFACE_VIEW = 10; 107 108 private static final int SCREEN_DELAY = 2 * 60 * 1000; 109 110 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms 111 112 /** 113 * An unpublished intent flag requesting to start recording straight away 114 * and return as soon as recording is stopped. 115 * TODO: consider publishing by moving into MediaStore. 116 */ 117 private static final String EXTRA_QUICK_CAPTURE = 118 "android.intent.extra.quickCapture"; 119 120 private static final int MIN_THUMB_SIZE = 64; 121 // module fields 122 private CameraActivity mActivity; 123 private View mRootView; 124 private boolean mPaused; 125 private int mCameraId; 126 private Parameters mParameters; 127 128 private boolean mSnapshotInProgress = false; 129 130 private static final String EFFECT_BG_FROM_GALLERY = "gallery"; 131 132 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback(); 133 134 private ComboPreferences mPreferences; 135 private PreferenceGroup mPreferenceGroup; 136 137 private PreviewFrameLayout mPreviewFrameLayout; 138 private boolean mSurfaceViewReady; 139 private SurfaceHolder.Callback mSurfaceViewCallback; 140 private PreviewSurfaceView mPreviewSurfaceView; 141 private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener; 142 private View mReviewControl; 143 private RotateDialogController mRotateDialog; 144 145 // An review image having same size as preview. It is displayed when 146 // recording is stopped in capture intent. 147 private ImageView mReviewImage; 148 private Rotatable mReviewCancelButton; 149 private Rotatable mReviewDoneButton; 150 private RotateImageView mReviewPlayButton; 151 private View mReviewRetakeButton; 152 private ShutterButton mShutterButton; 153 private TextView mRecordingTimeView; 154 private RotateLayout mBgLearningMessageRotater; 155 private View mBgLearningMessageFrame; 156 private LinearLayout mLabelsLinearLayout; 157 158 private boolean mIsVideoCaptureIntent; 159 private boolean mQuickCapture; 160 161 private MediaRecorder mMediaRecorder; 162 private EffectsRecorder mEffectsRecorder; 163 private boolean mEffectsDisplayResult; 164 165 private int mEffectType = EffectsRecorder.EFFECT_NONE; 166 private Object mEffectParameter = null; 167 private String mEffectUriFromGallery = null; 168 private String mPrefVideoEffectDefault; 169 private boolean mResetEffect = true; 170 171 private boolean mSwitchingCamera; 172 private boolean mMediaRecorderRecording = false; 173 private long mRecordingStartTime; 174 private boolean mRecordingTimeCountsDown = false; 175 private RotateLayout mRecordingTimeRect; 176 private long mOnResumeTime; 177 // The video file that the hardware camera is about to record into 178 // (or is recording into.) 179 private String mVideoFilename; 180 private ParcelFileDescriptor mVideoFileDescriptor; 181 182 // The video file that has already been recorded, and that is being 183 // examined by the user. 184 private String mCurrentVideoFilename; 185 private Uri mCurrentVideoUri; 186 private ContentValues mCurrentVideoValues; 187 188 private CamcorderProfile mProfile; 189 190 // The video duration limit. 0 menas no limit. 191 private int mMaxVideoDurationInMs; 192 193 // Time Lapse parameters. 194 private boolean mCaptureTimeLapse = false; 195 // Default 0. If it is larger than 0, the camcorder is in time lapse mode. 196 private int mTimeBetweenTimeLapseFrameCaptureMs = 0; 197 private View mTimeLapseLabel; 198 199 private int mDesiredPreviewWidth; 200 private int mDesiredPreviewHeight; 201 202 boolean mPreviewing = false; // True if preview is started. 203 // The display rotation in degrees. This is only valid when mPreviewing is 204 // true. 205 private int mDisplayRotation; 206 private int mCameraDisplayOrientation; 207 208 private ContentResolver mContentResolver; 209 210 private LocationManager mLocationManager; 211 212 private VideoNamer mVideoNamer; 213 214 private RenderOverlay mRenderOverlay; 215 private PieRenderer mPieRenderer; 216 217 private VideoController mVideoControl; 218 private AbstractSettingPopup mPopup; 219 private int mPendingSwitchCameraId; 220 221 private ZoomRenderer mZoomRenderer; 222 223 private PreviewGestures mGestures; 224 225 private final Handler mHandler = new MainHandler(); 226 227 private MyOrientationEventListener mOrientationListener; 228 // The degrees of the device rotated clockwise from its natural orientation. 229 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; 230 // The orientation compensation for icons and thumbnails. Ex: if the value 231 // is 90, the UI components should be rotated 90 degrees counter-clockwise. 232 private int mOrientationCompensation = 0; 233 // The orientation compensation when we start recording. 234 private int mOrientationCompensationAtRecordStart; 235 236 private int mZoomValue; // The current zoom value. 237 private int mZoomMax; 238 private boolean mRestoreFlash; // This is used to check if we need to restore the flash 239 // status when going back from gallery. 240 241 protected class CameraOpenThread extends Thread { 242 @Override 243 public void run() { 244 try { 245 mActivity.mCameraDevice = Util.openCamera(mActivity, mCameraId); 246 mParameters = mActivity.mCameraDevice.getParameters(); 247 } catch (CameraHardwareException e) { 248 mActivity.mOpenCameraFail = true; 249 } catch (CameraDisabledException e) { 250 mActivity.mCameraDisabled = true; 251 } 252 } 253 } 254 255 // This Handler is used to post message back onto the main thread of the 256 // application 257 private class MainHandler extends Handler { 258 @Override 259 public void handleMessage(Message msg) { 260 switch (msg.what) { 261 262 case ENABLE_SHUTTER_BUTTON: 263 mShutterButton.setEnabled(true); 264 break; 265 266 case CLEAR_SCREEN_DELAY: { 267 mActivity.getWindow().clearFlags( 268 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 269 break; 270 } 271 272 case UPDATE_RECORD_TIME: { 273 updateRecordingTime(); 274 break; 275 } 276 277 case CHECK_DISPLAY_ROTATION: { 278 // Restart the preview if display rotation has changed. 279 // Sometimes this happens when the device is held upside 280 // down and camera app is opened. Rotation animation will 281 // take some time and the rotation value we have got may be 282 // wrong. Framework does not have a callback for this now. 283 if ((Util.getDisplayRotation(mActivity) != mDisplayRotation) 284 && !mMediaRecorderRecording && !mSwitchingCamera) { 285 startPreview(); 286 } 287 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) { 288 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); 289 } 290 break; 291 } 292 293 case SHOW_TAP_TO_SNAPSHOT_TOAST: { 294 showTapToSnapshotToast(); 295 break; 296 } 297 298 case SWITCH_CAMERA: { 299 switchCamera(); 300 break; 301 } 302 303 case SWITCH_CAMERA_START_ANIMATION: { 304 ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera(); 305 306 // Enable all camera controls. 307 mSwitchingCamera = false; 308 break; 309 } 310 311 case HIDE_SURFACE_VIEW: { 312 mPreviewSurfaceView.setVisibility(View.GONE); 313 break; 314 } 315 316 default: 317 Log.v(TAG, "Unhandled message: " + msg.what); 318 break; 319 } 320 } 321 } 322 323 private BroadcastReceiver mReceiver = null; 324 325 private class MyBroadcastReceiver extends BroadcastReceiver { 326 @Override 327 public void onReceive(Context context, Intent intent) { 328 String action = intent.getAction(); 329 if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 330 stopVideoRecording(); 331 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 332 Toast.makeText(mActivity, 333 mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show(); 334 } 335 } 336 } 337 338 private String createName(long dateTaken) { 339 Date date = new Date(dateTaken); 340 SimpleDateFormat dateFormat = new SimpleDateFormat( 341 mActivity.getString(R.string.video_file_name_format)); 342 343 return dateFormat.format(date); 344 } 345 346 private int getPreferredCameraId(ComboPreferences preferences) { 347 int intentCameraId = Util.getCameraFacingIntentExtras(mActivity); 348 if (intentCameraId != -1) { 349 // Testing purpose. Launch a specific camera through the intent 350 // extras. 351 return intentCameraId; 352 } else { 353 return CameraSettings.readPreferredCameraId(preferences); 354 } 355 } 356 357 private void initializeSurfaceView() { 358 mPreviewSurfaceView = (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view); 359 if (!ApiHelper.HAS_SURFACE_TEXTURE) { // API level < 11 360 if (mSurfaceViewCallback == null) { 361 mSurfaceViewCallback = new SurfaceViewCallback(); 362 } 363 mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback); 364 mPreviewSurfaceView.setVisibility(View.VISIBLE); 365 } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 366 if (mSurfaceViewCallback == null) { 367 mSurfaceViewCallback = new SurfaceViewCallback(); 368 mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() { 369 @Override 370 public void onFrameDrawn(CameraScreenNail c) { 371 mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW); 372 } 373 }; 374 } 375 mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback); 376 } 377 } 378 379 private void initializeOverlay() { 380 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); 381 if (mPieRenderer == null) { 382 mPieRenderer = new PieRenderer(mActivity); 383 mRenderOverlay.addRenderer(mPieRenderer); 384 mVideoControl = new VideoController(mActivity, this, mPieRenderer); 385 mVideoControl.setListener(this); 386 mPieRenderer.setPieListener(this); 387 } 388 if (mZoomRenderer == null) { 389 mZoomRenderer = new ZoomRenderer(mActivity); 390 mRenderOverlay.addRenderer(mZoomRenderer); 391 } 392 if (mGestures == null) { 393 mGestures = new PreviewGestures(mActivity, this, mRenderOverlay, 394 mZoomRenderer, mPieRenderer); 395 if (isVideoCaptureIntent()) { 396 if (mReviewCancelButton != null) { 397 mGestures.addTouchReceiver((View) mReviewCancelButton); 398 } 399 if (mReviewDoneButton != null) { 400 mGestures.addTouchReceiver((View) mReviewDoneButton); 401 } 402 } 403 } 404 } 405 406 @Override 407 public void init(CameraActivity activity, View root, boolean reuseScreenNail) { 408 mActivity = activity; 409 mRootView = root; 410 mPreferences = new ComboPreferences(mActivity); 411 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); 412 mCameraId = getPreferredCameraId(mPreferences); 413 414 mPreferences.setLocalId(mActivity, mCameraId); 415 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); 416 417 mActivity.mNumberOfCameras = CameraHolder.instance().getNumberOfCameras(); 418 mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default); 419 resetEffect(); 420 421 /* 422 * To reduce startup time, we start the preview in another thread. 423 * We make sure the preview is started at the end of onCreate. 424 */ 425 CameraOpenThread cameraOpenThread = new CameraOpenThread(); 426 cameraOpenThread.start(); 427 428 mContentResolver = mActivity.getContentResolver(); 429 430 mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView); 431 432 // Surface texture is from camera screen nail and startPreview needs it. 433 // This must be done before startPreview. 434 mIsVideoCaptureIntent = isVideoCaptureIntent(); 435 if (reuseScreenNail) { 436 mActivity.reuseCameraScreenNail(!mIsVideoCaptureIntent); 437 } else { 438 mActivity.createCameraScreenNail(!mIsVideoCaptureIntent); 439 } 440 initializeSurfaceView(); 441 442 // Make sure camera device is opened. 443 try { 444 cameraOpenThread.join(); 445 if (mActivity.mOpenCameraFail) { 446 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); 447 return; 448 } else if (mActivity.mCameraDisabled) { 449 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 450 return; 451 } 452 } catch (InterruptedException ex) { 453 // ignore 454 } 455 456 Thread startPreviewThread = new Thread(new Runnable() { 457 @Override 458 public void run() { 459 readVideoPreferences(); 460 startPreview(); 461 } 462 }); 463 startPreviewThread.start(); 464 465 initializeControlByIntent(); 466 initializeOverlay(); 467 initializeMiscControls(); 468 469 mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog); 470 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); 471 mOrientationListener = new MyOrientationEventListener(mActivity); 472 mLocationManager = new LocationManager(mActivity, null); 473 474 // Make sure preview is started. 475 try { 476 startPreviewThread.join(); 477 if (mActivity.mOpenCameraFail) { 478 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); 479 return; 480 } else if (mActivity.mCameraDisabled) { 481 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 482 return; 483 } 484 } catch (InterruptedException ex) { 485 // ignore 486 } 487 488 showTimeLapseUI(mCaptureTimeLapse); 489 initializeVideoSnapshot(); 490 resizeForPreviewAspectRatio(); 491 492 initializeVideoControl(); 493 mPendingSwitchCameraId = -1; 494 } 495 496 @Override 497 public void onStop() {} 498 499 private void loadCameraPreferences() { 500 CameraSettings settings = new CameraSettings(mActivity, mParameters, 501 mCameraId, CameraHolder.instance().getCameraInfo()); 502 // Remove the video quality preference setting when the quality is given in the intent. 503 mPreferenceGroup = filterPreferenceScreenByIntent( 504 settings.getPreferenceGroup(R.xml.video_preferences)); 505 } 506 507 @Override 508 public boolean collapseCameraControls() { 509 if (mPopup != null) { 510 dismissPopup(); 511 return true; 512 } 513 return false; 514 } 515 516 private void enableCameraControls(boolean enable) { 517 if (mGestures != null) { 518 mGestures.setZoomOnly(!enable); 519 } 520 } 521 522 private void initializeVideoControl() { 523 loadCameraPreferences(); 524 mVideoControl.initialize(mPreferenceGroup); 525 if (effectsActive()) { 526 mVideoControl.overrideSettings( 527 CameraSettings.KEY_VIDEO_QUALITY, 528 Integer.toString(getLowVideoQuality())); 529 } 530 } 531 532 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 533 private static int getLowVideoQuality() { 534 if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) { 535 return CamcorderProfile.QUALITY_480P; 536 } else { 537 return CamcorderProfile.QUALITY_LOW; 538 } 539 } 540 541 private class MyOrientationEventListener 542 extends OrientationEventListener { 543 public MyOrientationEventListener(Context context) { 544 super(context); 545 } 546 547 @Override 548 public void onOrientationChanged(int orientation) { 549 // We keep the last known orientation. So if the user first orient 550 // the camera then point the camera to floor or sky, we still have 551 // the correct orientation. 552 if (orientation == ORIENTATION_UNKNOWN) return; 553 int newOrientation = Util.roundOrientation(orientation, mOrientation); 554 555 if (mOrientation != newOrientation) { 556 mOrientation = newOrientation; 557 // The input of effects recorder is affected by 558 // android.hardware.Camera.setDisplayOrientation. Its value only 559 // compensates the camera orientation (no Display.getRotation). 560 // So the orientation hint here should only consider sensor 561 // orientation. 562 if (effectsActive()) { 563 mEffectsRecorder.setOrientationHint(mOrientation); 564 } 565 } 566 567 // When the screen is unlocked, display rotation may change. Always 568 // calculate the up-to-date orientationCompensation. 569 int orientationCompensation = 570 (mOrientation + Util.getDisplayRotation(mActivity)) % 360; 571 572 if (mOrientationCompensation != orientationCompensation) { 573 mOrientationCompensation = orientationCompensation; 574 // Do not rotate the icons during recording because the video 575 // orientation is fixed after recording. 576 if (!mMediaRecorderRecording) { 577 setOrientationIndicator(mOrientationCompensation, true); 578 } 579 } 580 581 // Show the toast after getting the first orientation changed. 582 if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) { 583 mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST); 584 showTapToSnapshotToast(); 585 } 586 } 587 } 588 589 private void setOrientationIndicator(int orientation, boolean animation) { 590 Rotatable[] indicators = { 591 mRenderOverlay, 592 mBgLearningMessageRotater, 593 mReviewDoneButton, mReviewPlayButton, mRotateDialog}; 594 for (Rotatable indicator : indicators) { 595 if (indicator != null) indicator.setOrientation(orientation, animation); 596 } 597 598 // We change the orientation of the review cancel button only for tablet 599 // UI because there's a label along with the X icon. For phone UI, we 600 // don't change the orientation because there's only a symmetrical X 601 // icon. 602 if (mReviewCancelButton instanceof RotateLayout) { 603 mReviewCancelButton.setOrientation(orientation, animation); 604 } 605 606 // We change the orientation of the linearlayout only for phone UI because when in portrait 607 // the width is not enough. 608 if (mLabelsLinearLayout != null) { 609 if (((orientation / 90) & 1) == 0) { 610 mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); 611 } else { 612 mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 613 } 614 } 615 mRecordingTimeRect.setOrientation(mOrientationCompensation, animation); 616 } 617 618 private void startPlayVideoActivity() { 619 Intent intent = new Intent(Intent.ACTION_VIEW); 620 intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat)); 621 try { 622 mActivity.startActivity(intent); 623 } catch (ActivityNotFoundException ex) { 624 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 625 } 626 } 627 628 @OnClickAttr 629 public void onThumbnailClicked(View v) { 630 if (!mMediaRecorderRecording && mActivity.mThumbnail != null 631 && !mSwitchingCamera) { 632 mActivity.gotoGallery(); 633 } 634 } 635 636 @OnClickAttr 637 public void onReviewRetakeClicked(View v) { 638 deleteCurrentVideo(); 639 hideAlert(); 640 } 641 642 @OnClickAttr 643 public void onReviewPlayClicked(View v) { 644 startPlayVideoActivity(); 645 } 646 647 @OnClickAttr 648 public void onReviewDoneClicked(View v) { 649 doReturnToCaller(true); 650 } 651 652 @OnClickAttr 653 public void onReviewCancelClicked(View v) { 654 stopVideoRecording(); 655 doReturnToCaller(false); 656 } 657 658 private void onStopVideoRecording() { 659 mEffectsDisplayResult = true; 660 boolean recordFail = stopVideoRecording(); 661 if (mIsVideoCaptureIntent) { 662 if (!effectsActive()) { 663 if (mQuickCapture) { 664 doReturnToCaller(!recordFail); 665 } else if (!recordFail) { 666 showAlert(); 667 } 668 } 669 } else if (!recordFail){ 670 // Start capture animation. 671 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 672 // The capture animation is disabled on ICS because we use SurfaceView 673 // for preview during recording. When the recording is done, we switch 674 // back to use SurfaceTexture for preview and we need to stop then start 675 // the preview. This will cause the preview flicker since the preview 676 // will not be continuous for a short period of time. 677 ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(getCameraRotation()); 678 } 679 if (!effectsActive()) getThumbnail(); 680 } 681 } 682 683 private int getCameraRotation() { 684 return (mOrientationCompensation - mDisplayRotation + 360) % 360; 685 } 686 687 public void onProtectiveCurtainClick(View v) { 688 // Consume clicks 689 } 690 691 @Override 692 public void onShutterButtonClick() { 693 if (collapseCameraControls() || mSwitchingCamera) return; 694 695 boolean stop = mMediaRecorderRecording; 696 697 if (stop) { 698 onStopVideoRecording(); 699 } else { 700 startVideoRecording(); 701 } 702 mShutterButton.setEnabled(false); 703 704 // Keep the shutter button disabled when in video capture intent 705 // mode and recording is stopped. It'll be re-enabled when 706 // re-take button is clicked. 707 if (!(mIsVideoCaptureIntent && stop)) { 708 mHandler.sendEmptyMessageDelayed( 709 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT); 710 } 711 } 712 713 @Override 714 public void onShutterButtonFocus(boolean pressed) { 715 // Do nothing (everything happens in onShutterButtonClick). 716 } 717 718 private void readVideoPreferences() { 719 // The preference stores values from ListPreference and is thus string type for all values. 720 // We need to convert it to int manually. 721 String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId, 722 mActivity.getResources().getString(R.string.pref_video_quality_default)); 723 String videoQuality = 724 mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY, 725 defaultQuality); 726 int quality = Integer.valueOf(videoQuality); 727 728 // Set video quality. 729 Intent intent = mActivity.getIntent(); 730 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 731 int extraVideoQuality = 732 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 733 if (extraVideoQuality > 0) { 734 quality = CamcorderProfile.QUALITY_HIGH; 735 } else { // 0 is mms. 736 quality = CamcorderProfile.QUALITY_LOW; 737 } 738 } 739 740 // Set video duration limit. The limit is read from the preference, 741 // unless it is specified in the intent. 742 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 743 int seconds = 744 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0); 745 mMaxVideoDurationInMs = 1000 * seconds; 746 } else { 747 mMaxVideoDurationInMs = CameraSettings.DEFAULT_VIDEO_DURATION; 748 } 749 750 // Set effect 751 mEffectType = CameraSettings.readEffectType(mPreferences); 752 if (mEffectType != EffectsRecorder.EFFECT_NONE) { 753 mEffectParameter = CameraSettings.readEffectParameter(mPreferences); 754 // Set quality to be no higher than 480p. 755 CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality); 756 if (profile.videoFrameHeight > 480) { 757 quality = getLowVideoQuality(); 758 } 759 // On initial startup, can get here before indicator control is 760 // enabled. In that case, UI quality override handled in 761 // initializeIndicatorControl. 762// if (mIndicatorControlContainer != null) { 763// mIndicatorControlContainer.overrideSettings( 764// CameraSettings.KEY_VIDEO_QUALITY, 765// Integer.toString(getLowVideoQuality())); 766// } 767 } else { 768 mEffectParameter = null; 769// if (mIndicatorControlContainer != null) { 770// mIndicatorControlContainer.overrideSettings( 771// CameraSettings.KEY_VIDEO_QUALITY, 772// null); 773// } 774 } 775 // Read time lapse recording interval. 776 if (ApiHelper.HAS_TIME_LAPSE_RECORDING) { 777 String frameIntervalStr = mPreferences.getString( 778 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, 779 mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default)); 780 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr); 781 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0); 782 } 783 // TODO: This should be checked instead directly +1000. 784 if (mCaptureTimeLapse) quality += 1000; 785 mProfile = CamcorderProfile.get(mCameraId, quality); 786 getDesiredPreviewSize(); 787 } 788 789 private void writeDefaultEffectToPrefs() { 790 ComboPreferences.Editor editor = mPreferences.edit(); 791 editor.putString(CameraSettings.KEY_VIDEO_EFFECT, 792 mActivity.getString(R.string.pref_video_effect_default)); 793 editor.apply(); 794 } 795 796 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 797 private void getDesiredPreviewSize() { 798 mParameters = mActivity.mCameraDevice.getParameters(); 799 if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) { 800 if (mParameters.getSupportedVideoSizes() == null || effectsActive()) { 801 mDesiredPreviewWidth = mProfile.videoFrameWidth; 802 mDesiredPreviewHeight = mProfile.videoFrameHeight; 803 } else { // Driver supports separates outputs for preview and video. 804 List<Size> sizes = mParameters.getSupportedPreviewSizes(); 805 Size preferred = mParameters.getPreferredPreviewSizeForVideo(); 806 int product = preferred.width * preferred.height; 807 Iterator<Size> it = sizes.iterator(); 808 // Remove the preview sizes that are not preferred. 809 while (it.hasNext()) { 810 Size size = it.next(); 811 if (size.width * size.height > product) { 812 it.remove(); 813 } 814 } 815 Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes, 816 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 817 mDesiredPreviewWidth = optimalSize.width; 818 mDesiredPreviewHeight = optimalSize.height; 819 } 820 } else { 821 mDesiredPreviewWidth = mProfile.videoFrameWidth; 822 mDesiredPreviewHeight = mProfile.videoFrameHeight; 823 } 824 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth + 825 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight); 826 } 827 828 private void resizeForPreviewAspectRatio() { 829 mPreviewFrameLayout.setAspectRatio( 830 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 831 } 832 833 @Override 834 public void installIntentFilter() { 835 // install an intent filter to receive SD card related events. 836 IntentFilter intentFilter = 837 new IntentFilter(Intent.ACTION_MEDIA_EJECT); 838 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 839 intentFilter.addDataScheme("file"); 840 mReceiver = new MyBroadcastReceiver(); 841 mActivity.registerReceiver(mReceiver, intentFilter); 842 } 843 844 @Override 845 public void onResumeBeforeSuper() { 846 mPaused = false; 847 } 848 849 @Override 850 public void onResumeAfterSuper() { 851 if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled) 852 return; 853 854 mZoomValue = 0; 855 856 showVideoSnapshotUI(false); 857 858 // Start orientation listener as soon as possible because it takes 859 // some time to get first orientation. 860 mOrientationListener.enable(); 861 if (!mPreviewing) { 862 if (resetEffect()) { 863 mBgLearningMessageFrame.setVisibility(View.GONE); 864// mIndicatorControlContainer.reloadPreferences(); 865 } 866 CameraOpenThread cameraOpenThread = new CameraOpenThread(); 867 cameraOpenThread.start(); 868 try { 869 cameraOpenThread.join(); 870 if (mActivity.mOpenCameraFail) { 871 Util.showErrorAndFinish(mActivity, 872 R.string.cannot_connect_camera); 873 return; 874 } else if (mActivity.mCameraDisabled) { 875 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 876 return; 877 } 878 } catch (InterruptedException ex) { 879 // ignore 880 } 881 882 readVideoPreferences(); 883 resizeForPreviewAspectRatio(); 884 startPreview(); 885 } 886 887 // Initializing it here after the preview is started. 888 initializeZoom(); 889 890 keepScreenOnAwhile(); 891 892 // Initialize location service. 893 boolean recordLocation = RecordLocationPreference.get(mPreferences, 894 mContentResolver); 895 mLocationManager.recordLocation(recordLocation); 896 897 if (!mIsVideoCaptureIntent) { 898 mActivity.getLastThumbnail(); 899 } 900 901 if (mPreviewing) { 902 mOnResumeTime = SystemClock.uptimeMillis(); 903 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); 904 } 905 // Dismiss open menu if exists. 906 PopupManager.getInstance(mActivity).notifyShowPopup(null); 907 908 mVideoNamer = new VideoNamer(); 909 } 910 911 private void setDisplayOrientation() { 912 mDisplayRotation = Util.getDisplayRotation(mActivity); 913 if (ApiHelper.HAS_SURFACE_TEXTURE) { 914 // The display rotation is handled by gallery. 915 mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId); 916 } else { 917 // We need to consider display rotation ourselves. 918 mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId); 919 } 920 } 921 922 private void startPreview() { 923 Log.v(TAG, "startPreview"); 924 925 mActivity.mCameraDevice.setErrorCallback(mErrorCallback); 926 if (mPreviewing == true) { 927 stopPreview(); 928 if (effectsActive() && mEffectsRecorder != null) { 929 mEffectsRecorder.release(); 930 mEffectsRecorder = null; 931 } 932 } 933 934 setDisplayOrientation(); 935 mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); 936 setCameraParameters(); 937 938 try { 939 if (!effectsActive()) { 940 if (ApiHelper.HAS_SURFACE_TEXTURE) { 941 mActivity.mCameraDevice.setPreviewTextureAsync( 942 ((CameraScreenNail) mActivity.mCameraScreenNail).getSurfaceTexture()); 943 } else { 944 mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); 945 } 946 mActivity.mCameraDevice.startPreviewAsync(); 947 } else { 948 initializeEffectsPreview(); 949 mEffectsRecorder.startPreview(); 950 } 951 } catch (Throwable ex) { 952 closeCamera(); 953 throw new RuntimeException("startPreview failed", ex); 954 } 955 956 mPreviewing = true; 957 } 958 959 private void stopPreview() { 960 mActivity.mCameraDevice.stopPreview(); 961 mPreviewing = false; 962 } 963 964 // Closing the effects out. Will shut down the effects graph. 965 private void closeEffects() { 966 Log.v(TAG, "Closing effects"); 967 mEffectType = EffectsRecorder.EFFECT_NONE; 968 if (mEffectsRecorder == null) { 969 Log.d(TAG, "Effects are already closed. Nothing to do"); 970 return; 971 } 972 // This call can handle the case where the camera is already released 973 // after the recording has been stopped. 974 mEffectsRecorder.release(); 975 mEffectsRecorder = null; 976 } 977 978 // By default, we want to close the effects as well with the camera. 979 private void closeCamera() { 980 closeCamera(true); 981 } 982 983 // In certain cases, when the effects are active, we may want to shutdown 984 // only the camera related parts, and handle closing the effects in the 985 // effectsUpdate callback. 986 // For example, in onPause, we want to make the camera available to 987 // outside world immediately, however, want to wait till the effects 988 // callback to shut down the effects. In such a case, we just disconnect 989 // the effects from the camera by calling disconnectCamera. That way 990 // the effects can handle that when shutting down. 991 // 992 // @param closeEffectsAlso - indicates whether we want to close the 993 // effects also along with the camera. 994 private void closeCamera(boolean closeEffectsAlso) { 995 Log.v(TAG, "closeCamera"); 996 if (mActivity.mCameraDevice == null) { 997 Log.d(TAG, "already stopped."); 998 return; 999 } 1000 1001 if (mEffectsRecorder != null) { 1002 // Disconnect the camera from effects so that camera is ready to 1003 // be released to the outside world. 1004 mEffectsRecorder.disconnectCamera(); 1005 } 1006 if (closeEffectsAlso) closeEffects(); 1007 mActivity.mCameraDevice.setZoomChangeListener(null); 1008 mActivity.mCameraDevice.setErrorCallback(null); 1009 CameraHolder.instance().release(); 1010 mActivity.mCameraDevice = null; 1011 mPreviewing = false; 1012 mSnapshotInProgress = false; 1013 } 1014 1015 private void releasePreviewResources() { 1016 if (ApiHelper.HAS_SURFACE_TEXTURE) { 1017 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 1018 if (screenNail.getSurfaceTexture() != null) { 1019 screenNail.releaseSurfaceTexture(); 1020 } 1021 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 1022 mHandler.removeMessages(HIDE_SURFACE_VIEW); 1023 mPreviewSurfaceView.setVisibility(View.GONE); 1024 } 1025 } 1026 } 1027 1028 @Override 1029 public void onPauseBeforeSuper() { 1030 mPaused = true; 1031 1032 if (mMediaRecorderRecording) { 1033 // Camera will be released in onStopVideoRecording. 1034 onStopVideoRecording(); 1035 } else { 1036 closeCamera(); 1037 if (!effectsActive()) releaseMediaRecorder(); 1038 } 1039 if (effectsActive()) { 1040 // If the effects are active, make sure we tell the graph that the 1041 // surfacetexture is not valid anymore. Disconnect the graph from 1042 // the display. This should be done before releasing the surface 1043 // texture. 1044 mEffectsRecorder.disconnectDisplay(); 1045 } else { 1046 // Close the file descriptor and clear the video namer only if the 1047 // effects are not active. If effects are active, we need to wait 1048 // till we get the callback from the Effects that the graph is done 1049 // recording. That also needs a change in the stopVideoRecording() 1050 // call to not call closeCamera if the effects are active, because 1051 // that will close down the effects are well, thus making this if 1052 // condition invalid. 1053 closeVideoFileDescriptor(); 1054 clearVideoNamer(); 1055 } 1056 1057 releasePreviewResources(); 1058 1059 if (mReceiver != null) { 1060 mActivity.unregisterReceiver(mReceiver); 1061 mReceiver = null; 1062 } 1063 resetScreenOn(); 1064 1065 if (mOrientationListener != null) mOrientationListener.disable(); 1066 if (mLocationManager != null) mLocationManager.recordLocation(false); 1067 1068 mHandler.removeMessages(CHECK_DISPLAY_ROTATION); 1069 mHandler.removeMessages(SWITCH_CAMERA); 1070 mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION); 1071 mPendingSwitchCameraId = -1; 1072 mSwitchingCamera = false; 1073 // Call onPause after stopping video recording. So the camera can be 1074 // released as soon as possible. 1075 } 1076 1077 @Override 1078 public void onPauseAfterSuper() { 1079 } 1080 1081 @Override 1082 public void onUserInteraction() { 1083 if (!mMediaRecorderRecording && !mActivity.isFinishing()) { 1084 keepScreenOnAwhile(); 1085 } 1086 } 1087 1088 @Override 1089 public boolean onBackPressed() { 1090 if (mPaused) return true; 1091 if (mMediaRecorderRecording) { 1092 onStopVideoRecording(); 1093 return true; 1094 } else { 1095 return collapseCameraControls(); 1096 } 1097 } 1098 1099 @Override 1100 public boolean onKeyDown(int keyCode, KeyEvent event) { 1101 // Do not handle any key if the activity is paused. 1102 if (mPaused) { 1103 return true; 1104 } 1105 1106 switch (keyCode) { 1107 case KeyEvent.KEYCODE_CAMERA: 1108 if (event.getRepeatCount() == 0) { 1109 mShutterButton.performClick(); 1110 return true; 1111 } 1112 break; 1113 case KeyEvent.KEYCODE_DPAD_CENTER: 1114 if (event.getRepeatCount() == 0) { 1115 mShutterButton.performClick(); 1116 return true; 1117 } 1118 break; 1119 case KeyEvent.KEYCODE_MENU: 1120 if (mMediaRecorderRecording) return true; 1121 break; 1122 } 1123 return false; 1124 } 1125 1126 @Override 1127 public boolean onKeyUp(int keyCode, KeyEvent event) { 1128 switch (keyCode) { 1129 case KeyEvent.KEYCODE_CAMERA: 1130 mShutterButton.setPressed(false); 1131 return true; 1132 } 1133 return false; 1134 } 1135 1136 private boolean isVideoCaptureIntent() { 1137 String action = mActivity.getIntent().getAction(); 1138 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 1139 } 1140 1141 private void doReturnToCaller(boolean valid) { 1142 Intent resultIntent = new Intent(); 1143 int resultCode; 1144 if (valid) { 1145 resultCode = Activity.RESULT_OK; 1146 resultIntent.setData(mCurrentVideoUri); 1147 } else { 1148 resultCode = Activity.RESULT_CANCELED; 1149 } 1150 mActivity.setResultEx(resultCode, resultIntent); 1151 mActivity.finish(); 1152 } 1153 1154 private void cleanupEmptyFile() { 1155 if (mVideoFilename != null) { 1156 File f = new File(mVideoFilename); 1157 if (f.length() == 0 && f.delete()) { 1158 Log.v(TAG, "Empty video file deleted: " + mVideoFilename); 1159 mVideoFilename = null; 1160 } 1161 } 1162 } 1163 1164 private void setupMediaRecorderPreviewDisplay() { 1165 // Nothing to do here if using SurfaceTexture. 1166 if (!ApiHelper.HAS_SURFACE_TEXTURE) { 1167 mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface()); 1168 } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 1169 // We stop the preview here before unlocking the device because we 1170 // need to change the SurfaceTexture to SurfaceView for preview. 1171 stopPreview(); 1172 mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); 1173 // The orientation for SurfaceTexture is different from that for 1174 // SurfaceView. For SurfaceTexture we don't need to consider the 1175 // display rotation. Just consider the sensor's orientation and we 1176 // will set the orientation correctly when showing the texture. 1177 // Gallery will handle the orientation for the preview. For 1178 // SurfaceView we will have to take everything into account so the 1179 // display rotation is considered. 1180 mActivity.mCameraDevice.setDisplayOrientation( 1181 Util.getDisplayOrientation(mDisplayRotation, mCameraId)); 1182 mActivity.mCameraDevice.startPreviewAsync(); 1183 mPreviewing = true; 1184 mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface()); 1185 } 1186 } 1187 1188 // Prepares media recorder. 1189 private void initializeRecorder() { 1190 Log.v(TAG, "initializeRecorder"); 1191 // If the mCameraDevice is null, then this activity is going to finish 1192 if (mActivity.mCameraDevice == null) return; 1193 1194 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING && ApiHelper.HAS_SURFACE_TEXTURE) { 1195 // Set the SurfaceView to visible so the surface gets created. 1196 // surfaceCreated() is called immediately when the visibility is 1197 // changed to visible. Thus, mSurfaceViewReady should become true 1198 // right after calling setVisibility(). 1199 mPreviewSurfaceView.setVisibility(View.VISIBLE); 1200 if (!mSurfaceViewReady) return; 1201 } 1202 1203 Intent intent = mActivity.getIntent(); 1204 Bundle myExtras = intent.getExtras(); 1205 1206 long requestedSizeLimit = 0; 1207 closeVideoFileDescriptor(); 1208 if (mIsVideoCaptureIntent && myExtras != null) { 1209 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 1210 if (saveUri != null) { 1211 try { 1212 mVideoFileDescriptor = 1213 mContentResolver.openFileDescriptor(saveUri, "rw"); 1214 mCurrentVideoUri = saveUri; 1215 } catch (java.io.FileNotFoundException ex) { 1216 // invalid uri 1217 Log.e(TAG, ex.toString()); 1218 } 1219 } 1220 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 1221 } 1222 mMediaRecorder = new MediaRecorder(); 1223 1224 setupMediaRecorderPreviewDisplay(); 1225 // Unlock the camera object before passing it to media recorder. 1226 mActivity.mCameraDevice.unlock(); 1227 mMediaRecorder.setCamera(mActivity.mCameraDevice.getCamera()); 1228 if (!mCaptureTimeLapse) { 1229 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1230 } 1231 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1232 mMediaRecorder.setProfile(mProfile); 1233 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs); 1234 if (mCaptureTimeLapse) { 1235 double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs; 1236 setCaptureRate(mMediaRecorder, fps); 1237 } 1238 1239 setRecordLocation(); 1240 1241 // Set output file. 1242 // Try Uri in the intent first. If it doesn't exist, use our own 1243 // instead. 1244 if (mVideoFileDescriptor != null) { 1245 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); 1246 } else { 1247 generateVideoFilename(mProfile.fileFormat); 1248 mMediaRecorder.setOutputFile(mVideoFilename); 1249 } 1250 1251 // Set maximum file size. 1252 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD; 1253 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 1254 maxFileSize = requestedSizeLimit; 1255 } 1256 1257 try { 1258 mMediaRecorder.setMaxFileSize(maxFileSize); 1259 } catch (RuntimeException exception) { 1260 // We are going to ignore failure of setMaxFileSize here, as 1261 // a) The composer selected may simply not support it, or 1262 // b) The underlying media framework may not handle 64-bit range 1263 // on the size restriction. 1264 } 1265 1266 // See android.hardware.Camera.Parameters.setRotation for 1267 // documentation. 1268 // Note that mOrientation here is the device orientation, which is the opposite of 1269 // what activity.getWindowManager().getDefaultDisplay().getRotation() would return, 1270 // which is the orientation the graphics need to rotate in order to render correctly. 1271 int rotation = 0; 1272 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 1273 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; 1274 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { 1275 rotation = (info.orientation - mOrientation + 360) % 360; 1276 } else { // back-facing camera 1277 rotation = (info.orientation + mOrientation) % 360; 1278 } 1279 } 1280 mMediaRecorder.setOrientationHint(rotation); 1281 mOrientationCompensationAtRecordStart = mOrientationCompensation; 1282 1283 try { 1284 mMediaRecorder.prepare(); 1285 } catch (IOException e) { 1286 Log.e(TAG, "prepare failed for " + mVideoFilename, e); 1287 releaseMediaRecorder(); 1288 throw new RuntimeException(e); 1289 } 1290 1291 mMediaRecorder.setOnErrorListener(this); 1292 mMediaRecorder.setOnInfoListener(this); 1293 } 1294 1295 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 1296 private static void setCaptureRate(MediaRecorder recorder, double fps) { 1297 recorder.setCaptureRate(fps); 1298 } 1299 1300 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) 1301 private void setRecordLocation() { 1302 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 1303 Location loc = mLocationManager.getCurrentLocation(); 1304 if (loc != null) { 1305 mMediaRecorder.setLocation((float) loc.getLatitude(), 1306 (float) loc.getLongitude()); 1307 } 1308 } 1309 } 1310 1311 private void initializeEffectsPreview() { 1312 Log.v(TAG, "initializeEffectsPreview"); 1313 // If the mCameraDevice is null, then this activity is going to finish 1314 if (mActivity.mCameraDevice == null) return; 1315 1316 boolean inLandscape = (mActivity.getResources().getConfiguration().orientation 1317 == Configuration.ORIENTATION_LANDSCAPE); 1318 1319 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; 1320 1321 mEffectsDisplayResult = false; 1322 mEffectsRecorder = new EffectsRecorder(mActivity); 1323 1324 // TODO: Confirm none of the following need to go to initializeEffectsRecording() 1325 // and none of these change even when the preview is not refreshed. 1326 mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation); 1327 mEffectsRecorder.setCamera(mActivity.mCameraDevice); 1328 mEffectsRecorder.setCameraFacing(info.facing); 1329 mEffectsRecorder.setProfile(mProfile); 1330 mEffectsRecorder.setEffectsListener(this); 1331 mEffectsRecorder.setOnInfoListener(this); 1332 mEffectsRecorder.setOnErrorListener(this); 1333 1334 // The input of effects recorder is affected by 1335 // android.hardware.Camera.setDisplayOrientation. Its value only 1336 // compensates the camera orientation (no Display.getRotation). So the 1337 // orientation hint here should only consider sensor orientation. 1338 int orientation = 0; 1339 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 1340 orientation = mOrientation; 1341 } 1342 mEffectsRecorder.setOrientationHint(orientation); 1343 1344 mOrientationCompensationAtRecordStart = mOrientationCompensation; 1345 1346 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 1347 mEffectsRecorder.setPreviewSurfaceTexture(screenNail.getSurfaceTexture(), 1348 screenNail.getWidth(), screenNail.getHeight()); 1349 1350 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER && 1351 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) { 1352 mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery); 1353 } else { 1354 mEffectsRecorder.setEffect(mEffectType, mEffectParameter); 1355 } 1356 } 1357 1358 private void initializeEffectsRecording() { 1359 Log.v(TAG, "initializeEffectsRecording"); 1360 1361 Intent intent = mActivity.getIntent(); 1362 Bundle myExtras = intent.getExtras(); 1363 1364 long requestedSizeLimit = 0; 1365 closeVideoFileDescriptor(); 1366 if (mIsVideoCaptureIntent && myExtras != null) { 1367 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 1368 if (saveUri != null) { 1369 try { 1370 mVideoFileDescriptor = 1371 mContentResolver.openFileDescriptor(saveUri, "rw"); 1372 mCurrentVideoUri = saveUri; 1373 } catch (java.io.FileNotFoundException ex) { 1374 // invalid uri 1375 Log.e(TAG, ex.toString()); 1376 } 1377 } 1378 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 1379 } 1380 1381 mEffectsRecorder.setProfile(mProfile); 1382 // important to set the capture rate to zero if not timelapsed, since the 1383 // effectsrecorder object does not get created again for each recording 1384 // session 1385 if (mCaptureTimeLapse) { 1386 mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs)); 1387 } else { 1388 mEffectsRecorder.setCaptureRate(0); 1389 } 1390 1391 // Set output file 1392 if (mVideoFileDescriptor != null) { 1393 mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); 1394 } else { 1395 generateVideoFilename(mProfile.fileFormat); 1396 mEffectsRecorder.setOutputFile(mVideoFilename); 1397 } 1398 1399 // Set maximum file size. 1400 long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD; 1401 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 1402 maxFileSize = requestedSizeLimit; 1403 } 1404 mEffectsRecorder.setMaxFileSize(maxFileSize); 1405 mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs); 1406 } 1407 1408 1409 private void releaseMediaRecorder() { 1410 Log.v(TAG, "Releasing media recorder."); 1411 if (mMediaRecorder != null) { 1412 cleanupEmptyFile(); 1413 mMediaRecorder.reset(); 1414 mMediaRecorder.release(); 1415 mMediaRecorder = null; 1416 } 1417 mVideoFilename = null; 1418 } 1419 1420 private void releaseEffectsRecorder() { 1421 Log.v(TAG, "Releasing effects recorder."); 1422 if (mEffectsRecorder != null) { 1423 cleanupEmptyFile(); 1424 mEffectsRecorder.release(); 1425 mEffectsRecorder = null; 1426 } 1427 mEffectType = EffectsRecorder.EFFECT_NONE; 1428 mVideoFilename = null; 1429 } 1430 1431 private void generateVideoFilename(int outputFileFormat) { 1432 long dateTaken = System.currentTimeMillis(); 1433 String title = createName(dateTaken); 1434 // Used when emailing. 1435 String filename = title + convertOutputFormatToFileExt(outputFileFormat); 1436 String mime = convertOutputFormatToMimeType(outputFileFormat); 1437 String path = Storage.DIRECTORY + '/' + filename; 1438 String tmpPath = path + ".tmp"; 1439 mCurrentVideoValues = new ContentValues(7); 1440 mCurrentVideoValues.put(Video.Media.TITLE, title); 1441 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename); 1442 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken); 1443 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime); 1444 mCurrentVideoValues.put(Video.Media.DATA, path); 1445 mCurrentVideoValues.put(Video.Media.RESOLUTION, 1446 Integer.toString(mProfile.videoFrameWidth) + "x" + 1447 Integer.toString(mProfile.videoFrameHeight)); 1448 Location loc = mLocationManager.getCurrentLocation(); 1449 if (loc != null) { 1450 mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude()); 1451 mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude()); 1452 } 1453 mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues); 1454 mVideoFilename = tmpPath; 1455 Log.v(TAG, "New video filename: " + mVideoFilename); 1456 } 1457 1458 private boolean addVideoToMediaStore() { 1459 boolean fail = false; 1460 if (mVideoFileDescriptor == null) { 1461 mCurrentVideoValues.put(Video.Media.SIZE, 1462 new File(mCurrentVideoFilename).length()); 1463 long duration = SystemClock.uptimeMillis() - mRecordingStartTime; 1464 if (duration > 0) { 1465 if (mCaptureTimeLapse) { 1466 duration = getTimeLapseVideoLength(duration); 1467 } 1468 mCurrentVideoValues.put(Video.Media.DURATION, duration); 1469 } else { 1470 Log.w(TAG, "Video duration <= 0 : " + duration); 1471 } 1472 try { 1473 mCurrentVideoUri = mVideoNamer.getUri(); 1474 mActivity.addSecureAlbumItemIfNeeded(true, mCurrentVideoUri); 1475 1476 // Rename the video file to the final name. This avoids other 1477 // apps reading incomplete data. We need to do it after the 1478 // above mVideoNamer.getUri() call, so we are certain that the 1479 // previous insert to MediaProvider is completed. 1480 String finalName = mCurrentVideoValues.getAsString( 1481 Video.Media.DATA); 1482 if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) { 1483 mCurrentVideoFilename = finalName; 1484 } 1485 1486 mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues 1487 , null, null); 1488 mActivity.sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO, 1489 mCurrentVideoUri)); 1490 } catch (Exception e) { 1491 // We failed to insert into the database. This can happen if 1492 // the SD card is unmounted. 1493 Log.e(TAG, "failed to add video to media store", e); 1494 mCurrentVideoUri = null; 1495 mCurrentVideoFilename = null; 1496 fail = true; 1497 } finally { 1498 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 1499 } 1500 } 1501 mCurrentVideoValues = null; 1502 return fail; 1503 } 1504 1505 private void deleteCurrentVideo() { 1506 // Remove the video and the uri if the uri is not passed in by intent. 1507 if (mCurrentVideoFilename != null) { 1508 deleteVideoFile(mCurrentVideoFilename); 1509 mCurrentVideoFilename = null; 1510 if (mCurrentVideoUri != null) { 1511 mContentResolver.delete(mCurrentVideoUri, null, null); 1512 mCurrentVideoUri = null; 1513 } 1514 } 1515 mActivity.updateStorageSpaceAndHint(); 1516 } 1517 1518 private void deleteVideoFile(String fileName) { 1519 Log.v(TAG, "Deleting video " + fileName); 1520 File f = new File(fileName); 1521 if (!f.delete()) { 1522 Log.v(TAG, "Could not delete " + fileName); 1523 } 1524 } 1525 1526 private PreferenceGroup filterPreferenceScreenByIntent( 1527 PreferenceGroup screen) { 1528 Intent intent = mActivity.getIntent(); 1529 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 1530 CameraSettings.removePreferenceFromScreen(screen, 1531 CameraSettings.KEY_VIDEO_QUALITY); 1532 } 1533 1534 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 1535 CameraSettings.removePreferenceFromScreen(screen, 1536 CameraSettings.KEY_VIDEO_QUALITY); 1537 } 1538 return screen; 1539 } 1540 1541 // from MediaRecorder.OnErrorListener 1542 @Override 1543 public void onError(MediaRecorder mr, int what, int extra) { 1544 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra); 1545 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 1546 // We may have run out of space on the sdcard. 1547 stopVideoRecording(); 1548 mActivity.updateStorageSpaceAndHint(); 1549 } 1550 } 1551 1552 // from MediaRecorder.OnInfoListener 1553 @Override 1554 public void onInfo(MediaRecorder mr, int what, int extra) { 1555 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1556 if (mMediaRecorderRecording) onStopVideoRecording(); 1557 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1558 if (mMediaRecorderRecording) onStopVideoRecording(); 1559 1560 // Show the toast. 1561 Toast.makeText(mActivity, R.string.video_reach_size_limit, 1562 Toast.LENGTH_LONG).show(); 1563 } 1564 } 1565 1566 /* 1567 * Make sure we're not recording music playing in the background, ask the 1568 * MediaPlaybackService to pause playback. 1569 */ 1570 private void pauseAudioPlayback() { 1571 // Shamelessly copied from MediaPlaybackService.java, which 1572 // should be public, but isn't. 1573 Intent i = new Intent("com.android.music.musicservicecommand"); 1574 i.putExtra("command", "pause"); 1575 1576 mActivity.sendBroadcast(i); 1577 } 1578 1579 // For testing. 1580 public boolean isRecording() { 1581 return mMediaRecorderRecording; 1582 } 1583 1584 private void startVideoRecording() { 1585 Log.v(TAG, "startVideoRecording"); 1586 mActivity.setSwipingEnabled(false); 1587 mActivity.hideSwitcher(R.drawable.ic_switch_video_active); 1588 1589 mActivity.updateStorageSpaceAndHint(); 1590 if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) { 1591 Log.v(TAG, "Storage issue, ignore the start request"); 1592 return; 1593 } 1594 1595 mCurrentVideoUri = null; 1596 if (effectsActive()) { 1597 initializeEffectsRecording(); 1598 if (mEffectsRecorder == null) { 1599 Log.e(TAG, "Fail to initialize effect recorder"); 1600 return; 1601 } 1602 } else { 1603 initializeRecorder(); 1604 if (mMediaRecorder == null) { 1605 Log.e(TAG, "Fail to initialize media recorder"); 1606 return; 1607 } 1608 } 1609 1610 pauseAudioPlayback(); 1611 1612 if (effectsActive()) { 1613 try { 1614 mEffectsRecorder.startRecording(); 1615 } catch (RuntimeException e) { 1616 Log.e(TAG, "Could not start effects recorder. ", e); 1617 releaseEffectsRecorder(); 1618 return; 1619 } 1620 } else { 1621 try { 1622 mMediaRecorder.start(); // Recording is now started 1623 } catch (RuntimeException e) { 1624 Log.e(TAG, "Could not start media recorder. ", e); 1625 releaseMediaRecorder(); 1626 // If start fails, frameworks will not lock the camera for us. 1627 mActivity.mCameraDevice.lock(); 1628 return; 1629 } 1630 } 1631 1632 // The parameters may have been changed by MediaRecorder upon starting 1633 // recording. We need to alter the parameters if we support camcorder 1634 // zoom. To reduce latency when setting the parameters during zoom, we 1635 // update mParameters here once. 1636 if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) { 1637 mParameters = mActivity.mCameraDevice.getParameters(); 1638 } 1639 1640 enableCameraControls(false); 1641 1642 mMediaRecorderRecording = true; 1643 mRecordingStartTime = SystemClock.uptimeMillis(); 1644 showRecordingUI(true); 1645 1646 updateRecordingTime(); 1647 keepScreenOn(); 1648 } 1649 1650 private void showRecordingUI(boolean recording) { 1651 if (recording) { 1652 if (mActivity.mThumbnailView != null) mActivity.mThumbnailView.setEnabled(false); 1653// mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); 1654 mRecordingTimeView.setText(""); 1655 mRecordingTimeView.setVisibility(View.VISIBLE); 1656 if (mReviewControl != null) mReviewControl.setVisibility(View.GONE); 1657 if (mCaptureTimeLapse) { 1658// mIndicatorControlContainer.startTimeLapseAnimation( 1659// mTimeBetweenTimeLapseFrameCaptureMs, 1660// mRecordingStartTime); 1661 } 1662 // The camera is not allowed to be accessed in older api levels during 1663 // recording. It is therefore necessary to hide the zoom UI on older 1664 // platforms. 1665 // See the documentation of android.media.MediaRecorder.start() for 1666 // further explanation. 1667 if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING 1668 && mParameters.isZoomSupported()) { 1669// mZoomControl.setVisibility(View.GONE); 1670 } 1671 } else { 1672 if (mActivity.mThumbnailView != null) mActivity.mThumbnailView.setEnabled(true); 1673// mShutterButton.setImageResource(R.drawable.btn_shutter_video); 1674 mRecordingTimeView.setVisibility(View.GONE); 1675 if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE); 1676 if (mCaptureTimeLapse) { 1677// mIndicatorControlContainer.stopTimeLapseAnimation(); 1678 } 1679 if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING 1680 && mParameters.isZoomSupported()) { 1681// mZoomControl.setVisibility(View.VISIBLE); 1682 } 1683 } 1684 } 1685 1686 private void getThumbnail() { 1687 if (mCurrentVideoUri != null) { 1688 Bitmap videoFrame = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename, 1689 Math.max(mActivity.mThumbnailViewWidth, MIN_THUMB_SIZE)); 1690 if (videoFrame != null) { 1691 mActivity.mThumbnail = Thumbnail.createThumbnail(mCurrentVideoUri, videoFrame, 0); 1692 if (mActivity.mThumbnailView != null) { 1693 mActivity.mThumbnailView.setBitmap(mActivity.mThumbnail.getBitmap()); 1694 } 1695 } 1696 } 1697 } 1698 1699 private void showAlert() { 1700 Bitmap bitmap = null; 1701 if (mVideoFileDescriptor != null) { 1702 bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(), 1703 mPreviewFrameLayout.getWidth()); 1704 } else if (mCurrentVideoFilename != null) { 1705 bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename, 1706 mPreviewFrameLayout.getWidth()); 1707 } 1708 if (bitmap != null) { 1709 // MetadataRetriever already rotates the thumbnail. We should rotate 1710 // it to match the UI orientation (and mirror if it is front-facing camera). 1711 CameraInfo[] info = CameraHolder.instance().getCameraInfo(); 1712 boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT); 1713 bitmap = Util.rotateAndMirror(bitmap, -mOrientationCompensationAtRecordStart, 1714 mirror); 1715 mReviewImage.setImageBitmap(bitmap); 1716 mReviewImage.setVisibility(View.VISIBLE); 1717 } 1718 1719 Util.fadeOut(mShutterButton); 1720 1721 Util.fadeIn((View) mReviewDoneButton); 1722 Util.fadeIn(mReviewPlayButton); 1723 enableCameraControls(false); 1724 1725 showTimeLapseUI(false); 1726 } 1727 1728 private void hideAlert() { 1729 mReviewImage.setVisibility(View.GONE); 1730 mShutterButton.setEnabled(true); 1731 enableCameraControls(true); 1732 1733 Util.fadeOut((View) mReviewDoneButton); 1734 Util.fadeOut(mReviewPlayButton); 1735 1736 Util.fadeIn(mShutterButton); 1737 1738 if (mCaptureTimeLapse) { 1739 showTimeLapseUI(true); 1740 } 1741 } 1742 1743 private boolean stopVideoRecording() { 1744 Log.v(TAG, "stopVideoRecording"); 1745 mActivity.setSwipingEnabled(true); 1746 mActivity.showSwitcher(); 1747 1748 boolean fail = false; 1749 if (mMediaRecorderRecording) { 1750 boolean shouldAddToMediaStoreNow = false; 1751 1752 try { 1753 if (effectsActive()) { 1754 // This is asynchronous, so we can't add to media store now because thumbnail 1755 // may not be ready. In such case addVideoToMediaStore is called later 1756 // through a callback from the MediaEncoderFilter to EffectsRecorder, 1757 // and then to the VideoCamera. 1758 mEffectsRecorder.stopRecording(); 1759 } else { 1760 mMediaRecorder.setOnErrorListener(null); 1761 mMediaRecorder.setOnInfoListener(null); 1762 mMediaRecorder.stop(); 1763 shouldAddToMediaStoreNow = true; 1764 } 1765 mCurrentVideoFilename = mVideoFilename; 1766 Log.v(TAG, "stopVideoRecording: Setting current video filename: " 1767 + mCurrentVideoFilename); 1768 } catch (RuntimeException e) { 1769 Log.e(TAG, "stop fail", e); 1770 if (mVideoFilename != null) deleteVideoFile(mVideoFilename); 1771 fail = true; 1772 } 1773 mMediaRecorderRecording = false; 1774 1775 // If the activity is paused, this means activity is interrupted 1776 // during recording. Release the camera as soon as possible because 1777 // face unlock or other applications may need to use the camera. 1778 // However, if the effects are active, then we can only release the 1779 // camera and cannot release the effects recorder since that will 1780 // stop the graph. It is possible to separate out the Camera release 1781 // part and the effects release part. However, the effects recorder 1782 // does hold on to the camera, hence, it needs to be "disconnected" 1783 // from the camera in the closeCamera call. 1784 if (mPaused) { 1785 // Closing only the camera part if effects active. Effects will 1786 // be closed in the callback from effects. 1787 boolean closeEffects = !effectsActive(); 1788 closeCamera(closeEffects); 1789 } 1790 1791 showRecordingUI(false); 1792 if (!mIsVideoCaptureIntent) { 1793 enableCameraControls(true); 1794 } 1795 // The orientation was fixed during video recording. Now make it 1796 // reflect the device orientation as video recording is stopped. 1797 setOrientationIndicator(mOrientationCompensation, true); 1798 keepScreenOnAwhile(); 1799 if (shouldAddToMediaStoreNow) { 1800 if (addVideoToMediaStore()) fail = true; 1801 } 1802 } 1803 // always release media recorder if no effects running 1804 if (!effectsActive()) { 1805 releaseMediaRecorder(); 1806 if (!mPaused) { 1807 mActivity.mCameraDevice.lock(); 1808 if (ApiHelper.HAS_SURFACE_TEXTURE && 1809 !ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { 1810 stopPreview(); 1811 // Switch back to use SurfaceTexture for preview. 1812 ((CameraScreenNail) mActivity.mCameraScreenNail).setOneTimeOnFrameDrawnListener( 1813 mFrameDrawnListener); 1814 startPreview(); 1815 } 1816 } 1817 } 1818 // Update the parameters here because the parameters might have been altered 1819 // by MediaRecorder. 1820 if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters(); 1821 return fail; 1822 } 1823 1824 private void resetScreenOn() { 1825 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1826 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1827 } 1828 1829 private void keepScreenOnAwhile() { 1830 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1831 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1832 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1833 } 1834 1835 private void keepScreenOn() { 1836 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1837 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1838 } 1839 1840 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) { 1841 long seconds = milliSeconds / 1000; // round down to compute seconds 1842 long minutes = seconds / 60; 1843 long hours = minutes / 60; 1844 long remainderMinutes = minutes - (hours * 60); 1845 long remainderSeconds = seconds - (minutes * 60); 1846 1847 StringBuilder timeStringBuilder = new StringBuilder(); 1848 1849 // Hours 1850 if (hours > 0) { 1851 if (hours < 10) { 1852 timeStringBuilder.append('0'); 1853 } 1854 timeStringBuilder.append(hours); 1855 1856 timeStringBuilder.append(':'); 1857 } 1858 1859 // Minutes 1860 if (remainderMinutes < 10) { 1861 timeStringBuilder.append('0'); 1862 } 1863 timeStringBuilder.append(remainderMinutes); 1864 timeStringBuilder.append(':'); 1865 1866 // Seconds 1867 if (remainderSeconds < 10) { 1868 timeStringBuilder.append('0'); 1869 } 1870 timeStringBuilder.append(remainderSeconds); 1871 1872 // Centi seconds 1873 if (displayCentiSeconds) { 1874 timeStringBuilder.append('.'); 1875 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10; 1876 if (remainderCentiSeconds < 10) { 1877 timeStringBuilder.append('0'); 1878 } 1879 timeStringBuilder.append(remainderCentiSeconds); 1880 } 1881 1882 return timeStringBuilder.toString(); 1883 } 1884 1885 private long getTimeLapseVideoLength(long deltaMs) { 1886 // For better approximation calculate fractional number of frames captured. 1887 // This will update the video time at a higher resolution. 1888 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs; 1889 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000); 1890 } 1891 1892 private void updateRecordingTime() { 1893 if (!mMediaRecorderRecording) { 1894 return; 1895 } 1896 long now = SystemClock.uptimeMillis(); 1897 long delta = now - mRecordingStartTime; 1898 1899 // Starting a minute before reaching the max duration 1900 // limit, we'll countdown the remaining time instead. 1901 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0 1902 && delta >= mMaxVideoDurationInMs - 60000); 1903 1904 long deltaAdjusted = delta; 1905 if (countdownRemainingTime) { 1906 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999; 1907 } 1908 String text; 1909 1910 long targetNextUpdateDelay; 1911 if (!mCaptureTimeLapse) { 1912 text = millisecondToTimeString(deltaAdjusted, false); 1913 targetNextUpdateDelay = 1000; 1914 } else { 1915 // The length of time lapse video is different from the length 1916 // of the actual wall clock time elapsed. Display the video length 1917 // only in format hh:mm:ss.dd, where dd are the centi seconds. 1918 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true); 1919 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs; 1920 } 1921 1922 mRecordingTimeView.setText(text); 1923 1924 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1925 // Avoid setting the color on every update, do it only 1926 // when it needs changing. 1927 mRecordingTimeCountsDown = countdownRemainingTime; 1928 1929 int color = mActivity.getResources().getColor(countdownRemainingTime 1930 ? R.color.recording_time_remaining_text 1931 : R.color.recording_time_elapsed_text); 1932 1933 mRecordingTimeView.setTextColor(color); 1934 } 1935 1936 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); 1937 mHandler.sendEmptyMessageDelayed( 1938 UPDATE_RECORD_TIME, actualNextUpdateDelay); 1939 } 1940 1941 private static boolean isSupported(String value, List<String> supported) { 1942 return supported == null ? false : supported.indexOf(value) >= 0; 1943 } 1944 1945 @SuppressWarnings("deprecation") 1946 private void setCameraParameters() { 1947 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight); 1948 mParameters.setPreviewFrameRate(mProfile.videoFrameRate); 1949 1950 // Set flash mode. 1951 String flashMode; 1952 if (mActivity.mShowCameraAppView) { 1953 flashMode = mPreferences.getString( 1954 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 1955 mActivity.getString(R.string.pref_camera_video_flashmode_default)); 1956 } else { 1957 flashMode = Parameters.FLASH_MODE_OFF; 1958 } 1959 List<String> supportedFlash = mParameters.getSupportedFlashModes(); 1960 if (isSupported(flashMode, supportedFlash)) { 1961 mParameters.setFlashMode(flashMode); 1962 } else { 1963 flashMode = mParameters.getFlashMode(); 1964 if (flashMode == null) { 1965 flashMode = mActivity.getString( 1966 R.string.pref_camera_flashmode_no_flash); 1967 } 1968 } 1969 1970 // Set white balance parameter. 1971 String whiteBalance = mPreferences.getString( 1972 CameraSettings.KEY_WHITE_BALANCE, 1973 mActivity.getString(R.string.pref_camera_whitebalance_default)); 1974 if (isSupported(whiteBalance, 1975 mParameters.getSupportedWhiteBalance())) { 1976 mParameters.setWhiteBalance(whiteBalance); 1977 } else { 1978 whiteBalance = mParameters.getWhiteBalance(); 1979 if (whiteBalance == null) { 1980 whiteBalance = Parameters.WHITE_BALANCE_AUTO; 1981 } 1982 } 1983 1984 // Set zoom. 1985 if (mParameters.isZoomSupported()) { 1986 mParameters.setZoom(mZoomValue); 1987 } 1988 1989 // Set continuous autofocus. 1990 List<String> supportedFocus = mParameters.getSupportedFocusModes(); 1991 if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) { 1992 mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 1993 } 1994 1995 mParameters.set(Util.RECORDING_HINT, Util.TRUE); 1996 1997 // Enable video stabilization. Convenience methods not available in API 1998 // level <= 14 1999 String vstabSupported = mParameters.get("video-stabilization-supported"); 2000 if ("true".equals(vstabSupported)) { 2001 mParameters.set("video-stabilization", "true"); 2002 } 2003 2004 // Set picture size. 2005 // The logic here is different from the logic in still-mode camera. 2006 // There we determine the preview size based on the picture size, but 2007 // here we determine the picture size based on the preview size. 2008 List<Size> supported = mParameters.getSupportedPictureSizes(); 2009 Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported, 2010 (double) mDesiredPreviewWidth / mDesiredPreviewHeight); 2011 Size original = mParameters.getPictureSize(); 2012 if (!original.equals(optimalSize)) { 2013 mParameters.setPictureSize(optimalSize.width, optimalSize.height); 2014 } 2015 Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" + 2016 optimalSize.height); 2017 2018 // Set JPEG quality. 2019 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId, 2020 CameraProfile.QUALITY_HIGH); 2021 mParameters.setJpegQuality(jpegQuality); 2022 2023 mActivity.mCameraDevice.setParameters(mParameters); 2024 // Keep preview size up to date. 2025 mParameters = mActivity.mCameraDevice.getParameters(); 2026 2027 updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight); 2028 } 2029 2030 private void updateCameraScreenNailSize(int width, int height) { 2031 if (!ApiHelper.HAS_SURFACE_TEXTURE) return; 2032 2033 if (mCameraDisplayOrientation % 180 != 0) { 2034 int tmp = width; 2035 width = height; 2036 height = tmp; 2037 } 2038 2039 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 2040 int oldWidth = screenNail.getWidth(); 2041 int oldHeight = screenNail.getHeight(); 2042 2043 if (oldWidth != width || oldHeight != height) { 2044 screenNail.setSize(width, height); 2045 mActivity.notifyScreenNailChanged(); 2046 } 2047 2048 if (screenNail.getSurfaceTexture() == null) { 2049 screenNail.acquireSurfaceTexture(); 2050 } 2051 } 2052 2053 @Override 2054 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2055 switch (requestCode) { 2056 case REQUEST_EFFECT_BACKDROPPER: 2057 if (resultCode == Activity.RESULT_OK) { 2058 // onActivityResult() runs before onResume(), so this parameter will be 2059 // seen by startPreview from onResume() 2060 mEffectUriFromGallery = data.getData().toString(); 2061 Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery); 2062 mResetEffect = false; 2063 } else { 2064 mEffectUriFromGallery = null; 2065 Log.w(TAG, "No URI from gallery"); 2066 mResetEffect = true; 2067 } 2068 break; 2069 } 2070 } 2071 2072 @Override 2073 public void onEffectsUpdate(int effectId, int effectMsg) { 2074 Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg); 2075 if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) { 2076 // Effects have shut down. Hide learning message if any, 2077 // and restart regular preview. 2078 mBgLearningMessageFrame.setVisibility(View.GONE); 2079 checkQualityAndStartPreview(); 2080 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) { 2081 // This follows the codepath from onStopVideoRecording. 2082 if (mEffectsDisplayResult && !addVideoToMediaStore()) { 2083 if (mIsVideoCaptureIntent) { 2084 if (mQuickCapture) { 2085 doReturnToCaller(true); 2086 } else { 2087 showAlert(); 2088 } 2089 } else { 2090 getThumbnail(); 2091 } 2092 } 2093 mEffectsDisplayResult = false; 2094 // In onPause, these were not called if the effects were active. We 2095 // had to wait till the effects recording is complete to do this. 2096 if (mPaused) { 2097 closeVideoFileDescriptor(); 2098 clearVideoNamer(); 2099 } 2100 } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) { 2101 // Enable the shutter button once the preview is complete. 2102 mShutterButton.setEnabled(true); 2103 } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) { 2104 switch (effectMsg) { 2105 case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING: 2106 mBgLearningMessageFrame.setVisibility(View.VISIBLE); 2107 break; 2108 case EffectsRecorder.EFFECT_MSG_DONE_LEARNING: 2109 case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT: 2110 mBgLearningMessageFrame.setVisibility(View.GONE); 2111 break; 2112 } 2113 } 2114 // In onPause, this was not called if the effects were active. We had to 2115 // wait till the effects completed to do this. 2116 if (mPaused) { 2117 Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused"); 2118 closeEffects(); 2119 } 2120 } 2121 2122 public void onCancelBgTraining(View v) { 2123 // Remove training message 2124 mBgLearningMessageFrame.setVisibility(View.GONE); 2125 // Write default effect out to shared prefs 2126 writeDefaultEffectToPrefs(); 2127 // Tell the indicator controller to redraw based on new shared pref values 2128// mIndicatorControlContainer.reloadPreferences(); 2129 // Tell VideoCamer to re-init based on new shared pref values. 2130 onSharedPreferenceChanged(); 2131 } 2132 2133 @Override 2134 public synchronized void onEffectsError(Exception exception, String fileName) { 2135 // TODO: Eventually we may want to show the user an error dialog, and then restart the 2136 // camera and encoder gracefully. For now, we just delete the file and bail out. 2137 if (fileName != null && new File(fileName).exists()) { 2138 deleteVideoFile(fileName); 2139 } 2140 try { 2141 if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException") 2142 .isInstance(exception)) { 2143 Log.w(TAG, "Problem recoding video file. Removing incomplete file."); 2144 return; 2145 } 2146 } catch (ClassNotFoundException ex) { 2147 Log.w(TAG, ex); 2148 } 2149 throw new RuntimeException("Error during recording!", exception); 2150 } 2151 2152 private void initializeControlByIntent() { 2153 if (mIsVideoCaptureIntent) { 2154 mActivity.hideSwitcher(); 2155 // Cannot use RotateImageView for "done" and "cancel" button because 2156 // the tablet layout uses RotateLayout, which cannot be cast to 2157 // RotateImageView. 2158 mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done); 2159 mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel); 2160 mReviewPlayButton = (RotateImageView) mRootView.findViewById(R.id.btn_play); 2161 2162 ((View) mReviewCancelButton).setVisibility(View.VISIBLE); 2163 2164 ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() { 2165 @Override 2166 public void onClick(View v) { 2167 onReviewDoneClicked(v); 2168 } 2169 }); 2170 ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() { 2171 @Override 2172 public void onClick(View v) { 2173 onReviewCancelClicked(v); 2174 } 2175 }); 2176 2177 ((View) mReviewPlayButton).setOnClickListener(new OnClickListener() { 2178 @Override 2179 public void onClick(View v) { 2180 onReviewPlayClicked(v); 2181 } 2182 }); 2183 2184 2185 // Not grayed out upon disabled, to make the follow-up fade-out 2186 // effect look smooth. Note that the review done button in tablet 2187 // layout is not a TwoStateImageView. 2188 if (mReviewDoneButton instanceof TwoStateImageView) { 2189 ((TwoStateImageView) mReviewDoneButton).enableFilter(false); 2190 } 2191 } else { 2192 mActivity.mThumbnailView = (RotateImageView) mRootView.findViewById(R.id.thumbnail); 2193 if (mActivity.mThumbnailView != null) { 2194 mActivity.mThumbnailView.enableFilter(false); 2195 mActivity.mThumbnailView.setVisibility(View.VISIBLE); 2196 mActivity.mThumbnailViewWidth = mActivity.mThumbnailView.getLayoutParams().width; 2197 } 2198 } 2199 } 2200 2201 private void initializeMiscControls() { 2202 mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); 2203 mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); 2204 mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); 2205 2206 mShutterButton = mActivity.getShutterButton(); 2207 mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); 2208 mShutterButton.setOnShutterButtonListener(this); 2209 mShutterButton.requestFocus(); 2210 2211 // Disable the shutter button if effects are ON since it might take 2212 // a little more time for the effects preview to be ready. We do not 2213 // want to allow recording before that happens. The shutter button 2214 // will be enabled when we get the message from effectsrecorder that 2215 // the preview is running. This becomes critical when the camera is 2216 // swapped. 2217 if (effectsActive()) { 2218 mShutterButton.setEnabled(false); 2219 } 2220 2221 mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); 2222 mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); 2223 mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); 2224 // The R.id.labels can only be found in phone layout. 2225 // That is, mLabelsLinearLayout should be null in tablet layout. 2226 mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); 2227 2228 mBgLearningMessageRotater = (RotateLayout) mRootView.findViewById(R.id.bg_replace_message); 2229 mBgLearningMessageFrame = mRootView.findViewById(R.id.bg_replace_message_frame); 2230 } 2231 2232 @Override 2233 public void onConfigurationChanged(Configuration newConfig) { 2234 setDisplayOrientation(); 2235 2236 // Change layout in response to configuration change 2237 LayoutInflater inflater = mActivity.getLayoutInflater(); 2238 ((ViewGroup) mRootView).removeAllViews(); 2239 inflater.inflate(R.layout.video_module, (ViewGroup) mRootView); 2240 2241 // from onCreate() 2242 initializeControlByIntent(); 2243 initializeOverlay(); 2244 initializeSurfaceView(); 2245 initializeMiscControls(); 2246 showTimeLapseUI(mCaptureTimeLapse); 2247 initializeVideoSnapshot(); 2248 resizeForPreviewAspectRatio(); 2249 initializeVideoControl(); 2250 2251 // from onResume() 2252 showVideoSnapshotUI(false); 2253 initializeZoom(); 2254 if (!mIsVideoCaptureIntent) { 2255 mActivity.updateThumbnailView(); 2256 } 2257 } 2258 2259 @Override 2260 public void onOverriddenPreferencesClicked() { 2261 } 2262 2263 @Override 2264 public void onRestorePreferencesClicked() { 2265 Runnable runnable = new Runnable() { 2266 @Override 2267 public void run() { 2268 restorePreferences(); 2269 } 2270 }; 2271 mRotateDialog.showAlertDialog( 2272 null, 2273 mActivity.getString(R.string.confirm_restore_message), 2274 mActivity.getString(android.R.string.ok), runnable, 2275 mActivity.getString(android.R.string.cancel), null); 2276 } 2277 2278 private void restorePreferences() { 2279 // Reset the zoom. Zoom value is not stored in preference. 2280 if (mParameters.isZoomSupported()) { 2281 mZoomValue = 0; 2282 setCameraParameters(); 2283// mZoomControl.setZoomIndex(0); 2284 } 2285 2286// if (mIndicatorControlContainer != null) { 2287// mIndicatorControlContainer.dismissSettingPopup(); 2288 CameraSettings.restorePreferences(mActivity, mPreferences, 2289 mParameters); 2290// mIndicatorControlContainer.reloadPreferences(); 2291 onSharedPreferenceChanged(); 2292// } 2293 } 2294 2295 private boolean effectsActive() { 2296 return (mEffectType != EffectsRecorder.EFFECT_NONE); 2297 } 2298 2299 @Override 2300 public void onSharedPreferenceChanged() { 2301 // ignore the events after "onPause()" or preview has not started yet 2302 if (mPaused) return; 2303 synchronized (mPreferences) { 2304 // If mCameraDevice is not ready then we can set the parameter in 2305 // startPreview(). 2306 if (mActivity.mCameraDevice == null) return; 2307 2308 boolean recordLocation = RecordLocationPreference.get( 2309 mPreferences, mContentResolver); 2310 mLocationManager.recordLocation(recordLocation); 2311 2312 // Check if the current effects selection has changed 2313 if (updateEffectSelection()) return; 2314 2315 readVideoPreferences(); 2316 showTimeLapseUI(mCaptureTimeLapse); 2317 // We need to restart the preview if preview size is changed. 2318 Size size = mParameters.getPreviewSize(); 2319 if (size.width != mDesiredPreviewWidth 2320 || size.height != mDesiredPreviewHeight) { 2321 if (!effectsActive()) { 2322 stopPreview(); 2323 } else { 2324 mEffectsRecorder.release(); 2325 mEffectsRecorder = null; 2326 } 2327 resizeForPreviewAspectRatio(); 2328 startPreview(); // Parameters will be set in startPreview(). 2329 } else { 2330 setCameraParameters(); 2331 } 2332 } 2333 } 2334 2335 private void switchCamera() { 2336 if (mPaused) return; 2337 2338 Log.d(TAG, "Start to switch camera."); 2339 mCameraId = mPendingSwitchCameraId; 2340 mPendingSwitchCameraId = -1; 2341 mVideoControl.setCameraId(mCameraId); 2342 2343 closeCamera(); 2344 2345 // Restart the camera and initialize the UI. From onCreate. 2346 mPreferences.setLocalId(mActivity, mCameraId); 2347 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); 2348 CameraOpenThread cameraOpenThread = new CameraOpenThread(); 2349 cameraOpenThread.start(); 2350 try { 2351 cameraOpenThread.join(); 2352 } catch (InterruptedException ex) { 2353 // ignore 2354 } 2355 readVideoPreferences(); 2356 startPreview(); 2357 initializeVideoSnapshot(); 2358 resizeForPreviewAspectRatio(); 2359 initializeVideoControl(); 2360 2361 // From onResume 2362 initializeZoom(); 2363 setOrientationIndicator(mOrientationCompensation, false); 2364 2365 if (ApiHelper.HAS_SURFACE_TEXTURE) { 2366 // Start switch camera animation. Post a message because 2367 // onFrameAvailable from the old camera may already exist. 2368 mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION); 2369 } 2370 } 2371 2372 // Preview texture has been copied. Now camera can be released and the 2373 // animation can be started. 2374 @Override 2375 public void onPreviewTextureCopied() { 2376 mHandler.sendEmptyMessage(SWITCH_CAMERA); 2377 } 2378 2379 private boolean updateEffectSelection() { 2380 int previousEffectType = mEffectType; 2381 Object previousEffectParameter = mEffectParameter; 2382 mEffectType = CameraSettings.readEffectType(mPreferences); 2383 mEffectParameter = CameraSettings.readEffectParameter(mPreferences); 2384 2385 if (mEffectType == previousEffectType) { 2386 if (mEffectType == EffectsRecorder.EFFECT_NONE) return false; 2387 if (mEffectParameter.equals(previousEffectParameter)) return false; 2388 } 2389 Log.v(TAG, "New effect selection: " + mPreferences.getString( 2390 CameraSettings.KEY_VIDEO_EFFECT, "none")); 2391 2392 if (mEffectType == EffectsRecorder.EFFECT_NONE) { 2393 // Stop effects and return to normal preview 2394 mEffectsRecorder.stopPreview(); 2395 mPreviewing = false; 2396 return true; 2397 } 2398 if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER && 2399 ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) { 2400 // Request video from gallery to use for background 2401 Intent i = new Intent(Intent.ACTION_PICK); 2402 i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI, 2403 "video/*"); 2404 i.putExtra(Intent.EXTRA_LOCAL_ONLY, true); 2405 mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER); 2406 return true; 2407 } 2408 if (previousEffectType == EffectsRecorder.EFFECT_NONE) { 2409 // Stop regular preview and start effects. 2410 stopPreview(); 2411 checkQualityAndStartPreview(); 2412 } else { 2413 // Switch currently running effect 2414 mEffectsRecorder.setEffect(mEffectType, mEffectParameter); 2415 } 2416 return true; 2417 } 2418 2419 // Verifies that the current preview view size is correct before starting 2420 // preview. If not, resets the surface texture and resizes the view. 2421 private void checkQualityAndStartPreview() { 2422 readVideoPreferences(); 2423 showTimeLapseUI(mCaptureTimeLapse); 2424 Size size = mParameters.getPreviewSize(); 2425 if (size.width != mDesiredPreviewWidth 2426 || size.height != mDesiredPreviewHeight) { 2427 resizeForPreviewAspectRatio(); 2428 } 2429 // Start up preview again 2430 startPreview(); 2431 } 2432 2433 private void showTimeLapseUI(boolean enable) { 2434 if (mTimeLapseLabel != null) { 2435 mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); 2436 } 2437 } 2438 2439 @Override 2440 public boolean dispatchTouchEvent(MotionEvent m) { 2441 if (mSwitchingCamera) return true; 2442 if (mPopup == null && mGestures != null && mRenderOverlay != null) { 2443 return mGestures.dispatchTouch(m); 2444 } else if (mPopup != null) { 2445 return mActivity.superDispatchTouchEvent(m); 2446 } 2447 return false; 2448 } 2449 2450 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { 2451 @Override 2452 public void onZoomValueChanged(int index) { 2453 // Not useful to change zoom value when the activity is paused. 2454 if (mPaused) return; 2455 2456 mZoomValue = index; 2457 2458 // Set zoom parameters asynchronously 2459 mParameters.setZoom(mZoomValue); 2460 mActivity.mCameraDevice.setParametersAsync(mParameters); 2461 } 2462 2463 @Override 2464 public void onZoomStart() { 2465 } 2466 @Override 2467 public void onZoomEnd() { 2468 } 2469 } 2470 2471 private void initializeZoom() { 2472 if (!mParameters.isZoomSupported()) return; 2473 mZoomMax = mParameters.getMaxZoom(); 2474 // Currently we use immediate zoom for fast zooming to get better UX and 2475 // there is no plan to take advantage of the smooth zoom. 2476 mZoomRenderer.setZoomMax(mZoomMax); 2477 mZoomRenderer.setZoomIndex(mParameters.getZoom()); 2478 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); 2479 if (!mParameters.isZoomSupported()) return; 2480 2481 mZoomMax = mParameters.getMaxZoom(); 2482 // Currently we use immediate zoom for fast zooming to get better UX and 2483 // there is no plan to take advantage of the smooth zoom. 2484 } 2485 2486 private void initializeVideoSnapshot() { 2487 if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { 2488 mActivity.setSingleTapUpListener(mPreviewFrameLayout); 2489 // Show the tap to focus toast if this is the first start. 2490 if (mPreferences.getBoolean( 2491 CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) { 2492 // Delay the toast for one second to wait for orientation. 2493 mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000); 2494 } 2495 } else { 2496 mActivity.setSingleTapUpListener(null); 2497 } 2498 } 2499 2500 void showVideoSnapshotUI(boolean enabled) { 2501 if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { 2502 mPreviewFrameLayout.showBorder(enabled); 2503// mIndicatorControlContainer.enableZoom(!enabled); 2504 mShutterButton.setEnabled(!enabled); 2505 } 2506 } 2507 2508 // Preview area is touched. Take a picture. 2509 @Override 2510 public void onSingleTapUp(View view, int x, int y) { 2511 if (mMediaRecorderRecording && effectsActive()) { 2512 new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint, 2513 mOrientation).show(); 2514 return; 2515 } 2516 2517 if (mPaused || mSnapshotInProgress || effectsActive()) { 2518 return; 2519 } 2520 2521 if (!mMediaRecorderRecording) 2522 { 2523 // check for dismissing popup 2524 if (mPopup != null) 2525 dismissPopup(); 2526 return; 2527 } 2528 2529 // Set rotation and gps data. 2530 int rotation = Util.getJpegRotation(mCameraId, mOrientation); 2531 mParameters.setRotation(rotation); 2532 Location loc = mLocationManager.getCurrentLocation(); 2533 Util.setGpsParameters(mParameters, loc); 2534 mActivity.mCameraDevice.setParameters(mParameters); 2535 2536 Log.v(TAG, "Video snapshot start"); 2537 mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); 2538 showVideoSnapshotUI(true); 2539 mSnapshotInProgress = true; 2540 } 2541 2542 @Override 2543 public void updateCameraAppView() { 2544 if (!mPreviewing || mParameters.getFlashMode() == null) return; 2545 2546 // When going to and back from gallery, we need to turn off/on the flash. 2547 if (!mActivity.mShowCameraAppView) { 2548 if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) { 2549 mRestoreFlash = false; 2550 return; 2551 } 2552 mRestoreFlash = true; 2553 setCameraParameters(); 2554 } else if (mRestoreFlash) { 2555 mRestoreFlash = false; 2556 setCameraParameters(); 2557 } 2558 } 2559 2560 @Override 2561 public void onFullScreenChanged(boolean full) { 2562 if (mGestures != null) { 2563 mGestures.setEnabled(full); 2564 } 2565 if (ApiHelper.HAS_SURFACE_TEXTURE) { 2566 if (mActivity.mCameraScreenNail != null) { 2567 ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full); 2568 } 2569 return; 2570 } 2571 if (full) { 2572 mPreviewSurfaceView.expand(); 2573 } else { 2574 mPreviewSurfaceView.shrink(); 2575 } 2576 } 2577 2578 private final class JpegPictureCallback implements PictureCallback { 2579 Location mLocation; 2580 2581 public JpegPictureCallback(Location loc) { 2582 mLocation = loc; 2583 } 2584 2585 @Override 2586 public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) { 2587 Log.v(TAG, "onPictureTaken"); 2588 mSnapshotInProgress = false; 2589 showVideoSnapshotUI(false); 2590 storeImage(jpegData, mLocation); 2591 } 2592 } 2593 2594 private void storeImage(final byte[] data, Location loc) { 2595 long dateTaken = System.currentTimeMillis(); 2596 String title = Util.createJpegName(dateTaken); 2597 int orientation = Exif.getOrientation(data); 2598 Size s = mParameters.getPictureSize(); 2599 Uri uri = Storage.addImage(mContentResolver, title, dateTaken, loc, orientation, data, 2600 s.width, s.height); 2601 if (uri != null) { 2602 // Create a thumbnail whose width is equal or bigger than that of the preview. 2603 int ratio = (int) Math.ceil((double) mParameters.getPictureSize().width 2604 / mPreviewFrameLayout.getWidth()); 2605 int inSampleSize = Integer.highestOneBit(ratio); 2606 mActivity.mThumbnail = Thumbnail.createThumbnail(data, orientation, inSampleSize, uri); 2607 if (mActivity.mThumbnail != null && mActivity.mThumbnailView != null) { 2608 mActivity.mThumbnailView.setBitmap(mActivity.mThumbnail.getBitmap()); 2609 } 2610 Util.broadcastNewPicture(mActivity, uri); 2611 } 2612 } 2613 2614 private boolean resetEffect() { 2615 if (mResetEffect) { 2616 String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT, 2617 mPrefVideoEffectDefault); 2618 if (!mPrefVideoEffectDefault.equals(value)) { 2619 writeDefaultEffectToPrefs(); 2620 return true; 2621 } 2622 } 2623 mResetEffect = true; 2624 return false; 2625 } 2626 2627 private String convertOutputFormatToMimeType(int outputFileFormat) { 2628 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) { 2629 return "video/mp4"; 2630 } 2631 return "video/3gpp"; 2632 } 2633 2634 private String convertOutputFormatToFileExt(int outputFileFormat) { 2635 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) { 2636 return ".mp4"; 2637 } 2638 return ".3gp"; 2639 } 2640 2641 private void closeVideoFileDescriptor() { 2642 if (mVideoFileDescriptor != null) { 2643 try { 2644 mVideoFileDescriptor.close(); 2645 } catch (IOException e) { 2646 Log.e(TAG, "Fail to close fd", e); 2647 } 2648 mVideoFileDescriptor = null; 2649 } 2650 } 2651 2652 private void showTapToSnapshotToast() { 2653 new RotateTextToast(mActivity, R.string.video_snapshot_hint, mOrientationCompensation) 2654 .show(); 2655 // Clear the preference. 2656 Editor editor = mPreferences.edit(); 2657 editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false); 2658 editor.apply(); 2659 } 2660 2661 private void clearVideoNamer() { 2662 if (mVideoNamer != null) { 2663 mVideoNamer.finish(); 2664 mVideoNamer = null; 2665 } 2666 } 2667 2668 private static class VideoNamer extends Thread { 2669 private boolean mRequestPending; 2670 private ContentResolver mResolver; 2671 private ContentValues mValues; 2672 private boolean mStop; 2673 private Uri mUri; 2674 2675 // Runs in main thread 2676 public VideoNamer() { 2677 start(); 2678 } 2679 2680 // Runs in main thread 2681 public synchronized void prepareUri( 2682 ContentResolver resolver, ContentValues values) { 2683 mRequestPending = true; 2684 mResolver = resolver; 2685 mValues = new ContentValues(values); 2686 notifyAll(); 2687 } 2688 2689 // Runs in main thread 2690 public synchronized Uri getUri() { 2691 // wait until the request is done. 2692 while (mRequestPending) { 2693 try { 2694 wait(); 2695 } catch (InterruptedException ex) { 2696 // ignore. 2697 } 2698 } 2699 Uri uri = mUri; 2700 mUri = null; 2701 return uri; 2702 } 2703 2704 // Runs in namer thread 2705 @Override 2706 public synchronized void run() { 2707 while (true) { 2708 if (mStop) break; 2709 if (!mRequestPending) { 2710 try { 2711 wait(); 2712 } catch (InterruptedException ex) { 2713 // ignore. 2714 } 2715 continue; 2716 } 2717 cleanOldUri(); 2718 generateUri(); 2719 mRequestPending = false; 2720 notifyAll(); 2721 } 2722 cleanOldUri(); 2723 } 2724 2725 // Runs in main thread 2726 public synchronized void finish() { 2727 mStop = true; 2728 notifyAll(); 2729 } 2730 2731 // Runs in namer thread 2732 private void generateUri() { 2733 Uri videoTable = Uri.parse("content://media/external/video/media"); 2734 mUri = mResolver.insert(videoTable, mValues); 2735 } 2736 2737 // Runs in namer thread 2738 private void cleanOldUri() { 2739 if (mUri == null) return; 2740 mResolver.delete(mUri, null, null); 2741 mUri = null; 2742 } 2743 } 2744 2745 private class SurfaceViewCallback implements SurfaceHolder.Callback { 2746 public SurfaceViewCallback() {} 2747 2748 @Override 2749 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 2750 Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); 2751 } 2752 2753 @Override 2754 public void surfaceCreated(SurfaceHolder holder) { 2755 Log.v(TAG, "Surface created"); 2756 mSurfaceViewReady = true; 2757 if (mPaused) return; 2758 if (!ApiHelper.HAS_SURFACE_TEXTURE) { 2759 mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); 2760 if (!mPreviewing) { 2761 startPreview(); 2762 } 2763 } 2764 } 2765 2766 @Override 2767 public void surfaceDestroyed(SurfaceHolder holder) { 2768 Log.v(TAG, "Surface destroyed"); 2769 mSurfaceViewReady = false; 2770 if (mPaused) return; 2771 if (!ApiHelper.HAS_SURFACE_TEXTURE) { 2772 stopVideoRecording(); 2773 stopPreview(); 2774 } 2775 } 2776 } 2777 2778 @Override 2779 public boolean updateStorageHintOnResume() { 2780 return true; 2781 } 2782 2783 // required by OnPreferenceChangedListener 2784 @Override 2785 public void onCameraPickerClicked(int cameraId) { 2786 if (mPaused || mPendingSwitchCameraId != -1) return; 2787 2788 mPendingSwitchCameraId = cameraId; 2789 if (ApiHelper.HAS_SURFACE_TEXTURE) { 2790 Log.d(TAG, "Start to copy texture."); 2791 // We need to keep a preview frame for the animation before 2792 // releasing the camera. This will trigger onPreviewTextureCopied. 2793 ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture(); 2794 // Disable all camera controls. 2795 mSwitchingCamera = true; 2796 } else { 2797 switchCamera(); 2798 } 2799 } 2800 2801 @Override 2802 public boolean needsSwitcher() { 2803 return !mIsVideoCaptureIntent; 2804 } 2805 2806 @Override 2807 public void onPieOpened(int centerX, int centerY) { 2808 mActivity.cancelActivityTouchHandling(); 2809 mActivity.setSwipingEnabled(false); 2810 } 2811 2812 @Override 2813 public void onPieClosed() { 2814 mActivity.setSwipingEnabled(true); 2815 } 2816 2817 public void showPopup(AbstractSettingPopup popup) { 2818 mActivity.hideUI(); 2819 mPopup = popup; 2820 mPopup.setVisibility(View.VISIBLE); 2821 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, 2822 LayoutParams.WRAP_CONTENT); 2823 lp.gravity = Gravity.CENTER; 2824 ((FrameLayout) mRootView).addView(mPopup, lp); 2825 } 2826 2827 public void dismissPopup() { 2828 mActivity.showUI(); 2829 if (mPopup != null) { 2830 ((FrameLayout) mRootView).removeView(mPopup); 2831 mPopup = null; 2832 } 2833 } 2834 2835 2836} 2837