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