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