VideoCamera.java revision 9a663f7de4fa42ebbdf1ddaa5ee73ee9a7347f55
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.camera; 18 19import com.android.camera.ui.CamcorderHeadUpDisplay; 20import com.android.camera.ui.CameraPicker; 21import com.android.camera.ui.GLRootView; 22import com.android.camera.ui.HeadUpDisplay; 23import com.android.camera.ui.IndicatorWheel; 24 25import android.content.ActivityNotFoundException; 26import android.content.BroadcastReceiver; 27import android.content.ContentResolver; 28import android.content.ContentValues; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.res.Configuration; 33import android.content.res.Resources; 34import android.graphics.Bitmap; 35import android.hardware.Camera.CameraInfo; 36import android.hardware.Camera.Parameters; 37import android.hardware.Camera.Size; 38import android.media.CamcorderProfile; 39import android.media.MediaRecorder; 40import android.media.ThumbnailUtils; 41import android.net.Uri; 42import android.os.Build; 43import android.os.Bundle; 44import android.os.Handler; 45import android.os.Message; 46import android.os.ParcelFileDescriptor; 47import android.os.SystemClock; 48import android.provider.MediaStore; 49import android.provider.Settings; 50import android.provider.MediaStore.Video; 51import android.util.Log; 52import android.view.GestureDetector; 53import android.view.KeyEvent; 54import android.view.Menu; 55import android.view.MenuItem; 56import android.view.MotionEvent; 57import android.view.OrientationEventListener; 58import android.view.SurfaceHolder; 59import android.view.SurfaceView; 60import android.view.View; 61import android.view.ViewGroup; 62import android.view.Window; 63import android.view.WindowManager; 64import android.view.MenuItem.OnMenuItemClickListener; 65import android.view.animation.AlphaAnimation; 66import android.view.animation.Animation; 67import android.widget.Button; 68import android.widget.ImageView; 69import android.widget.TextView; 70import android.widget.Toast; 71 72import java.io.File; 73import java.io.IOException; 74import java.text.SimpleDateFormat; 75import java.util.ArrayList; 76import java.util.Date; 77import java.util.HashMap; 78import java.util.Iterator; 79import java.util.List; 80 81/** 82 * The Camcorder activity. 83 */ 84public class VideoCamera extends ActivityBase 85 implements View.OnClickListener, 86 ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback, 87 MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener, 88 Switcher.OnSwitchListener, PreviewFrameLayout.OnSizeChangedListener { 89 90 private static final String TAG = "videocamera"; 91 92 private static final String LAST_THUMB_FILENAME = "video_last_thumb"; 93 94 private static final int CHECK_DISPLAY_ROTATION = 3; 95 private static final int CLEAR_SCREEN_DELAY = 4; 96 private static final int UPDATE_RECORD_TIME = 5; 97 private static final int ENABLE_SHUTTER_BUTTON = 6; 98 99 private static final int SCREEN_DELAY = 2 * 60 * 1000; 100 101 // The brightness settings used when it is set to automatic in the system. 102 // The reason why it is set to 0.7 is just because 1.0 is too bright. 103 private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f; 104 105 private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L; 106 107 private static final boolean SWITCH_CAMERA = true; 108 private static final boolean SWITCH_VIDEO = false; 109 110 private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms 111 112 private static final int[] TIME_LAPSE_VIDEO_QUALITY = { 113 CamcorderProfile.QUALITY_TIME_LAPSE_1080P, 114 CamcorderProfile.QUALITY_TIME_LAPSE_720P, 115 CamcorderProfile.QUALITY_TIME_LAPSE_480P, 116 CamcorderProfile.QUALITY_TIME_LAPSE_CIF, 117 CamcorderProfile.QUALITY_TIME_LAPSE_QCIF}; 118 119 private static final int[] VIDEO_QUALITY = { 120 CamcorderProfile.QUALITY_1080P, 121 CamcorderProfile.QUALITY_720P, 122 CamcorderProfile.QUALITY_480P, 123 CamcorderProfile.QUALITY_CIF, 124 CamcorderProfile.QUALITY_QCIF}; 125 126 /** 127 * An unpublished intent flag requesting to start recording straight away 128 * and return as soon as recording is stopped. 129 * TODO: consider publishing by moving into MediaStore. 130 */ 131 private final static String EXTRA_QUICK_CAPTURE = 132 "android.intent.extra.quickCapture"; 133 134 private android.hardware.Camera mCameraDevice; 135 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback(); 136 137 private ComboPreferences mPreferences; 138 private PreferenceGroup mPreferenceGroup; 139 140 private PreviewFrameLayout mPreviewFrameLayout; 141 private SurfaceHolder mSurfaceHolder = null; 142 private GLRootView mGLRootView; 143 // xlarge devices use indicator wheel. Other devices use head-up display. 144 private CamcorderHeadUpDisplay mHeadUpDisplay; 145 private IndicatorWheel mIndicatorWheel; 146 // Front/back camera picker for xlarge layout. 147 private CameraPicker mCameraPicker; 148 private View mReviewControl; 149 150 private Toast mNoShareToast; 151 // A button showing the last captured video thumbnail. Clicking on it 152 // goes to gallery. 153 private RotateImageView mThumbnailButton; 154 // The bitmap of the last captured video thumbnail and the URI of the 155 // original video. 156 private Thumbnail mThumbnail; 157 // An review image having same size as preview. It is displayed when 158 // recording is stopped in capture intent or share button is pressed. 159 private ImageView mReviewImage; 160 private ShutterButton mShutterButton; 161 private TextView mRecordingTimeView; 162 private SwitcherSet mSwitcher; 163 164 private boolean mIsVideoCaptureIntent; 165 private boolean mQuickCapture; 166 167 private boolean mOpenCameraFail = false; 168 169 private long mStorageSpace; 170 171 private MediaRecorder mMediaRecorder; 172 private boolean mMediaRecorderRecording = false; 173 private long mRecordingStartTime; 174 private boolean mRecordingTimeCountsDown = false; 175 private long mOnResumeTime; 176 // The video file that the hardware camera is about to record into 177 // (or is recording into.) 178 private String mVideoFilename; 179 private ParcelFileDescriptor mVideoFileDescriptor; 180 181 // The video file that has already been recorded, and that is being 182 // examined by the user. 183 private String mCurrentVideoFilename; 184 private Uri mCurrentVideoUri; 185 private ContentValues mCurrentVideoValues; 186 187 private CamcorderProfile mProfile; 188 // The array of video quality profiles supported by each camera(s). Here the 189 // cameraId is the index of the array to get the profile map which contain 190 // the set of quality string and its real quality of a camera. 191 private HashMap<String, Integer>[] mProfileQuality; 192 private HashMap<String, Integer>[] mTimeLapseProfileQuality; 193 194 // The video duration limit. 0 menas no limit. 195 private int mMaxVideoDurationInMs; 196 197 // Time Lapse parameters. 198 private boolean mCaptureTimeLapse = false; 199 // Default 0. If it is larger than 0, the camcorder is in time lapse mode. 200 private int mTimeBetweenTimeLapseFrameCaptureMs = 0; 201 private View mTimeLapseLabel; 202 private View mPreviewBorder; 203 204 private int mDesiredPreviewWidth; 205 private int mDesiredPreviewHeight; 206 207 boolean mPausing = false; 208 boolean mPreviewing = false; // True if preview is started. 209 // The display rotation in degrees. This is only valid when mPreviewing is 210 // true. 211 private int mDisplayRotation; 212 213 private ContentResolver mContentResolver; 214 215 private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); 216 217 private final Handler mHandler = new MainHandler(); 218 private Parameters mParameters; 219 220 // multiple cameras support 221 private int mNumberOfCameras; 222 private int mCameraId; 223 private int mFrontCameraId; 224 private int mBackCameraId; 225 226 private GestureDetector mPopupGestureDetector; 227 228 private MyOrientationEventListener mOrientationListener; 229 // The device orientation in degrees. Default is unknown. 230 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; 231 // The orientation compensation for icons and thumbnails. 232 private int mOrientationCompensation = 0; 233 private int mOrientationHint; // the orientation hint for video playback 234 235 // This Handler is used to post message back onto the main thread of the 236 // application 237 private class MainHandler extends Handler { 238 @Override 239 public void handleMessage(Message msg) { 240 switch (msg.what) { 241 242 case ENABLE_SHUTTER_BUTTON: 243 mShutterButton.setEnabled(true); 244 break; 245 246 case CLEAR_SCREEN_DELAY: { 247 getWindow().clearFlags( 248 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 249 break; 250 } 251 252 case UPDATE_RECORD_TIME: { 253 updateRecordingTime(); 254 break; 255 } 256 257 case CHECK_DISPLAY_ROTATION: { 258 // Restart the preview if display rotation has changed. 259 // Sometimes this happens when the device is held upside 260 // down and camera app is opened. Rotation animation will 261 // take some time and the rotation value we have got may be 262 // wrong. Framework does not have a callback for this now. 263 if ((Util.getDisplayRotation(VideoCamera.this) != mDisplayRotation) 264 && !mMediaRecorderRecording) { 265 startPreview(); 266 } 267 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) { 268 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); 269 } 270 break; 271 } 272 273 default: 274 Log.v(TAG, "Unhandled message: " + msg.what); 275 break; 276 } 277 } 278 } 279 280 private BroadcastReceiver mReceiver = null; 281 282 private class MyBroadcastReceiver extends BroadcastReceiver { 283 @Override 284 public void onReceive(Context context, Intent intent) { 285 String action = intent.getAction(); 286 if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 287 updateAndShowStorageHint(); 288 stopVideoRecording(); 289 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 290 updateAndShowStorageHint(); 291 updateThumbnailButton(); 292 } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 293 // SD card unavailable 294 // handled in ACTION_MEDIA_EJECT 295 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 296 Toast.makeText(VideoCamera.this, 297 getResources().getString(R.string.wait), Toast.LENGTH_LONG).show(); 298 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 299 updateAndShowStorageHint(); 300 } 301 } 302 } 303 304 private String createName(long dateTaken) { 305 Date date = new Date(dateTaken); 306 SimpleDateFormat dateFormat = new SimpleDateFormat( 307 getString(R.string.video_file_name_format)); 308 309 return dateFormat.format(date); 310 } 311 312 private void showCameraErrorAndFinish() { 313 Resources ress = getResources(); 314 Util.showFatalErrorAndFinish(VideoCamera.this, 315 ress.getString(R.string.camera_error_title), 316 ress.getString(R.string.cannot_connect_camera)); 317 } 318 319 @Override 320 public void onCreate(Bundle icicle) { 321 super.onCreate(icicle); 322 323 Window win = getWindow(); 324 325 // Overright the brightness settings if it is automatic 326 int mode = Settings.System.getInt( 327 getContentResolver(), 328 Settings.System.SCREEN_BRIGHTNESS_MODE, 329 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); 330 if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { 331 WindowManager.LayoutParams winParams = win.getAttributes(); 332 winParams.screenBrightness = DEFAULT_CAMERA_BRIGHTNESS; 333 win.setAttributes(winParams); 334 } 335 336 mPreferences = new ComboPreferences(this); 337 CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); 338 mCameraId = CameraSettings.readPreferredCameraId(mPreferences); 339 340 //Testing purpose. Launch a specific camera through the intent extras. 341 int intentCameraId = Util.getCameraFacingIntentExtras(this); 342 if (intentCameraId != -1) { 343 mCameraId = intentCameraId; 344 } 345 346 mPreferences.setLocalId(this, mCameraId); 347 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); 348 349 mNumberOfCameras = CameraHolder.instance().getNumberOfCameras(); 350 351 /* 352 * To reduce startup time, we start the preview in another thread. 353 * We make sure the preview is started at the end of onCreate. 354 */ 355 Thread startPreviewThread = new Thread(new Runnable() { 356 public void run() { 357 mOpenCameraFail = !openCamera(); 358 // In eng build, we throw the exception so that test tool 359 // can detect it and report it 360 if (mOpenCameraFail && "eng".equals(Build.TYPE)) { 361 throw new RuntimeException("openCamera failed"); 362 } 363 readVideoPreferences(); 364 startPreview(); 365 } 366 }); 367 startPreviewThread.start(); 368 369 mContentResolver = getContentResolver(); 370 371 requestWindowFeature(Window.FEATURE_PROGRESS); 372 mIsVideoCaptureIntent = isVideoCaptureIntent(); 373 if (mIsVideoCaptureIntent) { 374 setContentView(R.layout.video_camera_attach); 375 376 mReviewControl = findViewById(R.id.review_control); 377 mReviewControl.setVisibility(View.VISIBLE); 378 findViewById(R.id.btn_cancel).setOnClickListener(this); 379 findViewById(R.id.btn_done).setOnClickListener(this); 380 findViewById(R.id.btn_play).setOnClickListener(this); 381 View retake = findViewById(R.id.btn_retake); 382 retake.setOnClickListener(this); 383 if (retake instanceof ImageView) { 384 ((ImageView) retake).setImageResource(R.drawable.btn_ic_review_retake_video); 385 } else { 386 ((Button) retake).setCompoundDrawablesWithIntrinsicBounds( 387 R.drawable.ic_switch_video_holo_dark, 0, 0, 0); 388 } 389 } else { 390 setContentView(R.layout.video_camera); 391 392 initThumbnailButton(); 393 mSwitcher = (SwitcherSet) findViewById(R.id.camera_switch); 394 mSwitcher.setVisibility(View.VISIBLE); 395 mSwitcher.setOnSwitchListener(this); 396 } 397 398 mPreviewFrameLayout = (PreviewFrameLayout) 399 findViewById(R.id.frame_layout); 400 mPreviewFrameLayout.setOnSizeChangedListener(this); 401 402 mReviewImage = (ImageView) findViewById(R.id.review_image); 403 404 // don't set mSurfaceHolder here. We have it set ONLY within 405 // surfaceCreated / surfaceDestroyed, other parts of the code 406 // assume that when it is set, the surface is also set. 407 SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview); 408 SurfaceHolder holder = preview.getHolder(); 409 holder.addCallback(this); 410 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 411 412 mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); 413 414 mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); 415 mShutterButton.setImageResource(R.drawable.btn_ic_video_record); 416 mShutterButton.setOnShutterButtonListener(this); 417 mShutterButton.requestFocus(); 418 419 mRecordingTimeView = (TextView) findViewById(R.id.recording_time); 420 mOrientationListener = new MyOrientationEventListener(VideoCamera.this); 421 mTimeLapseLabel = findViewById(R.id.time_lapse_label); 422 mPreviewBorder = findViewById(R.id.preview_border); 423 424 // Make sure preview is started. 425 try { 426 startPreviewThread.join(); 427 if (mOpenCameraFail) { 428 showCameraErrorAndFinish(); 429 return; 430 } 431 } catch (InterruptedException ex) { 432 // ignore 433 } 434 435 showTimeLapseUI(mCaptureTimeLapse); 436 resizeForPreviewAspectRatio(); 437 438 mBackCameraId = CameraHolder.instance().getBackCameraId(); 439 mFrontCameraId = CameraHolder.instance().getFrontCameraId(); 440 441 // Initialize after startPreview becuase this need mParameters. 442 initializeIndicatorWheel(); 443 // xlarge devices use indicator wheel. Other devices use head-up display. 444 if (mIndicatorWheel == null) { 445 mHeadUpDisplay = new CamcorderHeadUpDisplay(this); 446 mHeadUpDisplay.setListener(new MyHeadUpDisplayListener()); 447 initializeHeadUpDisplay(); 448 } 449 initializeCameraPicker(); 450 } 451 452 private void changeHeadUpDisplayState() { 453 if (mHeadUpDisplay == null) return; 454 // If the camera resumes behind the lock screen, the orientation 455 // will be portrait. That causes OOM when we try to allocation GPU 456 // memory for the GLSurfaceView again when the orientation changes. So, 457 // we delayed initialization of HeadUpDisplay until the orientation 458 // becomes landscape. 459 Configuration config = getResources().getConfiguration(); 460 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE 461 && !mPausing && mGLRootView == null) { 462 attachHeadUpDisplay(); 463 } else if (mGLRootView != null) { 464 detachHeadUpDisplay(); 465 } 466 } 467 468 private void initializeCameraPicker() { 469 mCameraPicker = (CameraPicker) findViewById(R.id.camera_picker); 470 if (mCameraPicker != null) { 471 mCameraPicker.setImageResource(R.drawable.camera_toggle_video); 472 ListPreference pref = mPreferenceGroup.findPreference( 473 CameraSettings.KEY_CAMERA_ID); 474 if (pref != null) { 475 mCameraPicker.initialize(pref); 476 mCameraPicker.setListener(new MyCameraPickerListener()); 477 } 478 } 479 } 480 481 private void loadCameraPreferences() { 482 CameraSettings settings = new CameraSettings(this, mParameters, 483 mCameraId, CameraHolder.instance().getCameraInfo()); 484 mPreferenceGroup = settings.getPreferenceGroup(R.xml.video_preferences); 485 } 486 487 private void initializeHeadUpDisplay() { 488 if (mHeadUpDisplay == null) return; 489 loadCameraPreferences(); 490 491 if (mIsVideoCaptureIntent) { 492 mPreferenceGroup = filterPreferenceScreenByIntent(mPreferenceGroup); 493 } 494 mHeadUpDisplay.initialize(this, mPreferenceGroup, 495 mOrientationCompensation, mCaptureTimeLapse); 496 } 497 498 private void attachHeadUpDisplay() { 499 mHeadUpDisplay.setOrientation(mOrientationCompensation); 500 ViewGroup frame = (ViewGroup) findViewById(R.id.frame); 501 mGLRootView = new GLRootView(this); 502 frame.addView(mGLRootView); 503 mGLRootView.setContentPane(mHeadUpDisplay); 504 } 505 506 private void detachHeadUpDisplay() { 507 mHeadUpDisplay.collapse(); 508 ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView); 509 mGLRootView = null; 510 } 511 512 private boolean collapseCameraControls() { 513 if (mHeadUpDisplay != null && mHeadUpDisplay.collapse()) { 514 return true; 515 } 516 if (mIndicatorWheel != null && mIndicatorWheel.dismissSettingPopup()) { 517 return true; 518 } 519 return false; 520 } 521 522 private void enableCameraControls(boolean enable) { 523 if (mHeadUpDisplay != null) mHeadUpDisplay.setEnabled(enable); 524 if (mIndicatorWheel != null) mIndicatorWheel.setEnabled(enable); 525 if (mCameraPicker != null) mCameraPicker.setEnabled(enable); 526 if (mSwitcher != null) mSwitcher.setEnabled(enable); 527 } 528 529 private void initializeIndicatorWheel() { 530 mIndicatorWheel = (IndicatorWheel) findViewById(R.id.indicator_wheel); 531 if (mIndicatorWheel == null) return; 532 loadCameraPreferences(); 533 534 final String[] SETTING_KEYS = { 535 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 536 CameraSettings.KEY_VIDEO_QUALITY, 537 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL}; 538 final String[] OTHER_SETTING_KEYS = { 539 CameraSettings.KEY_WHITE_BALANCE, 540 CameraSettings.KEY_COLOR_EFFECT}; 541 mIndicatorWheel.initialize(this, mPreferenceGroup, SETTING_KEYS, OTHER_SETTING_KEYS); 542 mIndicatorWheel.setListener(new MyIndicatorWheelListener()); 543 mPopupGestureDetector = new GestureDetector(this, 544 new PopupGestureListener()); 545 } 546 547 public static int roundOrientation(int orientation) { 548 return ((orientation + 45) / 90 * 90) % 360; 549 } 550 551 private class MyOrientationEventListener 552 extends OrientationEventListener { 553 public MyOrientationEventListener(Context context) { 554 super(context); 555 } 556 557 @Override 558 public void onOrientationChanged(int orientation) { 559 if (mMediaRecorderRecording) return; 560 // We keep the last known orientation. So if the user first orient 561 // the camera then point the camera to floor or sky, we still have 562 // the correct orientation. 563 if (orientation == ORIENTATION_UNKNOWN) return; 564 mOrientation = roundOrientation(orientation); 565 // When the screen is unlocked, display rotation may change. Always 566 // calculate the up-to-date orientationCompensation. 567 int orientationCompensation = mOrientation 568 + Util.getDisplayRotation(VideoCamera.this); 569 if (mOrientationCompensation != orientationCompensation) { 570 mOrientationCompensation = orientationCompensation; 571 if (!mIsVideoCaptureIntent) { 572 setOrientationIndicator(mOrientationCompensation); 573 } 574 if (mHeadUpDisplay != null) { 575 mHeadUpDisplay.setOrientation(mOrientationCompensation); 576 } 577 } 578 } 579 } 580 581 private void setOrientationIndicator(int degree) { 582 RotateImageView icon = (RotateImageView) findViewById( 583 R.id.review_thumbnail); 584 if (icon != null) icon.setDegree(degree); 585 586 icon = (RotateImageView) findViewById(R.id.camera_switch_icon); 587 if (icon != null) icon.setDegree(degree); 588 icon = (RotateImageView) findViewById(R.id.video_switch_icon); 589 if (icon != null) icon.setDegree(degree); 590 } 591 592 @Override 593 protected void onStart() { 594 super.onStart(); 595 if (!mIsVideoCaptureIntent) { 596 mSwitcher.setSwitch(SWITCH_VIDEO); 597 } 598 } 599 600 private void startPlayVideoActivity() { 601 Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); 602 try { 603 startActivity(intent); 604 } catch (ActivityNotFoundException ex) { 605 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 606 } 607 } 608 609 public void onClick(View v) { 610 switch (v.getId()) { 611 case R.id.btn_retake: 612 deleteCurrentVideo(); 613 hideAlert(); 614 break; 615 case R.id.btn_play: 616 startPlayVideoActivity(); 617 break; 618 case R.id.btn_done: 619 doReturnToCaller(true); 620 break; 621 case R.id.btn_cancel: 622 stopVideoRecording(); 623 doReturnToCaller(false); 624 break; 625 case R.id.review_thumbnail: 626 if (!mMediaRecorderRecording && mThumbnail != null) { 627 Util.viewUri(mThumbnail.getUri(), this); 628 } 629 break; 630 case R.id.btn_gallery: 631 gotoGallery(); 632 break; 633 } 634 } 635 636 public void onShutterButtonFocus(ShutterButton button, boolean pressed) { 637 // Do nothing (everything happens in onShutterButtonClick). 638 } 639 640 private void onStopVideoRecording(boolean valid) { 641 stopVideoRecording(); 642 if (mIsVideoCaptureIntent) { 643 if (mQuickCapture) { 644 doReturnToCaller(valid); 645 } else { 646 showAlert(); 647 } 648 } else { 649 getThumbnail(); 650 } 651 } 652 653 public void onShutterButtonClick(ShutterButton button) { 654 switch (button.getId()) { 655 case R.id.shutter_button: 656 if (collapseCameraControls()) return; 657 boolean stop = mMediaRecorderRecording; 658 659 if (stop) { 660 onStopVideoRecording(true); 661 } else { 662 startVideoRecording(); 663 } 664 mShutterButton.setEnabled(false); 665 666 // Keep the shutter button disabled when in video capture intent 667 // mode and recording is stopped. It'll be re-enabled when 668 // re-take button is clicked. 669 if (!(mIsVideoCaptureIntent && stop)) { 670 mHandler.sendEmptyMessageDelayed( 671 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT); 672 } 673 break; 674 } 675 } 676 677 private OnScreenHint mStorageHint; 678 679 private void updateAndShowStorageHint() { 680 mStorageSpace = Storage.getAvailableSpace(); 681 showStorageHint(); 682 } 683 684 private void showStorageHint() { 685 String errorMessage = null; 686 if (mStorageSpace == Storage.UNAVAILABLE) { 687 errorMessage = getString(R.string.no_storage); 688 } else if (mStorageSpace == Storage.PREPARING) { 689 errorMessage = getString(R.string.preparing_sd); 690 } else if (mStorageSpace == Storage.UNKNOWN_SIZE) { 691 errorMessage = getString(R.string.access_sd_fail); 692 } else if (mStorageSpace < LOW_STORAGE_THRESHOLD) { 693 errorMessage = getString(R.string.spaceIsLow_content); 694 } 695 696 if (errorMessage != null) { 697 if (mStorageHint == null) { 698 mStorageHint = OnScreenHint.makeText(this, errorMessage); 699 } else { 700 mStorageHint.setText(errorMessage); 701 } 702 mStorageHint.show(); 703 } else if (mStorageHint != null) { 704 mStorageHint.cancel(); 705 mStorageHint = null; 706 } 707 } 708 709 private void readVideoPreferences() { 710 String quality = mPreferences.getString( 711 CameraSettings.KEY_VIDEO_QUALITY, 712 CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); 713 boolean videoQualityHigh = 714 CameraSettings.getVideoQuality(this, quality); 715 716 // Set video quality. 717 Intent intent = getIntent(); 718 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 719 int extraVideoQuality = 720 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 721 if (extraVideoQuality > 0) { 722 quality = getString(R.string.pref_video_quality_high); 723 } else { // 0 is mms. 724 quality = getString(R.string.pref_video_quality_mms); 725 } 726 } 727 728 // Set video duration limit. The limit is read from the preference, 729 // unless it is specified in the intent. 730 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 731 int seconds = 732 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0); 733 mMaxVideoDurationInMs = 1000 * seconds; 734 } else { 735 mMaxVideoDurationInMs = 736 CameraSettings.getVideoDurationInMillis(this, quality, mCameraId); 737 } 738 739 // Read time lapse recording interval. 740 String frameIntervalStr = mPreferences.getString( 741 CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, 742 getString(R.string.pref_video_time_lapse_frame_interval_default)); 743 mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr); 744 745 mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0); 746 int profileQuality = getProfileQuality(mCameraId, quality, mCaptureTimeLapse); 747 mProfile = CamcorderProfile.get(mCameraId, profileQuality); 748 getDesiredPreviewSize(); 749 } 750 751 int getProfileQuality(int cameraId, String quality, boolean captureTimeLapse) { 752 HashMap<String, Integer>[] qualityMap; 753 if (captureTimeLapse) { 754 if (mTimeLapseProfileQuality == null) { 755 mTimeLapseProfileQuality = new HashMap[ 756 CameraHolder.instance().getNumberOfCameras()]; 757 } 758 qualityMap = mTimeLapseProfileQuality; 759 if (qualityMap[cameraId] == null) { 760 qualityMap[cameraId] = buildProfileQuality(cameraId, TIME_LAPSE_VIDEO_QUALITY); 761 } 762 } else { 763 if (mProfileQuality == null) { 764 mProfileQuality = new HashMap[ 765 CameraHolder.instance().getNumberOfCameras()]; 766 } 767 qualityMap = mProfileQuality; 768 if (qualityMap[cameraId] == null) { 769 qualityMap[cameraId] = buildProfileQuality(cameraId, VIDEO_QUALITY); 770 } 771 } 772 return qualityMap[cameraId].get(quality); 773 } 774 775 HashMap<String, Integer> buildProfileQuality(int cameraId, 776 int qualityList[]) { 777 HashMap<String, Integer> qualityMap = new HashMap<String, Integer>(); 778 int highestQuality = -1, secondHighestQuality = -1, 779 lastEffectiveQuality = -1; 780 for (int i = 0; i < qualityList.length; i++) { 781 if (CamcorderProfile.hasProfile(cameraId, qualityList[i])) { 782 if (highestQuality == -1) { 783 highestQuality = qualityList[i]; 784 } else if (secondHighestQuality == -1) { 785 secondHighestQuality = qualityList[i]; 786 break; 787 } 788 lastEffectiveQuality = qualityList[i]; 789 } 790 } 791 if (secondHighestQuality == -1) { 792 secondHighestQuality = highestQuality; 793 } 794 qualityMap.put(getString(R.string.pref_video_quality_high), highestQuality); 795 qualityMap.put(getString(R.string.pref_video_quality_low), secondHighestQuality); 796 qualityMap.put(getString(R.string.pref_video_quality_youtube), highestQuality); 797 qualityMap.put(getString(R.string.pref_video_quality_mms), lastEffectiveQuality); 798 return qualityMap; 799 } 800 801 private void getDesiredPreviewSize() { 802 mParameters = mCameraDevice.getParameters(); 803 if (mParameters.getSupportedVideoSizes() == null) { 804 mDesiredPreviewWidth = mProfile.videoFrameWidth; 805 mDesiredPreviewHeight = mProfile.videoFrameHeight; 806 } else { // Driver supports separates outputs for preview and video. 807 List<Size> sizes = mParameters.getSupportedPreviewSizes(); 808 Size preferred = mParameters.getPreferredPreviewSizeForVideo(); 809 int product = preferred.width * preferred.height; 810 Iterator it = sizes.iterator(); 811 // Remove the preview sizes that are not preferred. 812 while (it.hasNext()) { 813 Size size = (Size) it.next(); 814 if (size.width * size.height > product) { 815 it.remove(); 816 } 817 } 818 Size optimalSize = Util.getOptimalPreviewSize(this, sizes, 819 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 820 mDesiredPreviewWidth = optimalSize.width; 821 mDesiredPreviewHeight = optimalSize.height; 822 } 823 Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth + 824 ". mDesiredPreviewHeight=" + mDesiredPreviewHeight); 825 } 826 827 private void resizeForPreviewAspectRatio() { 828 mPreviewFrameLayout.setAspectRatio( 829 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 830 } 831 832 @Override 833 protected void onResume() { 834 super.onResume(); 835 mPausing = false; 836 837 mReviewImage.setVisibility(View.GONE); 838 839 // Start orientation listener as soon as possible because it takes 840 // some time to get first orientation. 841 mOrientationListener.enable(); 842 if (!mPreviewing && !mOpenCameraFail) { 843 if (!openCamera()) return; 844 readVideoPreferences(); 845 resizeForPreviewAspectRatio(); 846 startPreview(); 847 } 848 keepScreenOnAwhile(); 849 850 // install an intent filter to receive SD card related events. 851 IntentFilter intentFilter = 852 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 853 intentFilter.addAction(Intent.ACTION_MEDIA_EJECT); 854 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 855 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 856 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 857 intentFilter.addDataScheme("file"); 858 mReceiver = new MyBroadcastReceiver(); 859 registerReceiver(mReceiver, intentFilter); 860 mStorageSpace = Storage.getAvailableSpace(); 861 862 mHandler.postDelayed(new Runnable() { 863 public void run() { 864 showStorageHint(); 865 } 866 }, 200); 867 868 changeHeadUpDisplayState(); 869 870 // Update the last video thumbnail. 871 if (!mIsVideoCaptureIntent) { 872 updateThumbnailButton(); 873 } 874 875 if (mPreviewing) { 876 mOnResumeTime = SystemClock.uptimeMillis(); 877 mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); 878 } 879 } 880 881 private void setPreviewDisplay(SurfaceHolder holder) { 882 try { 883 mCameraDevice.setPreviewDisplay(holder); 884 } catch (Throwable ex) { 885 closeCamera(); 886 throw new RuntimeException("setPreviewDisplay failed", ex); 887 } 888 } 889 890 private boolean openCamera() { 891 try { 892 if (mCameraDevice == null) { 893 mCameraDevice = CameraHolder.instance().open(mCameraId); 894 } 895 } catch (CameraHardwareException e) { 896 showCameraErrorAndFinish(); 897 return false; 898 } 899 return true; 900 } 901 902 private void startPreview() { 903 Log.v(TAG, "startPreview"); 904 mCameraDevice.setErrorCallback(mErrorCallback); 905 906 if (mPreviewing == true) { 907 mCameraDevice.stopPreview(); 908 mPreviewing = false; 909 } 910 setPreviewDisplay(mSurfaceHolder); 911 mDisplayRotation = Util.getDisplayRotation(this); 912 Util.setCameraDisplayOrientation(mDisplayRotation, mCameraId, mCameraDevice); 913 setCameraParameters(); 914 915 try { 916 mCameraDevice.startPreview(); 917 mPreviewing = true; 918 } catch (Throwable ex) { 919 closeCamera(); 920 throw new RuntimeException("startPreview failed", ex); 921 } 922 } 923 924 private void closeCamera() { 925 Log.v(TAG, "closeCamera"); 926 if (mCameraDevice == null) { 927 Log.d(TAG, "already stopped."); 928 return; 929 } 930 // If we don't lock the camera, release() will fail. 931 mCameraDevice.lock(); 932 CameraHolder.instance().release(); 933 mCameraDevice = null; 934 mPreviewing = false; 935 } 936 937 private void finishRecorderAndCloseCamera() { 938 // This is similar to what mShutterButton.performClick() does, 939 // but not quite the same. 940 if (mMediaRecorderRecording) { 941 if (mIsVideoCaptureIntent) { 942 stopVideoRecording(); 943 showAlert(); 944 } else { 945 stopVideoRecording(); 946 getThumbnail(); 947 } 948 } else { 949 stopVideoRecording(); 950 } 951 closeCamera(); 952 } 953 954 @Override 955 protected void onPause() { 956 super.onPause(); 957 mPausing = true; 958 959 changeHeadUpDisplayState(); 960 if (mIndicatorWheel != null) mIndicatorWheel.dismissSettingPopup(); 961 962 finishRecorderAndCloseCamera(); 963 964 if (mReceiver != null) { 965 unregisterReceiver(mReceiver); 966 mReceiver = null; 967 } 968 resetScreenOn(); 969 970 if (!mIsVideoCaptureIntent && mThumbnail != null) { 971 mThumbnail.saveTo(LAST_THUMB_FILENAME); 972 } 973 974 if (mStorageHint != null) { 975 mStorageHint.cancel(); 976 mStorageHint = null; 977 } 978 979 mOrientationListener.disable(); 980 981 mHandler.removeMessages(CHECK_DISPLAY_ROTATION); 982 } 983 984 @Override 985 public void onUserInteraction() { 986 super.onUserInteraction(); 987 if (!mMediaRecorderRecording) keepScreenOnAwhile(); 988 } 989 990 @Override 991 public void onBackPressed() { 992 if (mPausing) return; 993 if (mMediaRecorderRecording) { 994 onStopVideoRecording(false); 995 } else if (!collapseCameraControls()) { 996 super.onBackPressed(); 997 } 998 } 999 1000 @Override 1001 public boolean onKeyDown(int keyCode, KeyEvent event) { 1002 // Do not handle any key if the activity is paused. 1003 if (mPausing) { 1004 return true; 1005 } 1006 1007 switch (keyCode) { 1008 case KeyEvent.KEYCODE_CAMERA: 1009 if (event.getRepeatCount() == 0) { 1010 mShutterButton.performClick(); 1011 return true; 1012 } 1013 break; 1014 case KeyEvent.KEYCODE_DPAD_CENTER: 1015 if (event.getRepeatCount() == 0) { 1016 mShutterButton.performClick(); 1017 return true; 1018 } 1019 break; 1020 case KeyEvent.KEYCODE_MENU: 1021 if (mMediaRecorderRecording) return true; 1022 break; 1023 } 1024 1025 return super.onKeyDown(keyCode, event); 1026 } 1027 1028 @Override 1029 public boolean onKeyUp(int keyCode, KeyEvent event) { 1030 switch (keyCode) { 1031 case KeyEvent.KEYCODE_CAMERA: 1032 mShutterButton.setPressed(false); 1033 return true; 1034 } 1035 return super.onKeyUp(keyCode, event); 1036 } 1037 1038 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 1039 // Make sure we have a surface in the holder before proceeding. 1040 if (holder.getSurface() == null) { 1041 Log.d(TAG, "holder.getSurface() == null"); 1042 return; 1043 } 1044 1045 Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h); 1046 1047 mSurfaceHolder = holder; 1048 1049 if (mPausing) { 1050 // We're pausing, the screen is off and we already stopped 1051 // video recording. We don't want to start the camera again 1052 // in this case in order to conserve power. 1053 // The fact that surfaceChanged is called _after_ an onPause appears 1054 // to be legitimate since in that case the lockscreen always returns 1055 // to portrait orientation possibly triggering the notification. 1056 return; 1057 } 1058 1059 // The mCameraDevice will be null if it is fail to connect to the 1060 // camera hardware. In this case we will show a dialog and then 1061 // finish the activity, so it's OK to ignore it. 1062 if (mCameraDevice == null) return; 1063 1064 // Set preview display if the surface is being created. Preview was 1065 // already started. Also restart the preview if display rotation has 1066 // changed. Sometimes this happens when the device is held in portrait 1067 // and camera app is opened. Rotation animation takes some time and 1068 // display rotation in onCreate may not be what we want. 1069 if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation) 1070 && holder.isCreating()) { 1071 setPreviewDisplay(holder); 1072 } else { 1073 stopVideoRecording(); 1074 startPreview(); 1075 } 1076 } 1077 1078 public void surfaceCreated(SurfaceHolder holder) { 1079 } 1080 1081 public void surfaceDestroyed(SurfaceHolder holder) { 1082 mSurfaceHolder = null; 1083 } 1084 1085 private void gotoGallery() { 1086 MenuHelper.gotoCameraVideoGallery(this); 1087 } 1088 1089 @Override 1090 public boolean onCreateOptionsMenu(Menu menu) { 1091 super.onCreateOptionsMenu(menu); 1092 1093 if (mIsVideoCaptureIntent) { 1094 // No options menu for attach mode. 1095 return false; 1096 } else { 1097 addBaseMenuItems(menu); 1098 } 1099 return true; 1100 } 1101 1102 private boolean isVideoCaptureIntent() { 1103 String action = getIntent().getAction(); 1104 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 1105 } 1106 1107 private void doReturnToCaller(boolean valid) { 1108 Intent resultIntent = new Intent(); 1109 int resultCode; 1110 if (valid) { 1111 resultCode = RESULT_OK; 1112 resultIntent.setData(mCurrentVideoUri); 1113 } else { 1114 resultCode = RESULT_CANCELED; 1115 } 1116 setResultEx(resultCode, resultIntent); 1117 finish(); 1118 } 1119 1120 private void cleanupEmptyFile() { 1121 if (mVideoFilename != null) { 1122 File f = new File(mVideoFilename); 1123 if (f.length() == 0 && f.delete()) { 1124 Log.v(TAG, "Empty video file deleted: " + mVideoFilename); 1125 mVideoFilename = null; 1126 } 1127 } 1128 } 1129 1130 // Prepares media recorder. 1131 private void initializeRecorder() { 1132 Log.v(TAG, "initializeRecorder"); 1133 // If the mCameraDevice is null, then this activity is going to finish 1134 if (mCameraDevice == null) return; 1135 1136 if (mSurfaceHolder == null) { 1137 Log.v(TAG, "Surface holder is null. Wait for surface changed."); 1138 return; 1139 } 1140 1141 Intent intent = getIntent(); 1142 Bundle myExtras = intent.getExtras(); 1143 1144 long requestedSizeLimit = 0; 1145 if (mIsVideoCaptureIntent && myExtras != null) { 1146 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 1147 if (saveUri != null) { 1148 try { 1149 mVideoFileDescriptor = 1150 mContentResolver.openFileDescriptor(saveUri, "rw"); 1151 mCurrentVideoUri = saveUri; 1152 } catch (java.io.FileNotFoundException ex) { 1153 // invalid uri 1154 Log.e(TAG, ex.toString()); 1155 } 1156 } 1157 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 1158 } 1159 mMediaRecorder = new MediaRecorder(); 1160 1161 // Unlock the camera object before passing it to media recorder. 1162 mCameraDevice.unlock(); 1163 mMediaRecorder.setCamera(mCameraDevice); 1164 if (!mCaptureTimeLapse) { 1165 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1166 } 1167 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1168 mMediaRecorder.setProfile(mProfile); 1169 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs); 1170 if (mCaptureTimeLapse) { 1171 mMediaRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs)); 1172 } 1173 1174 // Set output file. 1175 // Try Uri in the intent first. If it doesn't exist, use our own 1176 // instead. 1177 if (mVideoFileDescriptor != null) { 1178 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); 1179 } else { 1180 generateVideoFilename(mProfile.fileFormat); 1181 mMediaRecorder.setOutputFile(mVideoFilename); 1182 } 1183 1184 mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); 1185 1186 // Set maximum file size. 1187 // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter 1188 // of that to make it more likely that recording can complete 1189 // successfully. 1190 long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD / 4; 1191 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 1192 maxFileSize = requestedSizeLimit; 1193 } 1194 1195 try { 1196 mMediaRecorder.setMaxFileSize(maxFileSize); 1197 } catch (RuntimeException exception) { 1198 // We are going to ignore failure of setMaxFileSize here, as 1199 // a) The composer selected may simply not support it, or 1200 // b) The underlying media framework may not handle 64-bit range 1201 // on the size restriction. 1202 } 1203 1204 // See android.hardware.Camera.Parameters.setRotation for 1205 // documentation. 1206 int rotation = 0; 1207 if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 1208 CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; 1209 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { 1210 rotation = (info.orientation - mOrientation + 360) % 360; 1211 } else { // back-facing camera 1212 rotation = (info.orientation + mOrientation) % 360; 1213 } 1214 } 1215 mMediaRecorder.setOrientationHint(rotation); 1216 mOrientationHint = rotation; 1217 1218 try { 1219 mMediaRecorder.prepare(); 1220 } catch (IOException e) { 1221 Log.e(TAG, "prepare failed for " + mVideoFilename, e); 1222 releaseMediaRecorder(); 1223 throw new RuntimeException(e); 1224 } 1225 1226 mMediaRecorder.setOnErrorListener(this); 1227 mMediaRecorder.setOnInfoListener(this); 1228 } 1229 1230 private void releaseMediaRecorder() { 1231 Log.v(TAG, "Releasing media recorder."); 1232 if (mMediaRecorder != null) { 1233 cleanupEmptyFile(); 1234 mMediaRecorder.reset(); 1235 mMediaRecorder.release(); 1236 mMediaRecorder = null; 1237 } 1238 mVideoFilename = null; 1239 if (mVideoFileDescriptor != null) { 1240 try { 1241 mVideoFileDescriptor.close(); 1242 } catch (IOException e) { 1243 Log.e(TAG, "Fail to close fd", e); 1244 } 1245 mVideoFileDescriptor = null; 1246 } 1247 // Take back the camera object control from media recorder. Camera 1248 // device may be null if the activity is paused. 1249 if (mCameraDevice != null) mCameraDevice.lock(); 1250 } 1251 1252 private void generateVideoFilename(int outputFileFormat) { 1253 long dateTaken = System.currentTimeMillis(); 1254 String title = createName(dateTaken); 1255 String filename = title + ".3gp"; // Used when emailing. 1256 String mime = "video/3gpp"; 1257 if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) { 1258 filename = title + ".mp4"; 1259 mime = "video/mp4"; 1260 } 1261 mVideoFilename = Storage.DIRECTORY + '/' + filename; 1262 mCurrentVideoValues = new ContentValues(7); 1263 mCurrentVideoValues.put(Video.Media.TITLE, title); 1264 mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename); 1265 mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken); 1266 mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime); 1267 mCurrentVideoValues.put(Video.Media.DATA, mVideoFilename); 1268 Log.v(TAG, "New video filename: " + mVideoFilename); 1269 } 1270 1271 private void registerVideo() { 1272 if (mVideoFileDescriptor == null) { 1273 Uri videoTable = Uri.parse("content://media/external/video/media"); 1274 mCurrentVideoValues.put(Video.Media.SIZE, 1275 new File(mCurrentVideoFilename).length()); 1276 long duration = SystemClock.uptimeMillis() - mRecordingStartTime; 1277 if (duration > 0) { 1278 if (mCaptureTimeLapse) { 1279 duration = getTimeLapseVideoLength(duration); 1280 } 1281 mCurrentVideoValues.put(Video.Media.DURATION, duration); 1282 } else { 1283 Log.w(TAG, "Video duration <= 0 : " + duration); 1284 } 1285 try { 1286 mCurrentVideoUri = mContentResolver.insert(videoTable, 1287 mCurrentVideoValues); 1288 } catch (Exception e) { 1289 // We failed to insert into the database. This can happen if 1290 // the SD card is unmounted. 1291 mCurrentVideoUri = null; 1292 mCurrentVideoFilename = null; 1293 } finally { 1294 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 1295 } 1296 } 1297 mCurrentVideoValues = null; 1298 } 1299 1300 private void deleteCurrentVideo() { 1301 // Remove the video and the uri if the uri is not passed in by intent. 1302 if (mCurrentVideoFilename != null) { 1303 deleteVideoFile(mCurrentVideoFilename); 1304 mCurrentVideoFilename = null; 1305 if (mCurrentVideoUri != null) { 1306 mContentResolver.delete(mCurrentVideoUri, null, null); 1307 mCurrentVideoUri = null; 1308 } 1309 } 1310 updateAndShowStorageHint(); 1311 } 1312 1313 private void deleteVideoFile(String fileName) { 1314 Log.v(TAG, "Deleting video " + fileName); 1315 File f = new File(fileName); 1316 if (!f.delete()) { 1317 Log.v(TAG, "Could not delete " + fileName); 1318 } 1319 } 1320 1321 private void addBaseMenuItems(Menu menu) { 1322 MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() { 1323 public void run() { 1324 switchToCameraMode(); 1325 } 1326 }); 1327 MenuItem gallery = menu.add(Menu.NONE, Menu.NONE, 1328 MenuHelper.POSITION_GOTO_GALLERY, 1329 R.string.camera_gallery_photos_text) 1330 .setOnMenuItemClickListener( 1331 new OnMenuItemClickListener() { 1332 public boolean onMenuItemClick(MenuItem item) { 1333 gotoGallery(); 1334 return true; 1335 } 1336 }); 1337 gallery.setIcon(android.R.drawable.ic_menu_gallery); 1338 mGalleryItems.add(gallery); 1339 1340 if (mNumberOfCameras > 1) { 1341 menu.add(Menu.NONE, Menu.NONE, 1342 MenuHelper.POSITION_SWITCH_CAMERA_ID, 1343 R.string.switch_camera_id) 1344 .setOnMenuItemClickListener(new OnMenuItemClickListener() { 1345 public boolean onMenuItemClick(MenuItem item) { 1346 CameraSettings.writePreferredCameraId(mPreferences, 1347 ((mCameraId == mFrontCameraId) 1348 ? mBackCameraId : mFrontCameraId)); 1349 onSharedPreferenceChanged(); 1350 return true; 1351 } 1352 }).setIcon(android.R.drawable.ic_menu_camera); 1353 } 1354 } 1355 1356 private PreferenceGroup filterPreferenceScreenByIntent( 1357 PreferenceGroup screen) { 1358 Intent intent = getIntent(); 1359 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 1360 CameraSettings.removePreferenceFromScreen(screen, 1361 CameraSettings.KEY_VIDEO_QUALITY); 1362 } 1363 1364 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 1365 CameraSettings.removePreferenceFromScreen(screen, 1366 CameraSettings.KEY_VIDEO_QUALITY); 1367 } 1368 return screen; 1369 } 1370 1371 // from MediaRecorder.OnErrorListener 1372 public void onError(MediaRecorder mr, int what, int extra) { 1373 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra); 1374 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 1375 // We may have run out of space on the sdcard. 1376 stopVideoRecording(); 1377 updateAndShowStorageHint(); 1378 } 1379 } 1380 1381 // from MediaRecorder.OnInfoListener 1382 public void onInfo(MediaRecorder mr, int what, int extra) { 1383 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1384 if (mMediaRecorderRecording) onStopVideoRecording(true); 1385 } else if (what 1386 == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1387 if (mMediaRecorderRecording) onStopVideoRecording(true); 1388 1389 // Show the toast. 1390 Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit, 1391 Toast.LENGTH_LONG).show(); 1392 } 1393 } 1394 1395 /* 1396 * Make sure we're not recording music playing in the background, ask the 1397 * MediaPlaybackService to pause playback. 1398 */ 1399 private void pauseAudioPlayback() { 1400 // Shamelessly copied from MediaPlaybackService.java, which 1401 // should be public, but isn't. 1402 Intent i = new Intent("com.android.music.musicservicecommand"); 1403 i.putExtra("command", "pause"); 1404 1405 sendBroadcast(i); 1406 } 1407 1408 // For testing. 1409 public boolean isRecording() { 1410 return mMediaRecorderRecording; 1411 } 1412 1413 private void startVideoRecording() { 1414 Log.v(TAG, "startVideoRecording"); 1415 1416 updateAndShowStorageHint(); 1417 if (mStorageSpace < LOW_STORAGE_THRESHOLD) { 1418 Log.v(TAG, "Storage issue, ignore the start request"); 1419 return; 1420 } 1421 1422 initializeRecorder(); 1423 if (mMediaRecorder == null) { 1424 Log.e(TAG, "Fail to initialize media recorder"); 1425 return; 1426 } 1427 1428 pauseAudioPlayback(); 1429 1430 try { 1431 mMediaRecorder.start(); // Recording is now started 1432 } catch (RuntimeException e) { 1433 Log.e(TAG, "Could not start media recorder. ", e); 1434 releaseMediaRecorder(); 1435 return; 1436 } 1437 enableCameraControls(false); 1438 1439 mMediaRecorderRecording = true; 1440 mRecordingStartTime = SystemClock.uptimeMillis(); 1441 showRecordingUI(true); 1442 1443 updateRecordingTime(); 1444 keepScreenOn(); 1445 } 1446 1447 private void showRecordingUI(boolean recording) { 1448 if (recording) { 1449 mShutterButton.setImageDrawable(getResources().getDrawable( 1450 R.drawable.btn_ic_video_record_stop)); 1451 mShutterButton.setBackgroundResource(R.drawable.btn_shutter_recording); 1452 mRecordingTimeView.setText(""); 1453 mRecordingTimeView.setVisibility(View.VISIBLE); 1454 if (mReviewControl != null) mReviewControl.setVisibility(View.GONE); 1455 if (mCaptureTimeLapse) { 1456 mIndicatorWheel.startTimeLapseAnimation( 1457 mTimeBetweenTimeLapseFrameCaptureMs, 1458 mRecordingStartTime); 1459 } 1460 } else { 1461 mShutterButton.setImageDrawable(getResources().getDrawable( 1462 R.drawable.btn_ic_video_record)); 1463 mShutterButton.setBackgroundResource(R.drawable.btn_shutter); 1464 mRecordingTimeView.setVisibility(View.GONE); 1465 if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE); 1466 if (mCaptureTimeLapse) { 1467 mIndicatorWheel.stopTimeLapseAnimation(); 1468 } 1469 } 1470 } 1471 1472 private void getThumbnail() { 1473 Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail( 1474 mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); 1475 if (videoFrame != null) { 1476 mThumbnail = new Thumbnail(mCurrentVideoUri, videoFrame, 0); 1477 mThumbnailButton.setBitmap(mThumbnail.getBitmap()); 1478 } 1479 } 1480 1481 private void showAlert() { 1482 if (mIndicatorWheel == null) { 1483 fadeOut(findViewById(R.id.shutter_button)); 1484 } 1485 if (mCurrentVideoFilename != null) { 1486 Bitmap bitmap = ThumbnailUtils.createVideoThumbnail( 1487 mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); 1488 if (bitmap != null) { 1489 // MetadataRetriever already rotates the thumbnail. We should rotate 1490 // it back (and mirror if it is front-facing camera). 1491 CameraInfo[] info = CameraHolder.instance().getCameraInfo(); 1492 if (info[mCameraId].facing == CameraInfo.CAMERA_FACING_BACK) { 1493 bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, false); 1494 } else { 1495 bitmap = Util.rotateAndMirror(bitmap, -mOrientationHint, true); 1496 } 1497 mReviewImage.setImageBitmap(bitmap); 1498 mReviewImage.setVisibility(View.VISIBLE); 1499 } 1500 } 1501 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1502 for (int id : pickIds) { 1503 View button = findViewById(id); 1504 fadeIn(((View) button.getParent())); 1505 } 1506 1507 // Remove the text of the cancel button 1508 View view = findViewById(R.id.btn_cancel); 1509 if (view instanceof Button) ((Button) view).setText(""); 1510 showTimeLapseUI(false); 1511 } 1512 1513 private void hideAlert() { 1514 mReviewImage.setVisibility(View.INVISIBLE); 1515 fadeIn(findViewById(R.id.shutter_button)); 1516 mShutterButton.setEnabled(true); 1517 enableCameraControls(true); 1518 1519 // Restore the text of the cancel button 1520 View view = findViewById(R.id.btn_cancel); 1521 if (view instanceof Button) { 1522 ((Button) view).setText(R.string.review_cancel); 1523 } 1524 1525 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1526 for (int id : pickIds) { 1527 View button = findViewById(id); 1528 ((View) button.getParent()).setVisibility(View.GONE); 1529 } 1530 if (mCaptureTimeLapse) { 1531 showTimeLapseUI(true); 1532 } 1533 } 1534 1535 private static void fadeIn(View view) { 1536 view.setVisibility(View.VISIBLE); 1537 Animation animation = new AlphaAnimation(0F, 1F); 1538 animation.setDuration(500); 1539 view.startAnimation(animation); 1540 } 1541 1542 private static void fadeOut(View view) { 1543 view.setVisibility(View.INVISIBLE); 1544 Animation animation = new AlphaAnimation(1F, 0F); 1545 animation.setDuration(500); 1546 view.startAnimation(animation); 1547 } 1548 1549 private boolean isAlertVisible() { 1550 return this.mReviewImage.getVisibility() == View.VISIBLE; 1551 } 1552 1553 private void stopVideoRecording() { 1554 Log.v(TAG, "stopVideoRecording"); 1555 if (mMediaRecorderRecording) { 1556 boolean needToRegisterRecording = false; 1557 mMediaRecorder.setOnErrorListener(null); 1558 mMediaRecorder.setOnInfoListener(null); 1559 try { 1560 mMediaRecorder.stop(); 1561 mCurrentVideoFilename = mVideoFilename; 1562 Log.v(TAG, "Setting current video filename: " 1563 + mCurrentVideoFilename); 1564 needToRegisterRecording = true; 1565 } catch (RuntimeException e) { 1566 Log.e(TAG, "stop fail", e); 1567 if (mVideoFilename != null) deleteVideoFile(mVideoFilename); 1568 } 1569 mMediaRecorderRecording = false; 1570 showRecordingUI(false); 1571 if (!mIsVideoCaptureIntent) { 1572 enableCameraControls(true); 1573 } 1574 keepScreenOnAwhile(); 1575 if (needToRegisterRecording && mStorageSpace >= LOW_STORAGE_THRESHOLD) { 1576 registerVideo(); 1577 } 1578 } 1579 releaseMediaRecorder(); // always release media recorder 1580 } 1581 1582 private void resetScreenOn() { 1583 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1584 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1585 } 1586 1587 private void keepScreenOnAwhile() { 1588 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1589 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1590 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1591 } 1592 1593 private void keepScreenOn() { 1594 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1595 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1596 } 1597 1598 private void initThumbnailButton() { 1599 mThumbnailButton = (RotateImageView) findViewById(R.id.review_thumbnail); 1600 mThumbnailButton.setOnClickListener(this); 1601 // Load the thumbnail from the disk. 1602 mThumbnail = Thumbnail.loadFrom(LAST_THUMB_FILENAME); 1603 } 1604 1605 private void updateThumbnailButton() { 1606 if (mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver)) { 1607 mThumbnail = Thumbnail.getLastVideoThumbnail(mContentResolver); 1608 } 1609 if (mThumbnail != null) { 1610 mThumbnailButton.setBitmap(mThumbnail.getBitmap()); 1611 } else { 1612 mThumbnailButton.setBitmap(null); 1613 } 1614 } 1615 1616 private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) { 1617 long seconds = milliSeconds / 1000; // round down to compute seconds 1618 long minutes = seconds / 60; 1619 long hours = minutes / 60; 1620 long remainderMinutes = minutes - (hours * 60); 1621 long remainderSeconds = seconds - (minutes * 60); 1622 1623 StringBuilder timeStringBuilder = new StringBuilder(); 1624 1625 // Hours 1626 if (hours > 0) { 1627 if (hours < 10) { 1628 timeStringBuilder.append('0'); 1629 } 1630 timeStringBuilder.append(hours); 1631 1632 timeStringBuilder.append(':'); 1633 } 1634 1635 // Minutes 1636 if (remainderMinutes < 10) { 1637 timeStringBuilder.append('0'); 1638 } 1639 timeStringBuilder.append(remainderMinutes); 1640 timeStringBuilder.append(':'); 1641 1642 // Seconds 1643 if (remainderSeconds < 10) { 1644 timeStringBuilder.append('0'); 1645 } 1646 timeStringBuilder.append(remainderSeconds); 1647 1648 // Centi seconds 1649 if (displayCentiSeconds) { 1650 timeStringBuilder.append('.'); 1651 long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10; 1652 if (remainderCentiSeconds < 10) { 1653 timeStringBuilder.append('0'); 1654 } 1655 timeStringBuilder.append(remainderCentiSeconds); 1656 } 1657 1658 return timeStringBuilder.toString(); 1659 } 1660 1661 private long getTimeLapseVideoLength(long deltaMs) { 1662 // For better approximation calculate fractional number of frames captured. 1663 // This will update the video time at a higher resolution. 1664 double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs; 1665 return (long) (numberOfFrames / mProfile.videoFrameRate * 1000); 1666 } 1667 1668 private void updateRecordingTime() { 1669 if (!mMediaRecorderRecording) { 1670 return; 1671 } 1672 long now = SystemClock.uptimeMillis(); 1673 long delta = now - mRecordingStartTime; 1674 1675 // Starting a minute before reaching the max duration 1676 // limit, we'll countdown the remaining time instead. 1677 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0 1678 && delta >= mMaxVideoDurationInMs - 60000); 1679 1680 long deltaAdjusted = delta; 1681 if (countdownRemainingTime) { 1682 deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999; 1683 } 1684 String text; 1685 1686 long targetNextUpdateDelay; 1687 if (!mCaptureTimeLapse) { 1688 text = millisecondToTimeString(deltaAdjusted, false); 1689 targetNextUpdateDelay = 1000; 1690 } else { 1691 // The length of time lapse video is different from the length 1692 // of the actual wall clock time elapsed. Display the video length 1693 // only in format hh:mm:ss.dd, where dd are the centi seconds. 1694 text = millisecondToTimeString(getTimeLapseVideoLength(delta), true); 1695 targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs; 1696 } 1697 1698 mRecordingTimeView.setText(text); 1699 1700 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1701 // Avoid setting the color on every update, do it only 1702 // when it needs changing. 1703 mRecordingTimeCountsDown = countdownRemainingTime; 1704 1705 int color = getResources().getColor(countdownRemainingTime 1706 ? R.color.recording_time_remaining_text 1707 : R.color.recording_time_elapsed_text); 1708 1709 mRecordingTimeView.setTextColor(color); 1710 } 1711 1712 long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); 1713 mHandler.sendEmptyMessageDelayed( 1714 UPDATE_RECORD_TIME, actualNextUpdateDelay); 1715 } 1716 1717 private static boolean isSupported(String value, List<String> supported) { 1718 return supported == null ? false : supported.indexOf(value) >= 0; 1719 } 1720 1721 private void setCameraParameters() { 1722 mParameters = mCameraDevice.getParameters(); 1723 1724 mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight); 1725 mParameters.setPreviewFrameRate(mProfile.videoFrameRate); 1726 1727 // Set flash mode. 1728 String flashMode = mPreferences.getString( 1729 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 1730 getString(R.string.pref_camera_video_flashmode_default)); 1731 List<String> supportedFlash = mParameters.getSupportedFlashModes(); 1732 if (isSupported(flashMode, supportedFlash)) { 1733 mParameters.setFlashMode(flashMode); 1734 } else { 1735 flashMode = mParameters.getFlashMode(); 1736 if (flashMode == null) { 1737 flashMode = getString( 1738 R.string.pref_camera_flashmode_no_flash); 1739 } 1740 } 1741 1742 // Set white balance parameter. 1743 String whiteBalance = mPreferences.getString( 1744 CameraSettings.KEY_WHITE_BALANCE, 1745 getString(R.string.pref_camera_whitebalance_default)); 1746 if (isSupported(whiteBalance, 1747 mParameters.getSupportedWhiteBalance())) { 1748 mParameters.setWhiteBalance(whiteBalance); 1749 } else { 1750 whiteBalance = mParameters.getWhiteBalance(); 1751 if (whiteBalance == null) { 1752 whiteBalance = Parameters.WHITE_BALANCE_AUTO; 1753 } 1754 } 1755 1756 // Set color effect parameter. 1757 String colorEffect = mPreferences.getString( 1758 CameraSettings.KEY_COLOR_EFFECT, 1759 getString(R.string.pref_camera_coloreffect_default)); 1760 if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) { 1761 mParameters.setColorEffect(colorEffect); 1762 } 1763 1764 mCameraDevice.setParameters(mParameters); 1765 // Keep preview size up to date. 1766 mParameters = mCameraDevice.getParameters(); 1767 } 1768 1769 private boolean switchToCameraMode() { 1770 if (isFinishing() || mMediaRecorderRecording) return false; 1771 MenuHelper.gotoCameraMode(VideoCamera.this); 1772 finish(); 1773 return true; 1774 } 1775 1776 public boolean onSwitchChanged(Switcher source, boolean onOff) { 1777 if (onOff == SWITCH_CAMERA) { 1778 return switchToCameraMode(); 1779 } else { 1780 return true; 1781 } 1782 } 1783 1784 @Override 1785 public void onConfigurationChanged(Configuration config) { 1786 super.onConfigurationChanged(config); 1787 1788 // If the camera resumes behind the lock screen, the orientation 1789 // will be portrait. That causes OOM when we try to allocation GPU 1790 // memory for the GLSurfaceView again when the orientation changes. So, 1791 // we delayed initialization of HeadUpDisplay until the orientation 1792 // becomes landscape. 1793 changeHeadUpDisplayState(); 1794 } 1795 1796 public void onSizeChanged() { 1797 // TODO: update the content on GLRootView 1798 } 1799 1800 private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener { 1801 public void onSharedPreferenceChanged() { 1802 mHandler.post(new Runnable() { 1803 public void run() { 1804 VideoCamera.this.onSharedPreferenceChanged(); 1805 } 1806 }); 1807 } 1808 1809 public void onRestorePreferencesClicked() { 1810 mHandler.post(new Runnable() { 1811 public void run() { 1812 VideoCamera.this.onRestorePreferencesClicked(); 1813 } 1814 }); 1815 } 1816 1817 public void onPopupWindowVisibilityChanged(final int visibility) { 1818 } 1819 } 1820 1821 private void onRestorePreferencesClicked() { 1822 Runnable runnable = new Runnable() { 1823 public void run() { 1824 restorePreferences(); 1825 } 1826 }; 1827 MenuHelper.confirmAction(this, 1828 getString(R.string.confirm_restore_title), 1829 getString(R.string.confirm_restore_message), 1830 runnable); 1831 } 1832 1833 private void restorePreferences() { 1834 if (mHeadUpDisplay != null) { 1835 mHeadUpDisplay.restorePreferences(mParameters); 1836 } 1837 1838 if (mIndicatorWheel != null) { 1839 mIndicatorWheel.dismissSettingPopup(); 1840 CameraSettings.restorePreferences(VideoCamera.this, mPreferences, 1841 mParameters); 1842 initializeIndicatorWheel(); 1843 onSharedPreferenceChanged(); 1844 } 1845 } 1846 1847 private void onSharedPreferenceChanged() { 1848 // ignore the events after "onPause()" or preview has not started yet 1849 if (mPausing) return; 1850 synchronized (mPreferences) { 1851 // If mCameraDevice is not ready then we can set the parameter in 1852 // startPreview(). 1853 if (mCameraDevice == null) return; 1854 1855 // Check if camera id is changed. 1856 int cameraId = CameraSettings.readPreferredCameraId(mPreferences); 1857 if (mCameraId != cameraId) { 1858 // Restart the activity to have a crossfade animation. 1859 // TODO: Use SurfaceTexture to implement a better and faster 1860 // animation. 1861 if (mIsVideoCaptureIntent) { 1862 // If the intent is video capture, stay in video capture mode. 1863 MenuHelper.gotoVideoMode(this, getIntent()); 1864 } else { 1865 MenuHelper.gotoVideoMode(this); 1866 } 1867 finish(); 1868 } else { 1869 readVideoPreferences(); 1870 // We need to restart the preview if preview size is changed. 1871 Size size = mParameters.getPreviewSize(); 1872 if (size.width != mDesiredPreviewWidth 1873 || size.height != mDesiredPreviewHeight) { 1874 mCameraDevice.stopPreview(); 1875 resizeForPreviewAspectRatio(); 1876 startPreview(); // Parameters will be set in startPreview(). 1877 } else { 1878 setCameraParameters(); 1879 } 1880 } 1881 showTimeLapseUI(mCaptureTimeLapse); 1882 } 1883 } 1884 1885 private void showTimeLapseUI(boolean enable) { 1886 if (mTimeLapseLabel != null) { 1887 mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.INVISIBLE); 1888 } 1889 if (mPreviewBorder != null) { 1890 mPreviewBorder.setBackgroundResource(enable 1891 ? R.drawable.border_preview_time_lapse_holo 1892 : R.drawable.border_preview_holo); 1893 } 1894 1895 } 1896 1897 private void onShareButtonClicked() { 1898 if (mPausing) return; 1899 1900 // Share the last captured video. 1901 if (mThumbnail != null) { 1902 mReviewImage.setImageBitmap(mThumbnail.getBitmap()); 1903 mReviewImage.setVisibility(View.VISIBLE); 1904 1905 Intent intent = new Intent(Intent.ACTION_SEND); 1906 intent.setType("video/*"); 1907 intent.putExtra(Intent.EXTRA_STREAM, mThumbnail.getUri()); 1908 startActivity(Intent.createChooser(intent, getString(R.string.share_video_via))); 1909 } else { // No last picture 1910 if (mNoShareToast == null) { 1911 mNoShareToast = Toast.makeText(this, 1912 getResources().getString(R.string.no_video_to_share), Toast.LENGTH_SHORT); 1913 } 1914 mNoShareToast.show(); 1915 } 1916 } 1917 1918 private class MyIndicatorWheelListener implements IndicatorWheel.Listener { 1919 public void onSharedPreferenceChanged() { 1920 VideoCamera.this.onSharedPreferenceChanged(); 1921 } 1922 1923 public void onRestorePreferencesClicked() { 1924 VideoCamera.this.onRestorePreferencesClicked(); 1925 } 1926 1927 public void onOverriddenPreferencesClicked() { 1928 } 1929 1930 public void onShareButtonClicked() { 1931 VideoCamera.this.onShareButtonClicked(); 1932 } 1933 } 1934 1935 private class MyCameraPickerListener implements CameraPicker.Listener { 1936 public void onSharedPreferenceChanged() { 1937 VideoCamera.this.onSharedPreferenceChanged(); 1938 } 1939 } 1940 1941 @Override 1942 public boolean dispatchTouchEvent(MotionEvent m) { 1943 // Check if the popup window should be dismissed first. 1944 if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) { 1945 return true; 1946 } 1947 1948 return super.dispatchTouchEvent(m); 1949 } 1950 1951 private class PopupGestureListener extends 1952 GestureDetector.SimpleOnGestureListener { 1953 @Override 1954 public boolean onDown(MotionEvent e) { 1955 // Check if the popup window is visible. 1956 View popup = mIndicatorWheel.getActivePopupWindow(); 1957 if (popup == null) return false; 1958 1959 // Let popup window or indicator wheel handle the event by 1960 // themselves. Dismiss the popup window if users touch on other 1961 // areas. 1962 if (!Util.pointInView(e.getX(), e.getY(), popup) 1963 && !Util.pointInView(e.getX(), e.getY(), mIndicatorWheel)) { 1964 mIndicatorWheel.dismissSettingPopup(); 1965 // Let event fall through. 1966 } 1967 return false; 1968 } 1969 } 1970} 1971