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