VideoCamera.java revision 61b98317dfb7b8bad4e826e1db560128e1c37374
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.gallery.IImage; 20import com.android.camera.gallery.IImageList; 21 22import android.app.Activity; 23import android.content.ActivityNotFoundException; 24import android.content.BroadcastReceiver; 25import android.content.ContentResolver; 26import android.content.ContentValues; 27import android.content.Context; 28import android.content.Intent; 29import android.content.IntentFilter; 30import android.content.SharedPreferences; 31import android.graphics.Bitmap; 32import android.graphics.drawable.Drawable; 33import android.media.MediaRecorder; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.Environment; 37import android.os.Handler; 38import android.os.Message; 39import android.os.StatFs; 40import android.os.SystemClock; 41import android.preference.PreferenceManager; 42import android.provider.MediaStore; 43import android.provider.MediaStore.Video; 44import android.text.format.DateFormat; 45import android.util.Log; 46import android.view.KeyEvent; 47import android.view.LayoutInflater; 48import android.view.Menu; 49import android.view.MenuItem; 50import android.view.SurfaceHolder; 51import android.view.View; 52import android.view.ViewGroup; 53import android.view.Window; 54import android.view.WindowManager; 55import android.view.MenuItem.OnMenuItemClickListener; 56import android.view.animation.AlphaAnimation; 57import android.view.animation.Animation; 58import android.widget.ImageView; 59import android.widget.TextView; 60import android.widget.Toast; 61 62import java.io.File; 63import java.io.FileDescriptor; 64import java.io.IOException; 65import java.text.SimpleDateFormat; 66import java.util.ArrayList; 67import java.util.Date; 68 69/** 70 * The Camcorder activity. 71 */ 72public class VideoCamera extends Activity implements View.OnClickListener, 73 ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback, 74 MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener { 75 76 private static final String TAG = "videocamera"; 77 78 private static final int INIT_RECORDER = 3; 79 private static final int CLEAR_SCREEN_DELAY = 4; 80 private static final int UPDATE_RECORD_TIME = 5; 81 82 private static final int SCREEN_DELAY = 2 * 60 * 1000; 83 84 private static final long NO_STORAGE_ERROR = -1L; 85 private static final long CANNOT_STAT_ERROR = -2L; 86 private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L; 87 88 private static final int STORAGE_STATUS_OK = 0; 89 private static final int STORAGE_STATUS_LOW = 1; 90 private static final int STORAGE_STATUS_NONE = 2; 91 92 public static final int MENU_SETTINGS = 6; 93 public static final int MENU_GALLERY_PHOTOS = 7; 94 public static final int MENU_GALLERY_VIDEOS = 8; 95 public static final int MENU_SAVE_GALLERY_PHOTO = 34; 96 public static final int MENU_SAVE_PLAY_VIDEO = 35; 97 public static final int MENU_SAVE_SELECT_VIDEO = 36; 98 public static final int MENU_SAVE_NEW_VIDEO = 37; 99 100 SharedPreferences mPreferences; 101 102 private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f; 103 VideoPreview mVideoPreview; 104 SurfaceHolder mSurfaceHolder = null; 105 ImageView mVideoFrame; 106 107 private boolean mIsVideoCaptureIntent; 108 // mLastPictureButton and mThumbController 109 // are non-null only if mIsVideoCaptureIntent is true. 110 private ImageView mLastPictureButton; 111 private ThumbnailController mThumbController; 112 113 private int mStorageStatus = STORAGE_STATUS_OK; 114 115 private MediaRecorder mMediaRecorder; 116 private boolean mMediaRecorderRecording = false; 117 private long mRecordingStartTime; 118 // The video file that the hardware camera is about to record into 119 // (or is recording into.) 120 private String mCameraVideoFilename; 121 private FileDescriptor mCameraVideoFileDescriptor; 122 123 // The video file that has already been recorded, and that is being 124 // examined by the user. 125 private String mCurrentVideoFilename; 126 private Uri mCurrentVideoUri; 127 private ContentValues mCurrentVideoValues; 128 129 // The video frame size we will record (like 352x288). 130 private int mVideoWidth, mVideoHeight; 131 132 // The video duration limit. 133 private int mMaxVideoDurationInMs; 134 135 boolean mPausing = false; 136 boolean mPreviewing = false; // True if preview is started. 137 boolean mRecorderInitialized = false; 138 139 private ContentResolver mContentResolver; 140 141 private ShutterButton mShutterButton; 142 private TextView mRecordingTimeView; 143 private boolean mRecordingTimeCountsDown = false; 144 145 ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); 146 147 private final Handler mHandler = new MainHandler(); 148 149 // This Handler is used to post message back onto the main thread of the 150 // application 151 private class MainHandler extends Handler { 152 @Override 153 public void handleMessage(Message msg) { 154 switch (msg.what) { 155 156 case CLEAR_SCREEN_DELAY: { 157 clearScreenOnFlag(); 158 break; 159 } 160 161 case UPDATE_RECORD_TIME: { 162 updateRecordingTime(); 163 break; 164 } 165 166 case INIT_RECORDER: { 167 initializeRecorder(); 168 break; 169 } 170 171 default: 172 Log.v(TAG, "Unhandled message: " + msg.what); 173 break; 174 } 175 } 176 } 177 178 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 179 @Override 180 public void onReceive(Context context, Intent intent) { 181 String action = intent.getAction(); 182 if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 183 updateAndShowStorageHint(false); 184 stopVideoRecording(); 185 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 186 updateAndShowStorageHint(true); 187 mRecorderInitialized = false; 188 initializeRecorder(); 189 } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 190 // SD card unavailable 191 // handled in ACTION_MEDIA_EJECT 192 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 193 Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000); 194 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 195 updateAndShowStorageHint(true); 196 } 197 } 198 }; 199 200 private static String createName(long dateTaken) { 201 return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); 202 } 203 204 /** Called with the activity is first created. */ 205 @Override 206 public void onCreate(Bundle icicle) { 207 super.onCreate(icicle); 208 209 /* 210 * To reduce startup time, we open camera device in another thread. 211 * Camera is opened in onCreate instead of onResume because there are 212 * lots of things to do here and camera open can be done in parallel. We 213 * will make sure the camera is opened at the end of onCreate. 214 */ 215 Thread openCameraThread = new Thread(new Runnable() { 216 public void run() { 217 mCameraDevice = CameraHolder.instance().open(); 218 } 219 }); 220 openCameraThread.start(); 221 222 mPreferences = PreferenceManager.getDefaultSharedPreferences(this); 223 mContentResolver = getContentResolver(); 224 225 requestWindowFeature(Window.FEATURE_PROGRESS); 226 setContentView(R.layout.video_camera); 227 228 mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview); 229 mVideoPreview.setAspectRatio(VIDEO_ASPECT_RATIO); 230 231 // don't set mSurfaceHolder here. We have it set ONLY within 232 // surfaceCreated / surfaceDestroyed, other parts of the code 233 // assume that when it is set, the surface is also set. 234 SurfaceHolder holder = mVideoPreview.getHolder(); 235 holder.addCallback(this); 236 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 237 238 mIsVideoCaptureIntent = isVideoCaptureIntent(); 239 mRecordingTimeView = (TextView) findViewById(R.id.recording_time); 240 mVideoFrame = (ImageView) findViewById(R.id.video_frame); 241 242 ViewGroup rootView = (ViewGroup) findViewById(R.id.video_camera); 243 LayoutInflater inflater = this.getLayoutInflater(); 244 if (!mIsVideoCaptureIntent) { 245 View controlBar = inflater.inflate(R.layout.camera_control, rootView); 246 mLastPictureButton = (ImageView) controlBar.findViewById(R.id.review_thumbnail); 247 mThumbController = new ThumbnailController(mLastPictureButton, mContentResolver); 248 mLastPictureButton.setOnClickListener(this); 249 mThumbController.loadData(ImageManager.getLastVideoThumbPath()); 250 findViewById(R.id.camera_switch).setOnClickListener(this); 251 } else { 252 View controlBar = inflater.inflate(R.layout.attach_camera_control, rootView); 253 controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this); 254 controlBar.findViewById(R.id.btn_retake).setOnClickListener(this); 255 controlBar.findViewById(R.id.btn_play).setOnClickListener(this); 256 controlBar.findViewById(R.id.btn_done).setOnClickListener(this); 257 } 258 259 mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); 260 mShutterButton.setImageResource(R.drawable.btn_ic_video_record); 261 mShutterButton.setOnShutterButtonListener(this); 262 mShutterButton.requestFocus(); 263 264 // Make sure the camera is opened. 265 try { 266 openCameraThread.join(); 267 } catch (InterruptedException ex) { 268 // ignore 269 } 270 } 271 272 private void startShareVideoActivity() { 273 Intent intent = new Intent(); 274 intent.setAction(Intent.ACTION_SEND); 275 intent.setType("video/3gpp"); 276 intent.putExtra(Intent.EXTRA_STREAM, mCurrentVideoUri); 277 try { 278 startActivity(Intent.createChooser(intent, getText(R.string.sendVideo))); 279 } catch (android.content.ActivityNotFoundException ex) { 280 Toast.makeText(VideoCamera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT) 281 .show(); 282 } 283 } 284 285 private void startPlayVideoActivity() { 286 Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); 287 try { 288 startActivity(intent); 289 } catch (android.content.ActivityNotFoundException ex) { 290 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 291 } 292 } 293 294 295 public void onClick(View v) { 296 switch (v.getId()) { 297 case R.id.btn_retake: 298 discardCurrentVideoAndInitRecorder(); 299 break; 300 case R.id.camera_switch: 301 MenuHelper.gotoCameraMode(this); 302 break; 303 case R.id.btn_play: 304 startPlayVideoActivity(); 305 break; 306 case R.id.btn_done: 307 doReturnToCaller(true); 308 break; 309 case R.id.btn_cancel: 310 stopVideoRecording(); 311 doReturnToCaller(false); 312 break; 313 case R.id.discard: { 314 Runnable deleteCallback = new Runnable() { 315 public void run() { 316 discardCurrentVideoAndInitRecorder(); 317 } 318 }; 319 MenuHelper.deleteVideo(this, deleteCallback); 320 break; 321 } 322 case R.id.share: { 323 startShareVideoActivity(); 324 break; 325 } 326 case R.id.play: { 327 doPlayCurrentVideo(); 328 break; 329 } 330 case R.id.review_thumbnail: { 331 stopVideoRecordingAndShowReview(); 332 break; 333 } 334 } 335 } 336 337 public void onShutterButtonFocus(ShutterButton button, boolean pressed) { 338 // Do nothing (everything happens in onShutterButtonClick). 339 } 340 341 public void onShutterButtonClick(ShutterButton button) { 342 switch (button.getId()) { 343 case R.id.shutter_button: 344 if (mMediaRecorderRecording) { 345 if (mIsVideoCaptureIntent) { 346 stopVideoRecordingAndShowAlert(); 347 } else { 348 stopVideoRecordingAndGetThumbnail(); 349 mRecorderInitialized = false; 350 initializeRecorder(); 351 } 352 } else if (mRecorderInitialized) { 353 // If the click comes before recorder initialization, it is 354 // ignored. If users click the button during initialization, 355 // the event is put in the queue and record will be started 356 // eventually. 357 startVideoRecording(); 358 } 359 break; 360 } 361 } 362 363 private void doPlayCurrentVideo() { 364 Log.v(TAG, "Playing current video: " + mCurrentVideoUri); 365 Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); 366 try { 367 startActivity(intent); 368 } catch (android.content.ActivityNotFoundException ex) { 369 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 370 } 371 } 372 373 private void discardCurrentVideoAndInitRecorder() { 374 deleteCurrentVideo(); 375 hideAlertAndInitializeRecorder(); 376 } 377 378 private OnScreenHint mStorageHint; 379 380 private void updateAndShowStorageHint(boolean mayHaveSd) { 381 mStorageStatus = getStorageStatus(mayHaveSd); 382 showStorageHint(); 383 } 384 385 private void showStorageHint() { 386 String errorMessage = null; 387 switch (mStorageStatus) { 388 case STORAGE_STATUS_NONE: 389 errorMessage = getString(R.string.no_storage); 390 break; 391 case STORAGE_STATUS_LOW: 392 errorMessage = getString(R.string.spaceIsLow_content); 393 } 394 if (errorMessage != null) { 395 if (mStorageHint == null) { 396 mStorageHint = OnScreenHint.makeText(this, errorMessage); 397 } else { 398 mStorageHint.setText(errorMessage); 399 } 400 mStorageHint.show(); 401 } else if (mStorageHint != null) { 402 mStorageHint.cancel(); 403 mStorageHint = null; 404 } 405 } 406 407 private int getStorageStatus(boolean mayHaveSd) { 408 long remaining = mayHaveSd ? getAvailableStorage() : NO_STORAGE_ERROR; 409 if (remaining == NO_STORAGE_ERROR) { 410 return STORAGE_STATUS_NONE; 411 } 412 return remaining < LOW_STORAGE_THRESHOLD ? STORAGE_STATUS_LOW : STORAGE_STATUS_OK; 413 } 414 415 private void readVideoSizePreference() { 416 boolean videoQualityHigh = 417 getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY, 418 CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); 419 420 // 1 minute = 60000ms 421 mMaxVideoDurationInMs = 422 60000 * getIntPreference(CameraSettings.KEY_VIDEO_DURATION, 423 CameraSettings.DEFAULT_VIDEO_DURATION_VALUE); 424 425 Intent intent = getIntent(); 426 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 427 int extraVideoQuality = intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 428 videoQualityHigh = (extraVideoQuality > 0); 429 } 430 431 if (videoQualityHigh) { 432 // CIF size 433 mVideoWidth = 352; 434 mVideoHeight = 288; 435 } else { 436 // QCIF size 437 mVideoWidth = 176; 438 mVideoHeight = 144; 439 } 440 } 441 442 @Override 443 public void onResume() { 444 super.onResume(); 445 mPausing = false; 446 447 setScreenTimeoutLong(); 448 readVideoSizePreference(); 449 450 // install an intent filter to receive SD card related events. 451 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 452 intentFilter.addAction(Intent.ACTION_MEDIA_EJECT); 453 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 454 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 455 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 456 intentFilter.addDataScheme("file"); 457 registerReceiver(mReceiver, intentFilter); 458 mStorageStatus = getStorageStatus(true); 459 460 mHandler.postDelayed(new Runnable() { 461 public void run() { 462 showStorageHint(); 463 } 464 }, 200); 465 466 if (mSurfaceHolder != null) { 467 startPreview(); 468 mRecorderInitialized = false; 469 mHandler.sendEmptyMessage(INIT_RECORDER); 470 } 471 } 472 473 private void setCameraParameters() { 474 android.hardware.Camera.Parameters param; 475 param = mCameraDevice.getParameters(); 476 param.setPreviewSize(mVideoWidth, mVideoHeight); 477 mCameraDevice.setParameters(param); 478 } 479 480 // Precondition: mSurfaceHolder != null 481 private void startPreview() { 482 Log.v(TAG, "startPreview"); 483 if (mPreviewing) { 484 // After recording a video, preview is not stopped. So just return. 485 return; 486 } 487 488 if (mCameraDevice == null) { 489 // If the activity is paused and resumed, camera device has been 490 // released and we need to open the camera. 491 mCameraDevice = CameraHolder.instance().open(); 492 } 493 494 setCameraParameters(); 495 try { 496 mCameraDevice.setPreviewDisplay(mSurfaceHolder); 497 } catch (Throwable ex) { 498 closeCamera(); 499 throw new RuntimeException("setPreviewDisplay failed", ex); 500 } 501 502 try { 503 mCameraDevice.startPreview(); 504 mPreviewing = true; 505 } catch (Throwable ex) { 506 closeCamera(); 507 throw new RuntimeException("startPreview failed", ex); 508 } 509 mCameraDevice.unlock(); 510 } 511 512 private void closeCamera() { 513 Log.v(TAG, "closeCamera"); 514 if (mCameraDevice == null) { 515 Log.d(TAG, "already stopped."); 516 return; 517 } 518 // If we don't lock the camera, release() will fail. 519 mCameraDevice.lock(); 520 CameraHolder.instance().release(); 521 mCameraDevice = null; 522 mPreviewing = false; 523 } 524 525 @Override 526 public void onStop() { 527 setScreenTimeoutSystemDefault(); 528 super.onStop(); 529 } 530 531 @Override 532 protected void onPause() { 533 super.onPause(); 534 535 mPausing = true; 536 537 // This is similar to what mShutterButton.performClick() does, 538 // but not quite the same. 539 if (mMediaRecorderRecording) { 540 if (mIsVideoCaptureIntent) { 541 stopVideoRecording(); 542 showAlert(); 543 } else { 544 stopVideoRecordingAndGetThumbnail(); 545 } 546 } else { 547 stopVideoRecording(); 548 } 549 closeCamera(); 550 551 unregisterReceiver(mReceiver); 552 setScreenTimeoutSystemDefault(); 553 554 if (!mIsVideoCaptureIntent) { 555 mThumbController.storeData(ImageManager.getLastVideoThumbPath()); 556 } 557 558 if (mStorageHint != null) { 559 mStorageHint.cancel(); 560 mStorageHint = null; 561 } 562 563 mHandler.removeMessages(INIT_RECORDER); 564 } 565 566 @Override 567 public boolean onKeyDown(int keyCode, KeyEvent event) { 568 // Do not handle any key if the activity is paused. 569 if (mPausing) { 570 return true; 571 } 572 573 setScreenTimeoutLong(); 574 575 switch (keyCode) { 576 case KeyEvent.KEYCODE_BACK: 577 if (mMediaRecorderRecording) { 578 mShutterButton.performClick(); 579 return true; 580 } 581 break; 582 case KeyEvent.KEYCODE_CAMERA: 583 if (event.getRepeatCount() == 0) { 584 mShutterButton.performClick(); 585 return true; 586 } 587 break; 588 case KeyEvent.KEYCODE_DPAD_CENTER: 589 if (event.getRepeatCount() == 0) { 590 mShutterButton.performClick(); 591 return true; 592 } 593 break; 594 case KeyEvent.KEYCODE_MENU: 595 if (mMediaRecorderRecording) { 596 mShutterButton.performClick(); 597 return true; 598 } 599 break; 600 } 601 602 return super.onKeyDown(keyCode, event); 603 } 604 605 @Override 606 public boolean onKeyUp(int keyCode, KeyEvent event) { 607 switch (keyCode) { 608 case KeyEvent.KEYCODE_CAMERA: 609 mShutterButton.setPressed(false); 610 return true; 611 } 612 return super.onKeyUp(keyCode, event); 613 } 614 615 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 616 if (mPausing) { 617 // We're pausing, the screen is off and we already stopped 618 // video recording. We don't want to start the camera again 619 // in this case in order to conserve power. 620 // The fact that surfaceChanged is called _after_ an onPause appears 621 // to be legitimate since in that case the lockscreen always returns 622 // to portrait orientation possibly triggering the notification. 623 return; 624 } 625 626 if (mMediaRecorderRecording) { 627 stopVideoRecording(); 628 } 629 630 // Start the preview if it is not started yet. Preview may be already 631 // started in onResume and then surfaceChanged is called due to 632 // orientation change. 633 if (!mPreviewing) { 634 startPreview(); 635 mRecorderInitialized = false; 636 mHandler.sendEmptyMessage(INIT_RECORDER); 637 } 638 } 639 640 public void surfaceCreated(SurfaceHolder holder) { 641 mSurfaceHolder = holder; 642 } 643 644 public void surfaceDestroyed(SurfaceHolder holder) { 645 mSurfaceHolder = null; 646 } 647 648 private void gotoGallery() { 649 MenuHelper.gotoCameraVideoGallery(this); 650 } 651 652 @Override 653 public boolean onPrepareOptionsMenu(Menu menu) { 654 super.onPrepareOptionsMenu(menu); 655 656 for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) { 657 if (i != MenuHelper.GENERIC_ITEM) { 658 menu.setGroupVisible(i, false); 659 } 660 } 661 662 menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true); 663 return true; 664 } 665 666 @Override 667 public boolean onCreateOptionsMenu(Menu menu) { 668 super.onCreateOptionsMenu(menu); 669 670 if (mIsVideoCaptureIntent) { 671 // No options menu for attach mode. 672 return false; 673 } else { 674 addBaseMenuItems(menu); 675 int menuFlags = 676 MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU 677 & ~MenuHelper.INCLUDE_DETAILS_MENU; 678 MenuHelper.addImageMenuItems(menu, menuFlags, false, VideoCamera.this, mHandler, 679 // Handler for deletion 680 new Runnable() { 681 public void run() { 682 // What do we do here? 683 // mContentResolver.delete(uri, null, null); 684 } 685 }, new MenuHelper.MenuInvoker() { 686 public void run(final MenuHelper.MenuCallback cb) { 687 } 688 }); 689 690 MenuItem gallery = 691 menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, 692 R.string.camera_gallery_photos_text).setOnMenuItemClickListener( 693 new MenuItem.OnMenuItemClickListener() { 694 public boolean onMenuItemClick(MenuItem item) { 695 gotoGallery(); 696 return true; 697 } 698 }); 699 gallery.setIcon(android.R.drawable.ic_menu_gallery); 700 } 701 return true; 702 } 703 704 private boolean isVideoCaptureIntent() { 705 String action = getIntent().getAction(); 706 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 707 } 708 709 private void doReturnToCaller(boolean success) { 710 Intent resultIntent = new Intent(); 711 int resultCode; 712 if (success) { 713 resultCode = RESULT_OK; 714 resultIntent.setData(mCurrentVideoUri); 715 } else { 716 resultCode = RESULT_CANCELED; 717 } 718 setResult(resultCode, resultIntent); 719 finish(); 720 } 721 722 /** 723 * Returns 724 * 725 * @return number of bytes available, or an ERROR code. 726 */ 727 private static long getAvailableStorage() { 728 try { 729 if (!ImageManager.hasStorage()) { 730 return NO_STORAGE_ERROR; 731 } else { 732 String storageDirectory = Environment.getExternalStorageDirectory().toString(); 733 StatFs stat = new StatFs(storageDirectory); 734 return ((long) stat.getAvailableBlocks() * (long) stat.getBlockSize()); 735 } 736 } catch (RuntimeException ex) { 737 // if we can't stat the filesystem then we don't know how many 738 // free bytes exist. It might be zero but just leave it 739 // blank since we really don't know. 740 return CANNOT_STAT_ERROR; 741 } 742 } 743 744 private void cleanupEmptyFile() { 745 if (mCameraVideoFilename != null) { 746 File f = new File(mCameraVideoFilename); 747 if (f.length() == 0 && f.delete()) { 748 Log.v(TAG, "Empty video file deleted: " + mCameraVideoFilename); 749 mCameraVideoFilename = null; 750 } 751 } 752 } 753 754 private android.hardware.Camera mCameraDevice; 755 756 // initializeRecorder() prepares media recorder. Return false if fails. 757 private boolean initializeRecorder() { 758 Log.v(TAG, "initializeRecorder"); 759 if (mRecorderInitialized) return true; 760 761 // We will call initializeRecorder() again when the alert is hidden. 762 if (isAlertVisible()) { 763 return false; 764 } 765 766 Intent intent = getIntent(); 767 Bundle myExtras = intent.getExtras(); 768 769 if (mIsVideoCaptureIntent && myExtras != null) { 770 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 771 if (saveUri != null) { 772 try { 773 mCameraVideoFileDescriptor = 774 mContentResolver.openFileDescriptor(saveUri, "rw").getFileDescriptor(); 775 mCurrentVideoUri = saveUri; 776 } catch (java.io.FileNotFoundException ex) { 777 // invalid uri 778 Log.e(TAG, ex.toString()); 779 } 780 } 781 } 782 releaseMediaRecorder(); 783 784 mMediaRecorder = new MediaRecorder(); 785 786 mMediaRecorder.setCamera(mCameraDevice); 787 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 788 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 789 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 790 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs); 791 792 if (mStorageStatus != STORAGE_STATUS_OK) { 793 mMediaRecorder.setOutputFile("/dev/null"); 794 } else { 795 // We try Uri in intent first. If it doesn't work, use our own 796 // instead. 797 if (mCameraVideoFileDescriptor != null) { 798 mMediaRecorder.setOutputFile(mCameraVideoFileDescriptor); 799 } else { 800 createVideoPath(); 801 mMediaRecorder.setOutputFile(mCameraVideoFilename); 802 } 803 } 804 805 // Use the same frame rate for both, since internally 806 // if the frame rate is too large, it can cause camera to become 807 // unstable. We need to fix the MediaRecorder to disable the support 808 // of setting frame rate for now. 809 mMediaRecorder.setVideoFrameRate(20); 810 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 811 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); 812 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 813 mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); 814 815 long remaining = getAvailableStorage(); 816 // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter 817 // of that to make it more likely that recording can complete 818 // successfully. 819 try { 820 mMediaRecorder.setMaxFileSize(remaining - LOW_STORAGE_THRESHOLD / 4); 821 } catch (RuntimeException exception) { 822 // We are going to ignore failure of setMaxFileSize here, as 823 // a) The composer selected may simply not support it, or 824 // b) The underlying media framework may not handle 64-bit range 825 // on the size restriction. 826 } 827 828 try { 829 mMediaRecorder.prepare(); 830 } catch (IOException exception) { 831 Log.e(TAG, "prepare failed for " + mCameraVideoFilename); 832 releaseMediaRecorder(); 833 // TODO: add more exception handling logic here 834 return false; 835 } 836 mMediaRecorderRecording = false; 837 838 // Update the last video thumbnail. 839 if (!mIsVideoCaptureIntent) { 840 if (!mThumbController.isUriValid()) { 841 updateLastVideo(); 842 } 843 mThumbController.updateDisplayIfNeeded(); 844 } 845 mRecorderInitialized = true; 846 return true; 847 } 848 849 private void releaseMediaRecorder() { 850 Log.v(TAG, "Releasing media recorder."); 851 if (mMediaRecorder != null) { 852 cleanupEmptyFile(); 853 mMediaRecorder.reset(); 854 mMediaRecorder.release(); 855 mMediaRecorder = null; 856 } 857 } 858 859 private int getIntPreference(String key, int defaultValue) { 860 String s = mPreferences.getString(key, ""); 861 int result = defaultValue; 862 try { 863 result = Integer.parseInt(s); 864 } catch (NumberFormatException e) { 865 // Ignore, result is already the default value. 866 } 867 return result; 868 } 869 870 private boolean getBooleanPreference(String key, boolean defaultValue) { 871 return getIntPreference(key, defaultValue ? 1 : 0) != 0; 872 } 873 874 private void createVideoPath() { 875 long dateTaken = System.currentTimeMillis(); 876 String title = createName(dateTaken); 877 String displayName = title + ".3gp"; // Used when emailing. 878 String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; 879 File cameraDir = new File(cameraDirPath); 880 cameraDir.mkdirs(); 881 SimpleDateFormat dateFormat = 882 new SimpleDateFormat(getString(R.string.video_file_name_format)); 883 Date date = new Date(dateTaken); 884 String filepart = dateFormat.format(date); 885 String filename = cameraDirPath + "/" + filepart + ".3gp"; 886 ContentValues values = new ContentValues(7); 887 values.put(Video.Media.TITLE, title); 888 values.put(Video.Media.DISPLAY_NAME, displayName); 889 values.put(Video.Media.DATE_TAKEN, dateTaken); 890 values.put(Video.Media.MIME_TYPE, "video/3gpp"); 891 values.put(Video.Media.DATA, filename); 892 mCameraVideoFilename = filename; 893 Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename); 894 mCurrentVideoValues = values; 895 } 896 897 private void registerVideo() { 898 if (mCameraVideoFileDescriptor == null) { 899 Uri videoTable = Uri.parse("content://media/external/video/media"); 900 mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length()); 901 mCurrentVideoUri = mContentResolver.insert(videoTable, mCurrentVideoValues); 902 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 903 } 904 mCurrentVideoValues = null; 905 } 906 907 private void deleteCurrentVideo() { 908 if (mCurrentVideoFilename != null) { 909 deleteVideoFile(mCurrentVideoFilename); 910 mCurrentVideoFilename = null; 911 } 912 if (mCurrentVideoUri != null) { 913 mContentResolver.delete(mCurrentVideoUri, null, null); 914 mCurrentVideoUri = null; 915 } 916 updateAndShowStorageHint(true); 917 } 918 919 private void deleteVideoFile(String fileName) { 920 Log.v(TAG, "Deleting video " + fileName); 921 File f = new File(fileName); 922 if (!f.delete()) { 923 Log.v(TAG, "Could not delete " + fileName); 924 } 925 } 926 927 private void addBaseMenuItems(Menu menu) { 928 MenuHelper.addSwitchModeMenuItem(menu, this, false); 929 { 930 MenuItem gallery = 931 menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, 932 R.string.camera_gallery_photos_text).setOnMenuItemClickListener( 933 new OnMenuItemClickListener() { 934 public boolean onMenuItemClick(MenuItem item) { 935 gotoGallery(); 936 return true; 937 } 938 }); 939 gallery.setIcon(android.R.drawable.ic_menu_gallery); 940 mGalleryItems.add(gallery); 941 } 942 { 943 MenuItem gallery = 944 menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, 945 R.string.camera_gallery_photos_text).setOnMenuItemClickListener( 946 new OnMenuItemClickListener() { 947 public boolean onMenuItemClick(MenuItem item) { 948 gotoGallery(); 949 return true; 950 } 951 }); 952 gallery.setIcon(android.R.drawable.ic_menu_gallery); 953 mGalleryItems.add(gallery); 954 } 955 956 MenuItem item = 957 menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings) 958 .setOnMenuItemClickListener(new OnMenuItemClickListener() { 959 public boolean onMenuItemClick(MenuItem item) { 960 // Keep the camera instance for a while. 961 // This avoids re-opening the camera and saves 962 // time. 963 CameraHolder.instance().keep(); 964 965 Intent intent = new Intent(); 966 intent.setClass(VideoCamera.this, CameraSettings.class); 967 startActivity(intent); 968 return true; 969 } 970 }); 971 item.setIcon(android.R.drawable.ic_menu_preferences); 972 } 973 974 // from MediaRecorder.OnErrorListener 975 public void onError(MediaRecorder mr, int what, int extra) { 976 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 977 // We may have run out of space on the sdcard. 978 stopVideoRecording(); 979 updateAndShowStorageHint(true); 980 } 981 } 982 983 // from MediaRecorder.OnInfoListener 984 public void onInfo(MediaRecorder mr, int what, int extra) { 985 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 986 mShutterButton.performClick(); 987 } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 988 mShutterButton.performClick(); 989 updateAndShowStorageHint(true); 990 } 991 } 992 993 /* 994 * Make sure we're not recording music playing in the background, ask the 995 * MediaPlaybackService to pause playback. 996 */ 997 private void pauseAudioPlayback() { 998 // Shamelessly copied from MediaPlaybackService.java, which 999 // should be public, but isn't. 1000 Intent i = new Intent("com.android.music.musicservicecommand"); 1001 i.putExtra("command", "pause"); 1002 1003 sendBroadcast(i); 1004 } 1005 1006 private void startVideoRecording() { 1007 Log.v(TAG, "startVideoRecording"); 1008 if (!mMediaRecorderRecording) { 1009 1010 if (mStorageStatus != STORAGE_STATUS_OK) { 1011 Log.v(TAG, "Storage issue, ignore the start request"); 1012 return; 1013 } 1014 1015 // Check mMediaRecorder to see whether it is initialized or not. 1016 if (mMediaRecorder == null) { 1017 Log.e(TAG, "MediaRecorder is not initialized."); 1018 return; 1019 } 1020 1021 pauseAudioPlayback(); 1022 1023 try { 1024 mMediaRecorder.setOnErrorListener(this); 1025 mMediaRecorder.setOnInfoListener(this); 1026 mMediaRecorder.start(); // Recording is now started 1027 } catch (RuntimeException e) { 1028 Log.e(TAG, "Could not start media recorder. ", e); 1029 return; 1030 } 1031 mMediaRecorderRecording = true; 1032 mRecordingStartTime = SystemClock.uptimeMillis(); 1033 updateRecordingIndicator(false); 1034 mRecordingTimeView.setText(""); 1035 mRecordingTimeView.setVisibility(View.VISIBLE); 1036 mHandler.sendEmptyMessage(UPDATE_RECORD_TIME); 1037 setScreenTimeoutInfinite(); 1038 } 1039 } 1040 1041 private void updateRecordingIndicator(boolean showRecording) { 1042 int drawableId = 1043 showRecording ? R.drawable.btn_ic_video_record 1044 : R.drawable.btn_ic_video_record_stop; 1045 Drawable drawable = getResources().getDrawable(drawableId); 1046 mShutterButton.setImageDrawable(drawable); 1047 } 1048 1049 private void stopVideoRecordingAndGetThumbnail() { 1050 stopVideoRecording(); 1051 acquireVideoThumb(); 1052 } 1053 1054 private void stopVideoRecordingAndShowAlert() { 1055 stopVideoRecording(); 1056 showAlert(); 1057 } 1058 1059 private void showAlert() { 1060 mVideoPreview.setVisibility(View.INVISIBLE); 1061 fadeOut(findViewById(R.id.shutter_button)); 1062 if (mCurrentVideoFilename != null) { 1063 mVideoFrame.setImageBitmap( 1064 Util.createVideoThumbnail(mCurrentVideoFilename)); 1065 mVideoFrame.setVisibility(View.VISIBLE); 1066 } 1067 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1068 for (int id : pickIds) { 1069 View button = findViewById(id); 1070 fadeIn(((View) button.getParent())); 1071 } 1072 } 1073 1074 private void hideAlert() { 1075 mVideoPreview.setVisibility(View.VISIBLE); 1076 mVideoFrame.setVisibility(View.INVISIBLE); 1077 fadeIn(findViewById(R.id.shutter_button)); 1078 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1079 for (int id : pickIds) { 1080 View button = findViewById(id); 1081 fadeOut(((View) button.getParent())); 1082 } 1083 } 1084 1085 private static void fadeIn(View view) { 1086 view.setVisibility(View.VISIBLE); 1087 Animation animation = new AlphaAnimation(0F, 1F); 1088 animation.setDuration(500); 1089 view.startAnimation(animation); 1090 } 1091 1092 private static void fadeOut(View view) { 1093 view.setVisibility(View.INVISIBLE); 1094 Animation animation = new AlphaAnimation(1F, 0F); 1095 animation.setDuration(500); 1096 view.startAnimation(animation); 1097 } 1098 1099 private boolean isAlertVisible() { 1100 return this.mVideoFrame.getVisibility() == View.VISIBLE; 1101 } 1102 1103 private void stopVideoRecordingAndShowReview() { 1104 stopVideoRecording(); 1105 if (mThumbController.isUriValid()) { 1106 Uri targetUri = mThumbController.getUri(); 1107 Intent intent = new Intent(this, ReviewImage.class); 1108 intent.setData(targetUri); 1109 intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true); 1110 intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true); 1111 intent.putExtra("com.android.camera.ReviewMode", true); 1112 try { 1113 startActivity(intent); 1114 } catch (ActivityNotFoundException ex) { 1115 Log.e(TAG, "review video fail", ex); 1116 } 1117 } else { 1118 Log.e(TAG, "Can't view last video."); 1119 } 1120 } 1121 1122 private void stopVideoRecording() { 1123 Log.v(TAG, "stopVideoRecording"); 1124 boolean needToRegisterRecording = false; 1125 if (mMediaRecorderRecording || mMediaRecorder != null) { 1126 if (mMediaRecorderRecording && mMediaRecorder != null) { 1127 try { 1128 mMediaRecorder.setOnErrorListener(null); 1129 mMediaRecorder.setOnInfoListener(null); 1130 mMediaRecorder.stop(); 1131 } catch (RuntimeException e) { 1132 Log.e(TAG, "stop fail: " + e.getMessage()); 1133 } 1134 1135 mCurrentVideoFilename = mCameraVideoFilename; 1136 Log.v(TAG, "Setting current video filename: " + mCurrentVideoFilename); 1137 needToRegisterRecording = true; 1138 mMediaRecorderRecording = false; 1139 } 1140 releaseMediaRecorder(); 1141 updateRecordingIndicator(true); 1142 mRecordingTimeView.setVisibility(View.GONE); 1143 setScreenTimeoutLong(); 1144 } 1145 if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) { 1146 registerVideo(); 1147 } 1148 1149 mCameraVideoFilename = null; 1150 mCameraVideoFileDescriptor = null; 1151 } 1152 1153 private void setScreenTimeoutSystemDefault() { 1154 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1155 clearScreenOnFlag(); 1156 } 1157 1158 private void setScreenTimeoutLong() { 1159 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1160 setScreenOnFlag(); 1161 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1162 } 1163 1164 private void setScreenTimeoutInfinite() { 1165 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1166 setScreenOnFlag(); 1167 } 1168 1169 private void clearScreenOnFlag() { 1170 Window w = getWindow(); 1171 final int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 1172 if ((w.getAttributes().flags & flag) != 0) { 1173 w.clearFlags(flag); 1174 } 1175 } 1176 1177 private void setScreenOnFlag() { 1178 Window w = getWindow(); 1179 final int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 1180 if ((w.getAttributes().flags & flag) == 0) { 1181 w.addFlags(flag); 1182 } 1183 } 1184 1185 private void hideAlertAndInitializeRecorder() { 1186 hideAlert(); 1187 mRecorderInitialized = false; 1188 mHandler.sendEmptyMessage(INIT_RECORDER); 1189 } 1190 1191 private void acquireVideoThumb() { 1192 Bitmap videoFrame = Util.createVideoThumbnail(mCurrentVideoFilename); 1193 mThumbController.setData(mCurrentVideoUri, videoFrame); 1194 } 1195 1196 private static ImageManager.DataLocation dataLocation() { 1197 return ImageManager.DataLocation.EXTERNAL; 1198 } 1199 1200 private void updateLastVideo() { 1201 IImageList list = 1202 ImageManager.allImages(mContentResolver, dataLocation(), 1203 ImageManager.INCLUDE_VIDEOS, ImageManager.SORT_ASCENDING, 1204 ImageManager.CAMERA_IMAGE_BUCKET_ID); 1205 int count = list.getCount(); 1206 if (count > 0) { 1207 IImage image = list.getImageAt(count - 1); 1208 Uri uri = image.fullSizeImageUri(); 1209 mThumbController.setData(uri, image.miniThumbBitmap()); 1210 } else { 1211 mThumbController.setData(null, null); 1212 } 1213 list.deactivate(); 1214 } 1215 1216 private void updateRecordingTime() { 1217 if (!mMediaRecorderRecording) { 1218 return; 1219 } 1220 long now = SystemClock.uptimeMillis(); 1221 long delta = now - mRecordingStartTime; 1222 1223 // Starting a minute before reaching the max duration 1224 // limit, we'll countdown the remaining time instead. 1225 boolean countdownRemainingTime = (delta >= mMaxVideoDurationInMs - 60000); 1226 1227 if (countdownRemainingTime) { 1228 delta = Math.max(0, mMaxVideoDurationInMs - delta); 1229 } 1230 1231 long seconds = (delta + 500) / 1000; // round to nearest 1232 long minutes = seconds / 60; 1233 long hours = minutes / 60; 1234 long remainderMinutes = minutes - (hours * 60); 1235 long remainderSeconds = seconds - (minutes * 60); 1236 1237 String secondsString = Long.toString(remainderSeconds); 1238 if (secondsString.length() < 2) { 1239 secondsString = "0" + secondsString; 1240 } 1241 String minutesString = Long.toString(remainderMinutes); 1242 if (minutesString.length() < 2) { 1243 minutesString = "0" + minutesString; 1244 } 1245 String text = minutesString + ":" + secondsString; 1246 if (hours > 0) { 1247 String hoursString = Long.toString(hours); 1248 if (hoursString.length() < 2) { 1249 hoursString = "0" + hoursString; 1250 } 1251 text = hoursString + ":" + text; 1252 } 1253 mRecordingTimeView.setText(text); 1254 1255 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1256 // Avoid setting the color on every update, do it only 1257 // when it needs changing. 1258 1259 mRecordingTimeCountsDown = countdownRemainingTime; 1260 1261 int color = 1262 getResources().getColor( 1263 countdownRemainingTime ? R.color.recording_time_remaining_text 1264 : R.color.recording_time_elapsed_text); 1265 1266 mRecordingTimeView.setTextColor(color); 1267 } 1268 1269 // Work around a limitation of the T-Mobile G1: The T-Mobile 1270 // hardware blitter can't pixel-accurately scale and clip at the 1271 // same time, and the SurfaceFlinger doesn't attempt to work around 1272 // this limitation. In order to avoid visual corruption we must 1273 // manually refresh the entire surface view when changing any 1274 // overlapping view's contents. 1275 mVideoPreview.invalidate(); 1276 mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000); 1277 } 1278} 1279