VideoCamera.java revision 6dfb03741e9ed0821fbd7fa6912f44bf76bebca6
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 android.content.ActivityNotFoundException; 20import android.content.BroadcastReceiver; 21import android.content.ContentResolver; 22import android.content.ContentValues; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.SharedPreferences; 27import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 28import android.content.res.Resources; 29import android.graphics.Bitmap; 30import android.graphics.drawable.Drawable; 31import android.hardware.Camera.Parameters; 32import android.hardware.Camera.Size; 33import android.media.MediaRecorder; 34import android.media.ThumbnailUtil; 35import android.net.Uri; 36import android.os.Build; 37import android.os.Bundle; 38import android.os.Environment; 39import android.os.Handler; 40import android.os.Message; 41import android.os.StatFs; 42import android.os.SystemClock; 43import android.os.SystemProperties; 44import android.preference.PreferenceManager; 45import android.preference.PreferenceScreen; 46import android.provider.MediaStore; 47import android.provider.MediaStore.Video; 48import android.util.Log; 49import android.view.KeyEvent; 50import android.view.LayoutInflater; 51import android.view.Menu; 52import android.view.MenuItem; 53import android.view.MotionEvent; 54import android.view.SurfaceHolder; 55import android.view.SurfaceView; 56import android.view.View; 57import android.view.ViewGroup; 58import android.view.Window; 59import android.view.WindowManager; 60import android.view.MenuItem.OnMenuItemClickListener; 61import android.view.animation.AlphaAnimation; 62import android.view.animation.Animation; 63import android.widget.ImageView; 64import android.widget.TextView; 65import android.widget.Toast; 66 67import com.android.camera.gallery.IImage; 68import com.android.camera.gallery.IImageList; 69 70import java.io.File; 71import java.io.FileDescriptor; 72import java.io.IOException; 73import java.text.SimpleDateFormat; 74import java.util.ArrayList; 75import java.util.Date; 76import java.util.HashMap; 77import java.util.List; 78 79/** 80 * The Camcorder activity. 81 */ 82public class VideoCamera extends NoSearchActivity 83 implements View.OnClickListener, 84 ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback, 85 MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener, 86 Switcher.OnSwitchListener, OnSharedPreferenceChangeListener, 87 OnScreenSettings.OnVisibilityChangedListener, 88 PreviewFrameLayout.OnSizeChangedListener { 89 90 private static final String TAG = "videocamera"; 91 92 private static final int INIT_RECORDER = 3; 93 private static final int CLEAR_SCREEN_DELAY = 4; 94 private static final int UPDATE_RECORD_TIME = 5; 95 private static final int ENABLE_SHUTTER_BUTTON = 6; 96 97 private static final int SCREEN_DELAY = 2 * 60 * 1000; 98 99 private static final long NO_STORAGE_ERROR = -1L; 100 private static final long CANNOT_STAT_ERROR = -2L; 101 private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L; 102 103 private static final int STORAGE_STATUS_OK = 0; 104 private static final int STORAGE_STATUS_LOW = 1; 105 private static final int STORAGE_STATUS_NONE = 2; 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 SharedPreferences mPreferences; 113 114 private PreviewFrameLayout mPreviewFrameLayout; 115 private SurfaceView mVideoPreview; 116 private SurfaceHolder mSurfaceHolder = null; 117 private ImageView mVideoFrame; 118 119 private boolean mIsVideoCaptureIntent; 120 // mLastPictureButton and mThumbController 121 // are non-null only if mIsVideoCaptureIntent is true. 122 private ImageView mLastPictureButton; 123 private ThumbnailController mThumbController; 124 private boolean mStartPreviewFail = false; 125 126 private int mStorageStatus = STORAGE_STATUS_OK; 127 128 private MediaRecorder mMediaRecorder; 129 private boolean mMediaRecorderRecording = false; 130 private long mRecordingStartTime; 131 // The video file that the hardware camera is about to record into 132 // (or is recording into.) 133 private String mCameraVideoFilename; 134 private FileDescriptor mCameraVideoFileDescriptor; 135 136 // The video file that has already been recorded, and that is being 137 // examined by the user. 138 private String mCurrentVideoFilename; 139 private Uri mCurrentVideoUri; 140 private ContentValues mCurrentVideoValues; 141 private IconIndicator mWhitebalanceIndicator; 142 private IconIndicator mFlashIndicator; 143 144 private MediaRecorderProfile mProfile; 145 146 // The video duration limit. 0 menas no limit. 147 private int mMaxVideoDurationInMs; 148 149 boolean mPausing = false; 150 boolean mPreviewing = false; // True if preview is started. 151 152 private ContentResolver mContentResolver; 153 154 private ShutterButton mShutterButton; 155 private TextView mRecordingTimeView; 156 private View mGripper; 157 private Switcher mSwitcher; 158 private boolean mRecordingTimeCountsDown = false; 159 160 private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); 161 162 private final Handler mHandler = new MainHandler(); 163 private Parameters mParameters; 164 private OnScreenSettings mSettings; 165 166 // This Handler is used to post message back onto the main thread of the 167 // application 168 private class MainHandler extends Handler { 169 @Override 170 public void handleMessage(Message msg) { 171 switch (msg.what) { 172 173 case ENABLE_SHUTTER_BUTTON: 174 mShutterButton.setEnabled(true); 175 break; 176 177 case CLEAR_SCREEN_DELAY: { 178 getWindow().clearFlags( 179 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 180 break; 181 } 182 183 case UPDATE_RECORD_TIME: { 184 updateRecordingTime(); 185 break; 186 } 187 188 case INIT_RECORDER: { 189 initializeRecorder(); 190 break; 191 } 192 193 default: 194 Log.v(TAG, "Unhandled message: " + msg.what); 195 break; 196 } 197 } 198 } 199 200 private BroadcastReceiver mReceiver = null; 201 202 private class MyBroadcastReceiver extends BroadcastReceiver { 203 @Override 204 public void onReceive(Context context, Intent intent) { 205 String action = intent.getAction(); 206 if (action.equals(Intent.ACTION_MEDIA_EJECT)) { 207 updateAndShowStorageHint(false); 208 stopVideoRecording(); 209 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 210 updateAndShowStorageHint(true); 211 initializeRecorder(); 212 } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 213 // SD card unavailable 214 // handled in ACTION_MEDIA_EJECT 215 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 216 Toast.makeText(VideoCamera.this, 217 getResources().getString(R.string.wait), 5000); 218 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 219 updateAndShowStorageHint(true); 220 } 221 } 222 } 223 224 private String createName(long dateTaken) { 225 Date date = new Date(dateTaken); 226 SimpleDateFormat dateFormat = new SimpleDateFormat( 227 getString(R.string.video_file_name_format)); 228 229 return dateFormat.format(date); 230 } 231 232 private void showCameraBusyAndFinish() { 233 Resources ress = getResources(); 234 Util.showFatalErrorAndFinish(VideoCamera.this, 235 ress.getString(R.string.camera_error_title), 236 ress.getString(R.string.cannot_connect_camera)); 237 } 238 239 @Override 240 public void onCreate(Bundle icicle) { 241 super.onCreate(icicle); 242 243 mPreferences = PreferenceManager.getDefaultSharedPreferences(this); 244 CameraSettings.upgradePreferences(mPreferences); 245 246 readVideoPreferences(); 247 248 /* 249 * To reduce startup time, we start the preview in another thread. 250 * We make sure the preview is started at the end of onCreate. 251 */ 252 Thread startPreviewThread = new Thread(new Runnable() { 253 public void run() { 254 try { 255 mStartPreviewFail = false; 256 startPreview(); 257 } catch (CameraHardwareException e) { 258 // In eng build, we throw the exception so that test tool 259 // can detect it and report it 260 if ("eng".equals(Build.TYPE)) { 261 throw new RuntimeException(e); 262 } 263 mStartPreviewFail = true; 264 } 265 } 266 }); 267 startPreviewThread.start(); 268 269 mContentResolver = getContentResolver(); 270 271 requestWindowFeature(Window.FEATURE_PROGRESS); 272 setContentView(R.layout.video_camera); 273 274 mPreviewFrameLayout = (PreviewFrameLayout) 275 findViewById(R.id.frame_layout); 276 mPreviewFrameLayout.setOnSizeChangedListener(this); 277 resizeForPreviewAspectRatio(); 278 279 mVideoPreview = (SurfaceView) findViewById(R.id.camera_preview); 280 mVideoFrame = (ImageView) findViewById(R.id.video_frame); 281 282 // don't set mSurfaceHolder here. We have it set ONLY within 283 // surfaceCreated / surfaceDestroyed, other parts of the code 284 // assume that when it is set, the surface is also set. 285 SurfaceHolder holder = mVideoPreview.getHolder(); 286 holder.addCallback(this); 287 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 288 289 mIsVideoCaptureIntent = isVideoCaptureIntent(); 290 mRecordingTimeView = (TextView) findViewById(R.id.recording_time); 291 292 ViewGroup rootView = (ViewGroup) findViewById(R.id.video_camera); 293 LayoutInflater inflater = this.getLayoutInflater(); 294 if (!mIsVideoCaptureIntent) { 295 View controlBar = inflater.inflate( 296 R.layout.camera_control, rootView); 297 mLastPictureButton = 298 (ImageView) controlBar.findViewById(R.id.review_thumbnail); 299 mThumbController = new ThumbnailController( 300 getResources(), mLastPictureButton, mContentResolver); 301 mLastPictureButton.setOnClickListener(this); 302 mThumbController.loadData(ImageManager.getLastVideoThumbPath()); 303 mSwitcher = ((Switcher) findViewById(R.id.camera_switch)); 304 mSwitcher.setOnSwitchListener(this); 305 mSwitcher.addTouchView(findViewById(R.id.camera_switch_set)); 306 } else { 307 View controlBar = inflater.inflate( 308 R.layout.attach_camera_control, rootView); 309 controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this); 310 ImageView retake = 311 (ImageView) controlBar.findViewById(R.id.btn_retake); 312 retake.setOnClickListener(this); 313 retake.setImageResource(R.drawable.btn_ic_review_retake_video); 314 controlBar.findViewById(R.id.btn_play).setOnClickListener(this); 315 controlBar.findViewById(R.id.btn_done).setOnClickListener(this); 316 } 317 318 mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); 319 mShutterButton.setImageResource(R.drawable.btn_ic_video_record); 320 mShutterButton.setOnShutterButtonListener(this); 321 mShutterButton.requestFocus(); 322 mGripper = findViewById(R.id.btn_gripper); 323 mGripper.setOnTouchListener(new GripperTouchListener()); 324 325 mWhitebalanceIndicator = 326 (IconIndicator) findViewById(R.id.whitebalance_icon); 327 mFlashIndicator = (IconIndicator) findViewById(R.id.flash_icon); 328 329 // Make sure preview is started. 330 try { 331 startPreviewThread.join(); 332 if (mStartPreviewFail) { 333 showCameraBusyAndFinish(); 334 return; 335 } 336 } catch (InterruptedException ex) { 337 // ignore 338 } 339 removeUnsupportedIndicators(); 340 } 341 342 private void removeUnsupportedIndicators() { 343 if (mParameters.getSupportedWhiteBalance() == null) { 344 mWhitebalanceIndicator.setVisibility(View.GONE); 345 } 346 if (mParameters.getSupportedFlashModes() == null) { 347 mFlashIndicator.setVisibility(View.GONE); 348 } 349 } 350 351 @Override 352 protected void onStart() { 353 super.onStart(); 354 if (!mIsVideoCaptureIntent) { 355 mSwitcher.setSwitch(SWITCH_VIDEO); 356 } 357 } 358 359 private void startPlayVideoActivity() { 360 Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); 361 try { 362 startActivity(intent); 363 } catch (android.content.ActivityNotFoundException ex) { 364 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 365 } 366 } 367 368 public void onClick(View v) { 369 switch (v.getId()) { 370 case R.id.btn_retake: 371 discardCurrentVideoAndInitRecorder(); 372 break; 373 case R.id.btn_play: 374 startPlayVideoActivity(); 375 break; 376 case R.id.btn_done: 377 doReturnToCaller(true); 378 break; 379 case R.id.btn_cancel: 380 stopVideoRecording(); 381 doReturnToCaller(false); 382 break; 383 case R.id.review_thumbnail: { 384 if (!mMediaRecorderRecording) { 385 stopVideoRecordingAndShowReview(); 386 initializeRecorder(); 387 } 388 break; 389 } 390 } 391 } 392 393 public void onShutterButtonFocus(ShutterButton button, boolean pressed) { 394 // Do nothing (everything happens in onShutterButtonClick). 395 } 396 397 private void onStopVideoRecording() { 398 if (mIsVideoCaptureIntent) { 399 stopVideoRecordingAndShowAlert(); 400 } else { 401 stopVideoRecordingAndGetThumbnail(); 402 initializeRecorder(); 403 } 404 } 405 406 public void onShutterButtonClick(ShutterButton button) { 407 switch (button.getId()) { 408 case R.id.shutter_button: 409 if (mMediaRecorderRecording) { 410 onStopVideoRecording(); 411 } else if (mMediaRecorder != null) { 412 // If the click comes before recorder initialization, it is 413 // ignored. If users click the button during initialization, 414 // the event is put in the queue and record will be started 415 // eventually. 416 startVideoRecording(); 417 } 418 mShutterButton.setEnabled(false); 419 mHandler.sendEmptyMessageDelayed( 420 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT); 421 break; 422 } 423 } 424 425 private void discardCurrentVideoAndInitRecorder() { 426 deleteCurrentVideo(); 427 hideAlertAndInitializeRecorder(); 428 } 429 430 private OnScreenHint mStorageHint; 431 432 private void updateAndShowStorageHint(boolean mayHaveSd) { 433 mStorageStatus = getStorageStatus(mayHaveSd); 434 showStorageHint(); 435 } 436 437 private void showStorageHint() { 438 String errorMessage = null; 439 switch (mStorageStatus) { 440 case STORAGE_STATUS_NONE: 441 errorMessage = getString(R.string.no_storage); 442 break; 443 case STORAGE_STATUS_LOW: 444 errorMessage = getString(R.string.spaceIsLow_content); 445 } 446 if (errorMessage != null) { 447 if (mStorageHint == null) { 448 mStorageHint = OnScreenHint.makeText(this, errorMessage); 449 } else { 450 mStorageHint.setText(errorMessage); 451 } 452 mStorageHint.show(); 453 } else if (mStorageHint != null) { 454 mStorageHint.cancel(); 455 mStorageHint = null; 456 } 457 } 458 459 private int getStorageStatus(boolean mayHaveSd) { 460 long remaining = mayHaveSd ? getAvailableStorage() : NO_STORAGE_ERROR; 461 if (remaining == NO_STORAGE_ERROR) { 462 return STORAGE_STATUS_NONE; 463 } 464 return remaining < LOW_STORAGE_THRESHOLD 465 ? STORAGE_STATUS_LOW 466 : STORAGE_STATUS_OK; 467 } 468 469 private void readVideoPreferences() { 470 boolean videoQualityHigh = 471 getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY, 472 CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); 473 474 // Set video quality. 475 Intent intent = getIntent(); 476 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 477 int extraVideoQuality = 478 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 479 videoQualityHigh = (extraVideoQuality > 0); 480 } 481 482 // Set video duration limit. The limit is read from the preference, 483 // unless it is specified in the intent. 484 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 485 int seconds = 486 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0); 487 mMaxVideoDurationInMs = 1000 * seconds; 488 } else { 489 int minutes = getIntPreference(CameraSettings.KEY_VIDEO_DURATION, 490 CameraSettings.DEFAULT_VIDEO_DURATION_VALUE); 491 if (minutes == -1) { 492 // This is a special case: the value -1 means we want to use the 493 // device-dependent duration for MMS messages. The value is 494 // represented in seconds. 495 mMaxVideoDurationInMs = 496 1000 * CameraSettings.MMS_VIDEO_DURATION; 497 } else { 498 // 1 minute = 60000ms 499 mMaxVideoDurationInMs = 60000 * minutes; 500 } 501 } 502 503 mProfile = new MediaRecorderProfile(videoQualityHigh); 504 } 505 506 private void resizeForPreviewAspectRatio() { 507 mPreviewFrameLayout.setAspectRatio( 508 (double) mProfile.mVideoWidth / mProfile.mVideoHeight); 509 } 510 511 @Override 512 public void onResume() { 513 super.onResume(); 514 mPausing = false; 515 516 readVideoPreferences(); 517 resizeForPreviewAspectRatio(); 518 if (!mPreviewing && !mStartPreviewFail) { 519 try { 520 startPreview(); 521 } catch (CameraHardwareException e) { 522 showCameraBusyAndFinish(); 523 return; 524 } 525 } 526 keepScreenOnAwhile(); 527 528 // install an intent filter to receive SD card related events. 529 IntentFilter intentFilter = 530 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 531 intentFilter.addAction(Intent.ACTION_MEDIA_EJECT); 532 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 533 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 534 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 535 intentFilter.addDataScheme("file"); 536 mReceiver = new MyBroadcastReceiver(); 537 registerReceiver(mReceiver, intentFilter); 538 mStorageStatus = getStorageStatus(true); 539 540 mHandler.postDelayed(new Runnable() { 541 public void run() { 542 showStorageHint(); 543 } 544 }, 200); 545 546 if (mSurfaceHolder != null) { 547 mHandler.sendEmptyMessage(INIT_RECORDER); 548 } 549 } 550 551 private void setPreviewDisplay(SurfaceHolder holder) { 552 try { 553 mCameraDevice.setPreviewDisplay(holder); 554 } catch (Throwable ex) { 555 closeCamera(); 556 throw new RuntimeException("setPreviewDisplay failed", ex); 557 } 558 } 559 560 private void startPreview() throws CameraHardwareException { 561 Log.v(TAG, "startPreview"); 562 if (mPreviewing) { 563 // After recording a video, preview is not stopped. So just return. 564 return; 565 } 566 567 if (mCameraDevice == null) { 568 // If the activity is paused and resumed, camera device has been 569 // released and we need to open the camera. 570 mCameraDevice = CameraHolder.instance().open(); 571 } 572 573 mCameraDevice.lock(); 574 setCameraParameters(); 575 setPreviewDisplay(mSurfaceHolder); 576 577 try { 578 mCameraDevice.startPreview(); 579 mPreviewing = true; 580 } catch (Throwable ex) { 581 closeCamera(); 582 throw new RuntimeException("startPreview failed", ex); 583 } 584 585 // If setPreviewDisplay has been set with a valid surface, unlock now. 586 // If surface is null, unlock later. Otherwise, setPreviewDisplay in 587 // surfaceChanged will fail. 588 if (mSurfaceHolder != null) { 589 mCameraDevice.unlock(); 590 } 591 } 592 593 private void closeCamera() { 594 Log.v(TAG, "closeCamera"); 595 if (mCameraDevice == null) { 596 Log.d(TAG, "already stopped."); 597 return; 598 } 599 // If we don't lock the camera, release() will fail. 600 mCameraDevice.lock(); 601 CameraHolder.instance().release(); 602 mCameraDevice = null; 603 mPreviewing = false; 604 } 605 606 @Override 607 protected void onPause() { 608 super.onPause(); 609 610 mPausing = true; 611 612 if (mSettings != null && mSettings.isVisible()) { 613 mSettings.setVisible(false); 614 } 615 616 // This is similar to what mShutterButton.performClick() does, 617 // but not quite the same. 618 if (mMediaRecorderRecording) { 619 if (mIsVideoCaptureIntent) { 620 stopVideoRecording(); 621 showAlert(); 622 } else { 623 stopVideoRecordingAndGetThumbnail(); 624 } 625 } else { 626 stopVideoRecording(); 627 } 628 closeCamera(); 629 630 if (mReceiver != null) { 631 unregisterReceiver(mReceiver); 632 mReceiver = null; 633 } 634 resetScreenOn(); 635 636 if (!mIsVideoCaptureIntent) { 637 mThumbController.storeData(ImageManager.getLastVideoThumbPath()); 638 } 639 640 if (mStorageHint != null) { 641 mStorageHint.cancel(); 642 mStorageHint = null; 643 } 644 645 mHandler.removeMessages(INIT_RECORDER); 646 } 647 648 @Override 649 public void onUserInteraction() { 650 super.onUserInteraction(); 651 if (!mMediaRecorderRecording) keepScreenOnAwhile(); 652 } 653 654 @Override 655 public void onBackPressed() { 656 if (mPausing) return; 657 if (mMediaRecorderRecording) { 658 onStopVideoRecording(); 659 return; 660 } 661 super.onBackPressed(); 662 } 663 664 @Override 665 public boolean onKeyDown(int keyCode, KeyEvent event) { 666 // Do not handle any key if the activity is paused. 667 if (mPausing) { 668 return true; 669 } 670 671 switch (keyCode) { 672 case KeyEvent.KEYCODE_CAMERA: 673 if (event.getRepeatCount() == 0) { 674 mShutterButton.performClick(); 675 return true; 676 } 677 break; 678 case KeyEvent.KEYCODE_DPAD_CENTER: 679 if (event.getRepeatCount() == 0) { 680 mShutterButton.performClick(); 681 return true; 682 } 683 break; 684 case KeyEvent.KEYCODE_MENU: 685 if (mMediaRecorderRecording) { 686 onStopVideoRecording(); 687 return true; 688 } 689 break; 690 } 691 692 return super.onKeyDown(keyCode, event); 693 } 694 695 @Override 696 public boolean onKeyUp(int keyCode, KeyEvent event) { 697 switch (keyCode) { 698 case KeyEvent.KEYCODE_CAMERA: 699 mShutterButton.setPressed(false); 700 return true; 701 case KeyEvent.KEYCODE_MENU: 702 if (this.mIsVideoCaptureIntent) { 703 showOnScreenSettings(); 704 return true; 705 } 706 break; 707 } 708 return super.onKeyUp(keyCode, event); 709 } 710 711 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 712 // Make sure we have a surface in the holder before proceeding. 713 if (holder.getSurface() == null) { 714 Log.d(TAG, "holder.getSurface() == null"); 715 return; 716 } 717 718 if (mPausing) { 719 // We're pausing, the screen is off and we already stopped 720 // video recording. We don't want to start the camera again 721 // in this case in order to conserve power. 722 // The fact that surfaceChanged is called _after_ an onPause appears 723 // to be legitimate since in that case the lockscreen always returns 724 // to portrait orientation possibly triggering the notification. 725 return; 726 } 727 728 // The mCameraDevice will be null if it is fail to connect to the 729 // camera hardware. In this case we will show a dialog and then 730 // finish the activity, so it's OK to ignore it. 731 if (mCameraDevice == null) return; 732 733 if (mMediaRecorderRecording) { 734 stopVideoRecording(); 735 } 736 737 // Set preview display if the surface is being created. Preview was 738 // already started. 739 if (holder.isCreating()) { 740 setPreviewDisplay(holder); 741 mCameraDevice.unlock(); 742 mHandler.sendEmptyMessage(INIT_RECORDER); 743 } 744 } 745 746 public void surfaceCreated(SurfaceHolder holder) { 747 mSurfaceHolder = holder; 748 } 749 750 public void surfaceDestroyed(SurfaceHolder holder) { 751 mSurfaceHolder = null; 752 } 753 754 private void gotoGallery() { 755 MenuHelper.gotoCameraVideoGallery(this); 756 } 757 758 @Override 759 public boolean onCreateOptionsMenu(Menu menu) { 760 super.onCreateOptionsMenu(menu); 761 762 if (mIsVideoCaptureIntent) { 763 // No options menu for attach mode. 764 return false; 765 } else { 766 addBaseMenuItems(menu); 767 } 768 return true; 769 } 770 771 private boolean isVideoCaptureIntent() { 772 String action = getIntent().getAction(); 773 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 774 } 775 776 private void doReturnToCaller(boolean success) { 777 Intent resultIntent = new Intent(); 778 int resultCode; 779 if (success) { 780 resultCode = RESULT_OK; 781 resultIntent.setData(mCurrentVideoUri); 782 } else { 783 resultCode = RESULT_CANCELED; 784 } 785 setResult(resultCode, resultIntent); 786 finish(); 787 } 788 789 /** 790 * Returns 791 * 792 * @return number of bytes available, or an ERROR code. 793 */ 794 private static long getAvailableStorage() { 795 try { 796 if (!ImageManager.hasStorage()) { 797 return NO_STORAGE_ERROR; 798 } else { 799 String storageDirectory = 800 Environment.getExternalStorageDirectory().toString(); 801 StatFs stat = new StatFs(storageDirectory); 802 return (long) stat.getAvailableBlocks() 803 * (long) stat.getBlockSize(); 804 } 805 } catch (RuntimeException ex) { 806 // if we can't stat the filesystem then we don't know how many 807 // free bytes exist. It might be zero but just leave it 808 // blank since we really don't know. 809 return CANNOT_STAT_ERROR; 810 } 811 } 812 813 private void cleanupEmptyFile() { 814 if (mCameraVideoFilename != null) { 815 File f = new File(mCameraVideoFilename); 816 if (f.length() == 0 && f.delete()) { 817 Log.v(TAG, "Empty video file deleted: " + mCameraVideoFilename); 818 mCameraVideoFilename = null; 819 } 820 } 821 } 822 823 private android.hardware.Camera mCameraDevice; 824 825 // Prepares media recorder. 826 private void initializeRecorder() { 827 Log.v(TAG, "initializeRecorder"); 828 if (mMediaRecorder != null) return; 829 830 // We will call initializeRecorder() again when the alert is hidden. 831 // If the mCameraDevice is null, then this activity is going to finish 832 if (isAlertVisible() || mCameraDevice == null) return; 833 834 Intent intent = getIntent(); 835 Bundle myExtras = intent.getExtras(); 836 837 long requestedSizeLimit = 0; 838 if (mIsVideoCaptureIntent && myExtras != null) { 839 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 840 if (saveUri != null) { 841 try { 842 mCameraVideoFileDescriptor = 843 mContentResolver.openFileDescriptor(saveUri, "rw") 844 .getFileDescriptor(); 845 mCurrentVideoUri = saveUri; 846 } catch (java.io.FileNotFoundException ex) { 847 // invalid uri 848 Log.e(TAG, ex.toString()); 849 } 850 } 851 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 852 } 853 mMediaRecorder = new MediaRecorder(); 854 855 mMediaRecorder.setCamera(mCameraDevice); 856 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 857 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 858 mMediaRecorder.setOutputFormat(mProfile.mOutputFormat); 859 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs); 860 861 // Set output file. 862 if (mStorageStatus != STORAGE_STATUS_OK) { 863 mMediaRecorder.setOutputFile("/dev/null"); 864 } else { 865 // Try Uri in the intent first. If it doesn't exist, use our own 866 // instead. 867 if (mCameraVideoFileDescriptor != null) { 868 mMediaRecorder.setOutputFile(mCameraVideoFileDescriptor); 869 } else { 870 createVideoPath(); 871 mMediaRecorder.setOutputFile(mCameraVideoFilename); 872 } 873 } 874 875 // Use the same frame rate for both, since internally 876 // if the frame rate is too large, it can cause camera to become 877 // unstable. We need to fix the MediaRecorder to disable the support 878 // of setting frame rate for now. 879 mMediaRecorder.setVideoFrameRate(mProfile.mVideoFps); 880 mMediaRecorder.setVideoSize( 881 mProfile.mVideoWidth, mProfile.mVideoHeight); 882 mMediaRecorder.setParameters(String.format( 883 "video-param-encoding-bitrate=%d", mProfile.mVideoBitrate)); 884 mMediaRecorder.setParameters(String.format( 885 "audio-param-encoding-bitrate=%d", mProfile.mAudioBitrate)); 886 mMediaRecorder.setParameters(String.format( 887 "audio-param-number-of-channels=%d", mProfile.mAudioChannels)); 888 mMediaRecorder.setParameters(String.format( 889 "audio-param-sampling-rate=%d", mProfile.mAudioSamplingRate)); 890 mMediaRecorder.setVideoEncoder(mProfile.mVideoEncoder); 891 mMediaRecorder.setAudioEncoder(mProfile.mAudioEncoder); 892 mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); 893 894 // Set maximum file size. 895 // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter 896 // of that to make it more likely that recording can complete 897 // successfully. 898 long maxFileSize = getAvailableStorage() - LOW_STORAGE_THRESHOLD / 4; 899 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 900 maxFileSize = requestedSizeLimit; 901 } 902 903 try { 904 mMediaRecorder.setMaxFileSize(maxFileSize); 905 } catch (RuntimeException exception) { 906 // We are going to ignore failure of setMaxFileSize here, as 907 // a) The composer selected may simply not support it, or 908 // b) The underlying media framework may not handle 64-bit range 909 // on the size restriction. 910 } 911 912 try { 913 mMediaRecorder.prepare(); 914 } catch (IOException e) { 915 Log.e(TAG, "prepare failed for " + mCameraVideoFilename); 916 releaseMediaRecorder(); 917 throw new RuntimeException(e); 918 } 919 mMediaRecorderRecording = false; 920 921 // Update the last video thumbnail. 922 if (!mIsVideoCaptureIntent) { 923 if (!mThumbController.isUriValid()) { 924 updateLastVideo(); 925 } 926 mThumbController.updateDisplayIfNeeded(); 927 } 928 } 929 930 private void releaseMediaRecorder() { 931 Log.v(TAG, "Releasing media recorder."); 932 if (mMediaRecorder != null) { 933 cleanupEmptyFile(); 934 mMediaRecorder.reset(); 935 mMediaRecorder.release(); 936 mMediaRecorder = null; 937 } 938 } 939 940 private int getIntPreference(String key, int defaultValue) { 941 String s = mPreferences.getString(key, ""); 942 int result = defaultValue; 943 try { 944 result = Integer.parseInt(s); 945 } catch (NumberFormatException e) { 946 // Ignore, result is already the default value. 947 } 948 return result; 949 } 950 951 private boolean getBooleanPreference(String key, boolean defaultValue) { 952 return getIntPreference(key, defaultValue ? 1 : 0) != 0; 953 } 954 955 private void createVideoPath() { 956 long dateTaken = System.currentTimeMillis(); 957 String title = createName(dateTaken); 958 String filename = title + ".3gp"; // Used when emailing. 959 String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; 960 String filePath = cameraDirPath + "/" + filename; 961 File cameraDir = new File(cameraDirPath); 962 cameraDir.mkdirs(); 963 ContentValues values = new ContentValues(7); 964 values.put(Video.Media.TITLE, title); 965 values.put(Video.Media.DISPLAY_NAME, filename); 966 values.put(Video.Media.DATE_TAKEN, dateTaken); 967 values.put(Video.Media.MIME_TYPE, "video/3gpp"); 968 values.put(Video.Media.DATA, filePath); 969 mCameraVideoFilename = filePath; 970 Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename); 971 mCurrentVideoValues = values; 972 } 973 974 private void registerVideo() { 975 if (mCameraVideoFileDescriptor == null) { 976 Uri videoTable = Uri.parse("content://media/external/video/media"); 977 mCurrentVideoValues.put(Video.Media.SIZE, 978 new File(mCurrentVideoFilename).length()); 979 try { 980 mCurrentVideoUri = mContentResolver.insert(videoTable, 981 mCurrentVideoValues); 982 } catch (Exception e) { 983 // We failed to insert into the database. This can happen if 984 // the SD card is unmounted. 985 mCurrentVideoUri = null; 986 mCurrentVideoFilename = null; 987 } finally { 988 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 989 } 990 } 991 mCurrentVideoValues = null; 992 } 993 994 private void deleteCurrentVideo() { 995 if (mCurrentVideoFilename != null) { 996 deleteVideoFile(mCurrentVideoFilename); 997 mCurrentVideoFilename = null; 998 } 999 if (mCurrentVideoUri != null) { 1000 mContentResolver.delete(mCurrentVideoUri, null, null); 1001 mCurrentVideoUri = null; 1002 } 1003 updateAndShowStorageHint(true); 1004 } 1005 1006 private void deleteVideoFile(String fileName) { 1007 Log.v(TAG, "Deleting video " + fileName); 1008 File f = new File(fileName); 1009 if (!f.delete()) { 1010 Log.v(TAG, "Could not delete " + fileName); 1011 } 1012 } 1013 1014 private void addBaseMenuItems(Menu menu) { 1015 MenuItem gallery = menu.add(Menu.NONE, Menu.NONE, 1016 MenuHelper.POSITION_GOTO_GALLERY, 1017 R.string.camera_gallery_photos_text) 1018 .setOnMenuItemClickListener( 1019 new OnMenuItemClickListener() { 1020 public boolean onMenuItemClick(MenuItem item) { 1021 gotoGallery(); 1022 return true; 1023 } 1024 }); 1025 gallery.setIcon(android.R.drawable.ic_menu_gallery); 1026 mGalleryItems.add(gallery); 1027 1028 MenuItem item = menu.add(Menu.NONE, Menu.NONE, 1029 MenuHelper.POSITION_CAMERA_SETTING, R.string.settings) 1030 .setOnMenuItemClickListener(new OnMenuItemClickListener() { 1031 public boolean onMenuItemClick(MenuItem item) { 1032 showOnScreenSettings(); 1033 return true; 1034 }}); 1035 item.setIcon(android.R.drawable.ic_menu_preferences); 1036 } 1037 1038 private void showOnScreenSettings() { 1039 if (mSettings == null) { 1040 mSettings = new OnScreenSettings( 1041 findViewById(R.id.camera_preview)); 1042 CameraSettings helper = new CameraSettings(this, mParameters); 1043 PreferenceScreen screen = helper 1044 .getPreferenceScreen(R.xml.video_preferences); 1045 if (mIsVideoCaptureIntent) { 1046 screen = filterPreferenceScreenByIntent(screen); 1047 } 1048 1049 mSettings.setPreferenceScreen(screen); 1050 mSettings.setOnVisibilityChangedListener(this); 1051 } 1052 mSettings.setVisible(true); 1053 } 1054 1055 private PreferenceScreen filterPreferenceScreenByIntent( 1056 PreferenceScreen screen) { 1057 Intent intent = getIntent(); 1058 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 1059 CameraSettings.removePreferenceFromScreen(screen, 1060 CameraSettings.KEY_VIDEO_QUALITY); 1061 } 1062 1063 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 1064 CameraSettings.removePreferenceFromScreen(screen, 1065 CameraSettings.KEY_VIDEO_DURATION); 1066 } 1067 return screen; 1068 } 1069 1070 private class GripperTouchListener implements View.OnTouchListener { 1071 public boolean onTouch(View view, MotionEvent event) { 1072 switch (event.getAction()) { 1073 case MotionEvent.ACTION_DOWN: 1074 return true; 1075 case MotionEvent.ACTION_UP: 1076 showOnScreenSettings(); 1077 return true; 1078 } 1079 return false; 1080 } 1081 } 1082 1083 public void onVisibilityChanged(boolean visible) { 1084 // At this point, we are not recording. 1085 mGripper.setVisibility(visible ? View.INVISIBLE : View.VISIBLE); 1086 if (visible) { 1087 releaseMediaRecorder(); 1088 mPreferences.registerOnSharedPreferenceChangeListener(this); 1089 } else { 1090 // When mSetting is set to invisible in onPause, we should not 1091 // initialize recorder. 1092 if (!mPausing) initializeRecorder(); 1093 mPreferences.unregisterOnSharedPreferenceChangeListener(this); 1094 } 1095 } 1096 1097 // from MediaRecorder.OnErrorListener 1098 public void onError(MediaRecorder mr, int what, int extra) { 1099 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 1100 // We may have run out of space on the sdcard. 1101 stopVideoRecording(); 1102 updateAndShowStorageHint(true); 1103 } 1104 } 1105 1106 // from MediaRecorder.OnInfoListener 1107 public void onInfo(MediaRecorder mr, int what, int extra) { 1108 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1109 if (mMediaRecorderRecording) onStopVideoRecording(); 1110 } else if (what 1111 == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1112 if (mMediaRecorderRecording) onStopVideoRecording(); 1113 1114 // Show the toast. 1115 Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit, 1116 Toast.LENGTH_LONG).show(); 1117 } 1118 } 1119 1120 /* 1121 * Make sure we're not recording music playing in the background, ask the 1122 * MediaPlaybackService to pause playback. 1123 */ 1124 private void pauseAudioPlayback() { 1125 // Shamelessly copied from MediaPlaybackService.java, which 1126 // should be public, but isn't. 1127 Intent i = new Intent("com.android.music.musicservicecommand"); 1128 i.putExtra("command", "pause"); 1129 1130 sendBroadcast(i); 1131 } 1132 1133 private void startVideoRecording() { 1134 Log.v(TAG, "startVideoRecording"); 1135 if (!mMediaRecorderRecording) { 1136 1137 if (mStorageStatus != STORAGE_STATUS_OK) { 1138 Log.v(TAG, "Storage issue, ignore the start request"); 1139 return; 1140 } 1141 1142 // Check mMediaRecorder to see whether it is initialized or not. 1143 if (mMediaRecorder == null) { 1144 Log.e(TAG, "MediaRecorder is not initialized."); 1145 return; 1146 } 1147 1148 pauseAudioPlayback(); 1149 1150 try { 1151 mMediaRecorder.setOnErrorListener(this); 1152 mMediaRecorder.setOnInfoListener(this); 1153 mMediaRecorder.start(); // Recording is now started 1154 } catch (RuntimeException e) { 1155 Log.e(TAG, "Could not start media recorder. ", e); 1156 return; 1157 } 1158 mMediaRecorderRecording = true; 1159 mRecordingStartTime = SystemClock.uptimeMillis(); 1160 updateRecordingIndicator(false); 1161 mRecordingTimeView.setText(""); 1162 mRecordingTimeView.setVisibility(View.VISIBLE); 1163 updateRecordingTime(); 1164 keepScreenOn(); 1165 mGripper.setVisibility(View.INVISIBLE); 1166 } 1167 } 1168 1169 private void updateRecordingIndicator(boolean showRecording) { 1170 int drawableId = 1171 showRecording ? R.drawable.btn_ic_video_record 1172 : R.drawable.btn_ic_video_record_stop; 1173 Drawable drawable = getResources().getDrawable(drawableId); 1174 mShutterButton.setImageDrawable(drawable); 1175 } 1176 1177 private void stopVideoRecordingAndGetThumbnail() { 1178 stopVideoRecording(); 1179 acquireVideoThumb(); 1180 } 1181 1182 private void stopVideoRecordingAndShowAlert() { 1183 stopVideoRecording(); 1184 showAlert(); 1185 } 1186 1187 private void showAlert() { 1188 fadeOut(findViewById(R.id.shutter_button)); 1189 if (mCurrentVideoFilename != null) { 1190 mVideoFrame.setImageBitmap( 1191 ThumbnailUtil.createVideoThumbnail(mCurrentVideoFilename)); 1192 mVideoFrame.setVisibility(View.VISIBLE); 1193 } 1194 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1195 for (int id : pickIds) { 1196 View button = findViewById(id); 1197 fadeIn(((View) button.getParent())); 1198 } 1199 } 1200 1201 private void hideAlert() { 1202 mVideoFrame.setVisibility(View.INVISIBLE); 1203 fadeIn(findViewById(R.id.shutter_button)); 1204 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1205 for (int id : pickIds) { 1206 View button = findViewById(id); 1207 fadeOut(((View) button.getParent())); 1208 } 1209 } 1210 1211 private static void fadeIn(View view) { 1212 view.setVisibility(View.VISIBLE); 1213 Animation animation = new AlphaAnimation(0F, 1F); 1214 animation.setDuration(500); 1215 view.startAnimation(animation); 1216 } 1217 1218 private static void fadeOut(View view) { 1219 view.setVisibility(View.INVISIBLE); 1220 Animation animation = new AlphaAnimation(1F, 0F); 1221 animation.setDuration(500); 1222 view.startAnimation(animation); 1223 } 1224 1225 private boolean isAlertVisible() { 1226 return this.mVideoFrame.getVisibility() == View.VISIBLE; 1227 } 1228 1229 private void stopVideoRecordingAndShowReview() { 1230 stopVideoRecording(); 1231 if (mThumbController.isUriValid()) { 1232 Uri targetUri = mThumbController.getUri(); 1233 Intent intent = new Intent(this, ReviewImage.class); 1234 intent.setData(targetUri); 1235 intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true); 1236 intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true); 1237 intent.putExtra("com.android.camera.ReviewMode", true); 1238 try { 1239 startActivity(intent); 1240 } catch (ActivityNotFoundException ex) { 1241 Log.e(TAG, "review video fail", ex); 1242 } 1243 } else { 1244 Log.e(TAG, "Can't view last video."); 1245 } 1246 } 1247 1248 private void stopVideoRecording() { 1249 Log.v(TAG, "stopVideoRecording"); 1250 boolean needToRegisterRecording = false; 1251 if (mMediaRecorderRecording || mMediaRecorder != null) { 1252 if (mMediaRecorderRecording && mMediaRecorder != null) { 1253 try { 1254 mMediaRecorder.setOnErrorListener(null); 1255 mMediaRecorder.setOnInfoListener(null); 1256 mMediaRecorder.stop(); 1257 } catch (RuntimeException e) { 1258 Log.e(TAG, "stop fail: " + e.getMessage()); 1259 } 1260 1261 mCurrentVideoFilename = mCameraVideoFilename; 1262 Log.v(TAG, "Setting current video filename: " 1263 + mCurrentVideoFilename); 1264 needToRegisterRecording = true; 1265 mMediaRecorderRecording = false; 1266 } 1267 releaseMediaRecorder(); 1268 updateRecordingIndicator(true); 1269 mRecordingTimeView.setVisibility(View.GONE); 1270 keepScreenOnAwhile(); 1271 mGripper.setVisibility(View.VISIBLE); 1272 } 1273 if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) { 1274 registerVideo(); 1275 } 1276 1277 mCameraVideoFilename = null; 1278 mCameraVideoFileDescriptor = null; 1279 } 1280 1281 private void resetScreenOn() { 1282 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1283 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1284 } 1285 1286 private void keepScreenOnAwhile() { 1287 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1288 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1289 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1290 } 1291 1292 private void keepScreenOn() { 1293 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1294 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1295 } 1296 1297 private void hideAlertAndInitializeRecorder() { 1298 hideAlert(); 1299 mHandler.sendEmptyMessage(INIT_RECORDER); 1300 } 1301 1302 private void acquireVideoThumb() { 1303 Bitmap videoFrame = ThumbnailUtil.createVideoThumbnail( 1304 mCurrentVideoFilename); 1305 mThumbController.setData(mCurrentVideoUri, videoFrame); 1306 } 1307 1308 private static ImageManager.DataLocation dataLocation() { 1309 return ImageManager.DataLocation.EXTERNAL; 1310 } 1311 1312 private void updateLastVideo() { 1313 IImageList list = ImageManager.makeImageList( 1314 mContentResolver, 1315 dataLocation(), 1316 ImageManager.INCLUDE_VIDEOS, 1317 ImageManager.SORT_ASCENDING, 1318 ImageManager.CAMERA_IMAGE_BUCKET_ID); 1319 int count = list.getCount(); 1320 if (count > 0) { 1321 IImage image = list.getImageAt(count - 1); 1322 Uri uri = image.fullSizeImageUri(); 1323 mThumbController.setData(uri, image.miniThumbBitmap()); 1324 } else { 1325 mThumbController.setData(null, null); 1326 } 1327 list.close(); 1328 } 1329 1330 private void updateRecordingTime() { 1331 if (!mMediaRecorderRecording) { 1332 return; 1333 } 1334 long now = SystemClock.uptimeMillis(); 1335 long delta = now - mRecordingStartTime; 1336 1337 // Starting a minute before reaching the max duration 1338 // limit, we'll countdown the remaining time instead. 1339 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0 1340 && delta >= mMaxVideoDurationInMs - 60000); 1341 1342 long next_update_delay = 1000 - (delta % 1000); 1343 long seconds; 1344 if (countdownRemainingTime) { 1345 delta = Math.max(0, mMaxVideoDurationInMs - delta); 1346 seconds = (delta + 999) / 1000; 1347 } else { 1348 seconds = delta / 1000; // round to nearest 1349 } 1350 1351 long minutes = seconds / 60; 1352 long hours = minutes / 60; 1353 long remainderMinutes = minutes - (hours * 60); 1354 long remainderSeconds = seconds - (minutes * 60); 1355 1356 String secondsString = Long.toString(remainderSeconds); 1357 if (secondsString.length() < 2) { 1358 secondsString = "0" + secondsString; 1359 } 1360 String minutesString = Long.toString(remainderMinutes); 1361 if (minutesString.length() < 2) { 1362 minutesString = "0" + minutesString; 1363 } 1364 String text = minutesString + ":" + secondsString; 1365 if (hours > 0) { 1366 String hoursString = Long.toString(hours); 1367 if (hoursString.length() < 2) { 1368 hoursString = "0" + hoursString; 1369 } 1370 text = hoursString + ":" + text; 1371 } 1372 mRecordingTimeView.setText(text); 1373 1374 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1375 // Avoid setting the color on every update, do it only 1376 // when it needs changing. 1377 mRecordingTimeCountsDown = countdownRemainingTime; 1378 1379 int color = getResources().getColor(countdownRemainingTime 1380 ? R.color.recording_time_remaining_text 1381 : R.color.recording_time_elapsed_text); 1382 1383 mRecordingTimeView.setTextColor(color); 1384 } 1385 1386 mHandler.sendEmptyMessageDelayed( 1387 UPDATE_RECORD_TIME, next_update_delay); 1388 } 1389 1390 private static boolean isSupported(String value, List<String> supported) { 1391 return supported == null ? false : supported.indexOf(value) >= 0; 1392 } 1393 1394 private void setCameraParameters() { 1395 mParameters = mCameraDevice.getParameters(); 1396 1397 mParameters.setPreviewSize(mProfile.mVideoWidth, mProfile.mVideoHeight); 1398 mParameters.setPreviewFrameRate(mProfile.mVideoFps); 1399 1400 // Set flash mode. 1401 String flashMode = mPreferences.getString( 1402 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 1403 getString(R.string.pref_camera_video_flashmode_default)); 1404 List<String> supportedFlash = mParameters.getSupportedFlashModes(); 1405 if (isSupported(flashMode, supportedFlash)) { 1406 mParameters.setFlashMode(flashMode); 1407 } else { 1408 flashMode = mParameters.getFlashMode(); 1409 if (flashMode == null) { 1410 flashMode = getString( 1411 R.string.pref_camera_flashmode_no_flash); 1412 } 1413 } 1414 1415 // Set white balance parameter. 1416 String whiteBalance = mPreferences.getString( 1417 CameraSettings.KEY_WHITE_BALANCE, 1418 getString(R.string.pref_camera_whitebalance_default)); 1419 if (isSupported(whiteBalance, 1420 mParameters.getSupportedWhiteBalance())) { 1421 mParameters.setWhiteBalance(whiteBalance); 1422 } else { 1423 whiteBalance = mParameters.getWhiteBalance(); 1424 if (whiteBalance == null) { 1425 whiteBalance = Parameters.WHITE_BALANCE_AUTO; 1426 } 1427 } 1428 1429 // Set color effect parameter. 1430 String colorEffect = mPreferences.getString( 1431 CameraSettings.KEY_COLOR_EFFECT, 1432 getString(R.string.pref_camera_coloreffect_default)); 1433 if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) { 1434 mParameters.setColorEffect(colorEffect); 1435 } 1436 1437 mCameraDevice.setParameters(mParameters); 1438 1439 final String finalWhiteBalance = whiteBalance; 1440 final String finalFlashMode = flashMode; 1441 1442 // It can be execute from the startPreview thread, so we post it 1443 // to the main UI thread 1444 mHandler.post(new Runnable() { 1445 public void run() { 1446 mWhitebalanceIndicator.setMode(finalWhiteBalance); 1447 mFlashIndicator.setMode(finalFlashMode); 1448 } 1449 }); 1450 } 1451 1452 public boolean onSwitchChanged(Switcher source, boolean onOff) { 1453 if (onOff == SWITCH_CAMERA) { 1454 if (mMediaRecorderRecording) return false; 1455 MenuHelper.gotoCameraMode(this); 1456 finish(); 1457 } 1458 return true; 1459 } 1460 1461 public void onSharedPreferenceChanged( 1462 SharedPreferences preferences, String key) { 1463 // ignore the events after "onPause()" or preview has not started yet 1464 if (mPausing) return; 1465 1466 if (CameraSettings.KEY_VIDEO_DURATION.equals(key) 1467 || CameraSettings.KEY_VIDEO_QUALITY.equals(key)) { 1468 readVideoPreferences(); 1469 } 1470 1471 // If mCameraDevice is not ready then we can set the parameter in 1472 // startPreview(). 1473 if (mCameraDevice == null) return; 1474 1475 // We need to restart the preview if preview size is changed. 1476 Size size = mParameters.getPreviewSize(); 1477 if (size.width != mProfile.mVideoWidth 1478 || size.height != mProfile.mVideoHeight) { 1479 // It is assumed media recorder is released before 1480 // onSharedPreferenceChanged, so we can close the camera here. 1481 closeCamera(); 1482 try { 1483 resizeForPreviewAspectRatio(); 1484 startPreview(); // Parameters will be set in startPreview(). 1485 } catch (CameraHardwareException e) { 1486 showCameraBusyAndFinish(); 1487 } 1488 } else { 1489 try { 1490 // We need to lock the camera before writing parameters. 1491 mCameraDevice.lock(); 1492 } catch (RuntimeException e) { 1493 // When preferences are added for the first time, this method 1494 // will be called. But OnScreenSetting is not displayed yet and 1495 // media recorder still owns the camera. Lock will fail and we 1496 // just ignore it. 1497 return; 1498 } 1499 setCameraParameters(); 1500 mCameraDevice.unlock(); 1501 } 1502 } 1503 1504 public void onSizeChanged() { 1505 if (mSettings != null) { 1506 mSettings.updateLayout(); 1507 } 1508 1509 } 1510} 1511 1512// 1513// DefaultHashMap is a HashMap which returns a default value if the specified 1514// key is not found. 1515// 1516@SuppressWarnings("serial") 1517class DefaultHashMap<K, V> extends HashMap<K, V> { 1518 private V mDefaultValue; 1519 1520 public void putDefault(V defaultValue) { 1521 mDefaultValue = defaultValue; 1522 } 1523 1524 @Override 1525 public V get(Object key) { 1526 V value = super.get(key); 1527 return (value == null) ? mDefaultValue : value; 1528 } 1529} 1530 1531// 1532// MediaRecorderProfile reads from system properties to determine the proper 1533// values for various parameters for MediaRecorder. 1534// 1535class MediaRecorderProfile { 1536 1537 @SuppressWarnings("unused") 1538 private static final String TAG = "MediaRecorderProfile"; 1539 public final boolean mHiQuality; 1540 public final int mOutputFormat; 1541 public final int mVideoEncoder; 1542 public final int mAudioEncoder; 1543 public final int mVideoWidth; 1544 public final int mVideoHeight; 1545 public final int mVideoFps; 1546 public final int mVideoBitrate; 1547 public final int mAudioBitrate; 1548 public final int mAudioChannels; 1549 public final int mAudioSamplingRate; 1550 1551 MediaRecorderProfile(boolean hiQuality) { 1552 mHiQuality = hiQuality; 1553 1554 mOutputFormat = getFromTable("ro.media.enc.hprof.file.format", 1555 "ro.media.enc.lprof.file.format", 1556 OUTPUT_FORMAT_TABLE); 1557 1558 mVideoEncoder = getFromTable("ro.media.enc.hprof.codec.vid", 1559 "ro.media.enc.lprof.codec.vid", 1560 VIDEO_ENCODER_TABLE); 1561 1562 mAudioEncoder = getFromTable("ro.media.enc.hprof.codec.aud", 1563 "ro.media.enc.lprof.codec.aud", 1564 AUDIO_ENCODER_TABLE); 1565 1566 mVideoWidth = getInt("ro.media.enc.hprof.vid.width", 1567 "ro.media.enc.lprof.vid.width", 1568 352, 176); 1569 1570 mVideoHeight = getInt("ro.media.enc.hprof.vid.height", 1571 "ro.media.enc.lprof.vid.height", 1572 288, 144); 1573 1574 mVideoFps = getInt("ro.media.enc.hprof.vid.fps", 1575 "ro.media.enc.lprof.vid.fps", 1576 20, 20); 1577 1578 mVideoBitrate = getInt("ro.media.enc.hprof.vid.bps", 1579 "ro.media.enc.lprof.vid.bps", 1580 360000, 192000); 1581 1582 mAudioBitrate = getInt("ro.media.enc.hprof.aud.bps", 1583 "ro.media.enc.lprof.aud.bps", 1584 23450, 23450); 1585 1586 mAudioChannels = getInt("ro.media.enc.hprof.aud.ch", 1587 "ro.media.enc.lprof.aud.ch", 1588 1, 1); 1589 1590 mAudioSamplingRate = getInt("ro.media.enc.hprof.aud.hz", 1591 "ro.media.enc.lprof.aud.hz", 1592 8000, 8000); 1593 } 1594 1595 private int getFromTable(String highKey, String lowKey, 1596 DefaultHashMap<String, Integer> table) { 1597 String s; 1598 s = SystemProperties.get(mHiQuality ? highKey : lowKey); 1599 return table.get(s); 1600 } 1601 1602 private int getInt(String highKey, String lowKey, int highDefault, 1603 int lowDefault) { 1604 String key = mHiQuality ? highKey : lowKey; 1605 int defaultValue = mHiQuality ? highDefault : lowDefault; 1606 return SystemProperties.getInt(key, defaultValue); 1607 } 1608 1609 private static final DefaultHashMap<String, Integer> 1610 OUTPUT_FORMAT_TABLE = new DefaultHashMap<String, Integer>(); 1611 private static final DefaultHashMap<String, Integer> 1612 VIDEO_ENCODER_TABLE = new DefaultHashMap<String, Integer>(); 1613 private static final DefaultHashMap<String, Integer> 1614 AUDIO_ENCODER_TABLE = new DefaultHashMap<String, Integer>(); 1615 1616 static { 1617 OUTPUT_FORMAT_TABLE.put("3gp", MediaRecorder.OutputFormat.THREE_GPP); 1618 OUTPUT_FORMAT_TABLE.put("mp4", MediaRecorder.OutputFormat.MPEG_4); 1619 OUTPUT_FORMAT_TABLE.putDefault(MediaRecorder.OutputFormat.DEFAULT); 1620 1621 VIDEO_ENCODER_TABLE.put("h263", MediaRecorder.VideoEncoder.H263); 1622 VIDEO_ENCODER_TABLE.put("h264", MediaRecorder.VideoEncoder.H264); 1623 VIDEO_ENCODER_TABLE.put("m4v", MediaRecorder.VideoEncoder.MPEG_4_SP); 1624 VIDEO_ENCODER_TABLE.putDefault(MediaRecorder.VideoEncoder.DEFAULT); 1625 1626 AUDIO_ENCODER_TABLE.put("amrnb", MediaRecorder.AudioEncoder.AMR_NB); 1627 AUDIO_ENCODER_TABLE.put("amrwb", MediaRecorder.AudioEncoder.AMR_WB); 1628 AUDIO_ENCODER_TABLE.put("aac", MediaRecorder.AudioEncoder.AAC); 1629 AUDIO_ENCODER_TABLE.put("aacplus", MediaRecorder.AudioEncoder.AAC_PLUS); 1630 AUDIO_ENCODER_TABLE.put("eaacplus", 1631 MediaRecorder.AudioEncoder.EAAC_PLUS); 1632 AUDIO_ENCODER_TABLE.putDefault(MediaRecorder.AudioEncoder.DEFAULT); 1633 } 1634} 1635