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