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