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