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