VideoCamera.java revision b7957a87172eee9a34074bc6aec3a993dee96781
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 com.android.camera.gallery.IImage; 20import com.android.camera.gallery.IImageList; 21import com.android.camera.ui.CamcorderHeadUpDisplay; 22import com.android.camera.ui.GLRootView; 23import com.android.camera.ui.GLView; 24import com.android.camera.ui.HeadUpDisplay; 25 26import android.content.ActivityNotFoundException; 27import android.content.BroadcastReceiver; 28import android.content.ContentResolver; 29import android.content.ContentValues; 30import android.content.Context; 31import android.content.Intent; 32import android.content.IntentFilter; 33import android.content.SharedPreferences; 34import android.content.res.Resources; 35import android.graphics.Bitmap; 36import android.graphics.drawable.Drawable; 37import android.hardware.Camera.Parameters; 38import android.hardware.Camera.Size; 39import android.media.CamcorderProfile; 40import android.media.MediaRecorder; 41import android.media.ThumbnailUtils; 42import android.net.Uri; 43import android.os.Build; 44import android.os.Bundle; 45import android.os.Environment; 46import android.os.Handler; 47import android.os.Message; 48import android.os.StatFs; 49import android.os.SystemClock; 50import android.preference.PreferenceManager; 51import android.provider.MediaStore; 52import android.provider.Settings; 53import android.provider.MediaStore.Video; 54import android.util.Log; 55import android.view.KeyEvent; 56import android.view.LayoutInflater; 57import android.view.Menu; 58import android.view.MenuItem; 59import android.view.SurfaceHolder; 60import android.view.SurfaceView; 61import android.view.View; 62import android.view.ViewGroup; 63import android.view.Window; 64import android.view.WindowManager; 65import android.view.MenuItem.OnMenuItemClickListener; 66import android.view.animation.AlphaAnimation; 67import android.view.animation.Animation; 68import android.widget.ImageView; 69import android.widget.TextView; 70import android.widget.Toast; 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 (mHeadUpDisplay.collapse()) return; 437 438 if (mMediaRecorderRecording) { 439 onStopVideoRecording(true); 440 } else if (mMediaRecorder != null) { 441 // If the click comes before recorder initialization, it is 442 // ignored. If users click the button during initialization, 443 // the event is put in the queue and record will be started 444 // eventually. 445 startVideoRecording(); 446 } 447 mShutterButton.setEnabled(false); 448 mHandler.sendEmptyMessageDelayed( 449 ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT); 450 break; 451 } 452 } 453 454 private void discardCurrentVideoAndInitRecorder() { 455 deleteCurrentVideo(); 456 hideAlertAndInitializeRecorder(); 457 } 458 459 private OnScreenHint mStorageHint; 460 461 private void updateAndShowStorageHint(boolean mayHaveSd) { 462 mStorageStatus = getStorageStatus(mayHaveSd); 463 showStorageHint(); 464 } 465 466 private void showStorageHint() { 467 String errorMessage = null; 468 switch (mStorageStatus) { 469 case STORAGE_STATUS_NONE: 470 errorMessage = getString(R.string.no_storage); 471 break; 472 case STORAGE_STATUS_LOW: 473 errorMessage = getString(R.string.spaceIsLow_content); 474 } 475 if (errorMessage != null) { 476 if (mStorageHint == null) { 477 mStorageHint = OnScreenHint.makeText(this, errorMessage); 478 } else { 479 mStorageHint.setText(errorMessage); 480 } 481 mStorageHint.show(); 482 } else if (mStorageHint != null) { 483 mStorageHint.cancel(); 484 mStorageHint = null; 485 } 486 } 487 488 private int getStorageStatus(boolean mayHaveSd) { 489 long remaining = mayHaveSd ? getAvailableStorage() : NO_STORAGE_ERROR; 490 if (remaining == NO_STORAGE_ERROR) { 491 return STORAGE_STATUS_NONE; 492 } 493 return remaining < LOW_STORAGE_THRESHOLD 494 ? STORAGE_STATUS_LOW 495 : STORAGE_STATUS_OK; 496 } 497 498 private void readVideoPreferences() { 499 String quality = mPreferences.getString( 500 CameraSettings.KEY_VIDEO_QUALITY, 501 CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); 502 503 boolean videoQualityHigh = CameraSettings.getVideoQuality(quality); 504 505 // Set video quality. 506 Intent intent = getIntent(); 507 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 508 int extraVideoQuality = 509 intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 510 videoQualityHigh = (extraVideoQuality > 0); 511 } 512 513 // Set video duration limit. The limit is read from the preference, 514 // unless it is specified in the intent. 515 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 516 int seconds = 517 intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0); 518 mMaxVideoDurationInMs = 1000 * seconds; 519 } else { 520 mMaxVideoDurationInMs = 521 CameraSettings.getVidoeDurationInMillis(quality); 522 } 523 mProfile = CamcorderProfile.get(videoQualityHigh 524 ? CamcorderProfile.QUALITY_HIGH 525 : CamcorderProfile.QUALITY_LOW); 526 } 527 528 private void resizeForPreviewAspectRatio() { 529 if (mGLRootView != null) mGLRootView.aboutToChangeSize(); 530 mPreviewFrameLayout.setAspectRatio( 531 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 532 } 533 534 @Override 535 public void onResume() { 536 super.onResume(); 537 mPausing = false; 538 mGLRootView.onResume(); 539 540 mVideoPreview.setVisibility(View.VISIBLE); 541 readVideoPreferences(); 542 resizeForPreviewAspectRatio(); 543 if (!mPreviewing && !mStartPreviewFail) { 544 try { 545 startPreview(); 546 } catch (CameraHardwareException e) { 547 showCameraBusyAndFinish(); 548 return; 549 } 550 } 551 keepScreenOnAwhile(); 552 553 // install an intent filter to receive SD card related events. 554 IntentFilter intentFilter = 555 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 556 intentFilter.addAction(Intent.ACTION_MEDIA_EJECT); 557 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 558 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 559 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 560 intentFilter.addDataScheme("file"); 561 mReceiver = new MyBroadcastReceiver(); 562 registerReceiver(mReceiver, intentFilter); 563 mStorageStatus = getStorageStatus(true); 564 565 mHandler.postDelayed(new Runnable() { 566 public void run() { 567 showStorageHint(); 568 } 569 }, 200); 570 571 if (mSurfaceHolder != null) { 572 mHandler.sendEmptyMessage(INIT_RECORDER); 573 } 574 } 575 576 private void setPreviewDisplay(SurfaceHolder holder) { 577 try { 578 mCameraDevice.setPreviewDisplay(holder); 579 } catch (Throwable ex) { 580 closeCamera(); 581 throw new RuntimeException("setPreviewDisplay failed", ex); 582 } 583 } 584 585 private void startPreview() throws CameraHardwareException { 586 Log.v(TAG, "startPreview"); 587 if (mPreviewing) { 588 // After recording a video, preview is not stopped. So just return. 589 return; 590 } 591 592 if (mCameraDevice == null) { 593 // If the activity is paused and resumed, camera device has been 594 // released and we need to open the camera. 595 mCameraDevice = CameraHolder.instance().open(); 596 } 597 598 mCameraDevice.lock(); 599 setCameraParameters(); 600 setPreviewDisplay(mSurfaceHolder); 601 602 try { 603 mCameraDevice.startPreview(); 604 mPreviewing = true; 605 } catch (Throwable ex) { 606 closeCamera(); 607 throw new RuntimeException("startPreview failed", ex); 608 } 609 610 // If setPreviewDisplay has been set with a valid surface, unlock now. 611 // If surface is null, unlock later. Otherwise, setPreviewDisplay in 612 // surfaceChanged will fail. 613 if (mSurfaceHolder != null) { 614 mCameraDevice.unlock(); 615 } 616 } 617 618 private void closeCamera() { 619 Log.v(TAG, "closeCamera"); 620 if (mCameraDevice == null) { 621 Log.d(TAG, "already stopped."); 622 return; 623 } 624 // If we don't lock the camera, release() will fail. 625 mCameraDevice.lock(); 626 CameraHolder.instance().release(); 627 mCameraDevice = null; 628 mPreviewing = false; 629 } 630 631 @Override 632 protected void onPause() { 633 super.onPause(); 634 mPausing = true; 635 636 if (!isFinishing()) { 637 mGLRootView.onPause(); 638 if (mHeadUpDisplay != null) mHeadUpDisplay.collapse(); 639 } 640 641 // Hide the preview now. Otherwise, the preview may be rotated during 642 // onPause and it is annoying to users. 643 mVideoPreview.setVisibility(View.INVISIBLE); 644 645 // This is similar to what mShutterButton.performClick() does, 646 // but not quite the same. 647 if (mMediaRecorderRecording) { 648 if (mIsVideoCaptureIntent) { 649 stopVideoRecording(); 650 showAlert(); 651 } else { 652 stopVideoRecordingAndGetThumbnail(); 653 } 654 } else { 655 stopVideoRecording(); 656 } 657 closeCamera(); 658 659 if (mReceiver != null) { 660 unregisterReceiver(mReceiver); 661 mReceiver = null; 662 } 663 resetScreenOn(); 664 665 if (!mIsVideoCaptureIntent) { 666 mThumbController.storeData(ImageManager.getLastVideoThumbPath()); 667 } 668 669 if (mStorageHint != null) { 670 mStorageHint.cancel(); 671 mStorageHint = null; 672 } 673 674 mHandler.removeMessages(INIT_RECORDER); 675 } 676 677 @Override 678 public void onUserInteraction() { 679 super.onUserInteraction(); 680 if (!mMediaRecorderRecording) keepScreenOnAwhile(); 681 } 682 683 @Override 684 public void onBackPressed() { 685 if (mPausing) return; 686 if (mMediaRecorderRecording) { 687 onStopVideoRecording(false); 688 } else if (mHeadUpDisplay == null || !mHeadUpDisplay.collapse()) { 689 super.onBackPressed(); 690 } 691 } 692 693 @Override 694 public boolean onKeyDown(int keyCode, KeyEvent event) { 695 // Do not handle any key if the activity is paused. 696 if (mPausing) { 697 return true; 698 } 699 700 switch (keyCode) { 701 case KeyEvent.KEYCODE_CAMERA: 702 if (event.getRepeatCount() == 0) { 703 mShutterButton.performClick(); 704 return true; 705 } 706 break; 707 case KeyEvent.KEYCODE_DPAD_CENTER: 708 if (event.getRepeatCount() == 0) { 709 mShutterButton.performClick(); 710 return true; 711 } 712 break; 713 case KeyEvent.KEYCODE_MENU: 714 if (mMediaRecorderRecording) { 715 onStopVideoRecording(true); 716 return true; 717 } 718 break; 719 } 720 721 return super.onKeyDown(keyCode, event); 722 } 723 724 @Override 725 public boolean onKeyUp(int keyCode, KeyEvent event) { 726 switch (keyCode) { 727 case KeyEvent.KEYCODE_CAMERA: 728 mShutterButton.setPressed(false); 729 return true; 730 } 731 return super.onKeyUp(keyCode, event); 732 } 733 734 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 735 // Make sure we have a surface in the holder before proceeding. 736 if (holder.getSurface() == null) { 737 Log.d(TAG, "holder.getSurface() == null"); 738 return; 739 } 740 741 if (mPausing) { 742 // We're pausing, the screen is off and we already stopped 743 // video recording. We don't want to start the camera again 744 // in this case in order to conserve power. 745 // The fact that surfaceChanged is called _after_ an onPause appears 746 // to be legitimate since in that case the lockscreen always returns 747 // to portrait orientation possibly triggering the notification. 748 return; 749 } 750 751 // The mCameraDevice will be null if it is fail to connect to the 752 // camera hardware. In this case we will show a dialog and then 753 // finish the activity, so it's OK to ignore it. 754 if (mCameraDevice == null) return; 755 756 if (mMediaRecorderRecording) { 757 stopVideoRecording(); 758 } 759 760 // Set preview display if the surface is being created. Preview was 761 // already started. 762 if (holder.isCreating()) { 763 setPreviewDisplay(holder); 764 mCameraDevice.unlock(); 765 mHandler.sendEmptyMessage(INIT_RECORDER); 766 } 767 } 768 769 public void surfaceCreated(SurfaceHolder holder) { 770 mSurfaceHolder = holder; 771 } 772 773 public void surfaceDestroyed(SurfaceHolder holder) { 774 mSurfaceHolder = null; 775 } 776 777 private void gotoGallery() { 778 MenuHelper.gotoCameraVideoGallery(this); 779 } 780 781 @Override 782 public boolean onCreateOptionsMenu(Menu menu) { 783 super.onCreateOptionsMenu(menu); 784 785 if (mIsVideoCaptureIntent) { 786 // No options menu for attach mode. 787 return false; 788 } else { 789 addBaseMenuItems(menu); 790 } 791 return true; 792 } 793 794 private boolean isVideoCaptureIntent() { 795 String action = getIntent().getAction(); 796 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 797 } 798 799 private void doReturnToCaller(boolean valid) { 800 Intent resultIntent = new Intent(); 801 int resultCode; 802 if (valid) { 803 resultCode = RESULT_OK; 804 resultIntent.setData(mCurrentVideoUri); 805 } else { 806 resultCode = RESULT_CANCELED; 807 } 808 setResult(resultCode, resultIntent); 809 finish(); 810 } 811 812 /** 813 * Returns 814 * 815 * @return number of bytes available, or an ERROR code. 816 */ 817 private static long getAvailableStorage() { 818 try { 819 if (!ImageManager.hasStorage()) { 820 return NO_STORAGE_ERROR; 821 } else { 822 String storageDirectory = 823 Environment.getExternalStorageDirectory().toString(); 824 StatFs stat = new StatFs(storageDirectory); 825 return (long) stat.getAvailableBlocks() 826 * (long) stat.getBlockSize(); 827 } 828 } catch (RuntimeException ex) { 829 // if we can't stat the filesystem then we don't know how many 830 // free bytes exist. It might be zero but just leave it 831 // blank since we really don't know. 832 return CANNOT_STAT_ERROR; 833 } 834 } 835 836 private void cleanupEmptyFile() { 837 if (mCameraVideoFilename != null) { 838 File f = new File(mCameraVideoFilename); 839 if (f.length() == 0 && f.delete()) { 840 Log.v(TAG, "Empty video file deleted: " + mCameraVideoFilename); 841 mCameraVideoFilename = null; 842 } 843 } 844 } 845 846 private android.hardware.Camera mCameraDevice; 847 848 // Prepares media recorder. 849 private void initializeRecorder() { 850 Log.v(TAG, "initializeRecorder"); 851 if (mMediaRecorder != null) return; 852 853 // We will call initializeRecorder() again when the alert is hidden. 854 // If the mCameraDevice is null, then this activity is going to finish 855 if (isAlertVisible() || mCameraDevice == null) return; 856 857 Intent intent = getIntent(); 858 Bundle myExtras = intent.getExtras(); 859 860 long requestedSizeLimit = 0; 861 if (mIsVideoCaptureIntent && myExtras != null) { 862 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 863 if (saveUri != null) { 864 try { 865 mCameraVideoFileDescriptor = 866 mContentResolver.openFileDescriptor(saveUri, "rw") 867 .getFileDescriptor(); 868 mCurrentVideoUri = saveUri; 869 } catch (java.io.FileNotFoundException ex) { 870 // invalid uri 871 Log.e(TAG, ex.toString()); 872 } 873 } 874 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT); 875 } 876 mMediaRecorder = new MediaRecorder(); 877 878 mMediaRecorder.setCamera(mCameraDevice); 879 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 880 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 881 mMediaRecorder.setProfile(mProfile); 882 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs); 883 884 // Set output file. 885 if (mStorageStatus != STORAGE_STATUS_OK) { 886 mMediaRecorder.setOutputFile("/dev/null"); 887 } else { 888 // Try Uri in the intent first. If it doesn't exist, use our own 889 // instead. 890 if (mCameraVideoFileDescriptor != null) { 891 mMediaRecorder.setOutputFile(mCameraVideoFileDescriptor); 892 } else { 893 createVideoPath(); 894 mMediaRecorder.setOutputFile(mCameraVideoFilename); 895 } 896 } 897 898 mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); 899 900 // Set maximum file size. 901 // remaining >= LOW_STORAGE_THRESHOLD at this point, reserve a quarter 902 // of that to make it more likely that recording can complete 903 // successfully. 904 long maxFileSize = getAvailableStorage() - LOW_STORAGE_THRESHOLD / 4; 905 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) { 906 maxFileSize = requestedSizeLimit; 907 } 908 909 try { 910 mMediaRecorder.setMaxFileSize(maxFileSize); 911 } catch (RuntimeException exception) { 912 // We are going to ignore failure of setMaxFileSize here, as 913 // a) The composer selected may simply not support it, or 914 // b) The underlying media framework may not handle 64-bit range 915 // on the size restriction. 916 } 917 918 try { 919 mMediaRecorder.prepare(); 920 } catch (IOException e) { 921 Log.e(TAG, "prepare failed for " + mCameraVideoFilename); 922 releaseMediaRecorder(); 923 throw new RuntimeException(e); 924 } 925 mMediaRecorderRecording = false; 926 927 // Update the last video thumbnail. 928 if (!mIsVideoCaptureIntent) { 929 if (!mThumbController.isUriValid()) { 930 updateLastVideo(); 931 } 932 mThumbController.updateDisplayIfNeeded(); 933 } 934 } 935 936 private void releaseMediaRecorder() { 937 Log.v(TAG, "Releasing media recorder."); 938 if (mMediaRecorder != null) { 939 cleanupEmptyFile(); 940 mMediaRecorder.reset(); 941 mMediaRecorder.release(); 942 mMediaRecorder = null; 943 } 944 } 945 946 private void createVideoPath() { 947 long dateTaken = System.currentTimeMillis(); 948 String title = createName(dateTaken); 949 String filename = title + ".3gp"; // Used when emailing. 950 String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; 951 String filePath = cameraDirPath + "/" + filename; 952 File cameraDir = new File(cameraDirPath); 953 cameraDir.mkdirs(); 954 ContentValues values = new ContentValues(7); 955 values.put(Video.Media.TITLE, title); 956 values.put(Video.Media.DISPLAY_NAME, filename); 957 values.put(Video.Media.DATE_TAKEN, dateTaken); 958 values.put(Video.Media.MIME_TYPE, "video/3gpp"); 959 values.put(Video.Media.DATA, filePath); 960 mCameraVideoFilename = filePath; 961 Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename); 962 mCurrentVideoValues = values; 963 } 964 965 private void registerVideo() { 966 if (mCameraVideoFileDescriptor == null) { 967 Uri videoTable = Uri.parse("content://media/external/video/media"); 968 mCurrentVideoValues.put(Video.Media.SIZE, 969 new File(mCurrentVideoFilename).length()); 970 try { 971 mCurrentVideoUri = mContentResolver.insert(videoTable, 972 mCurrentVideoValues); 973 } catch (Exception e) { 974 // We failed to insert into the database. This can happen if 975 // the SD card is unmounted. 976 mCurrentVideoUri = null; 977 mCurrentVideoFilename = null; 978 } finally { 979 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 980 } 981 } 982 mCurrentVideoValues = null; 983 } 984 985 private void deleteCurrentVideo() { 986 if (mCurrentVideoFilename != null) { 987 deleteVideoFile(mCurrentVideoFilename); 988 mCurrentVideoFilename = null; 989 } 990 if (mCurrentVideoUri != null) { 991 mContentResolver.delete(mCurrentVideoUri, null, null); 992 mCurrentVideoUri = null; 993 } 994 updateAndShowStorageHint(true); 995 } 996 997 private void deleteVideoFile(String fileName) { 998 Log.v(TAG, "Deleting video " + fileName); 999 File f = new File(fileName); 1000 if (!f.delete()) { 1001 Log.v(TAG, "Could not delete " + fileName); 1002 } 1003 } 1004 1005 private void addBaseMenuItems(Menu menu) { 1006 MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() { 1007 public void run() { 1008 switchToCameraMode(); 1009 } 1010 }); 1011 MenuItem gallery = menu.add(Menu.NONE, Menu.NONE, 1012 MenuHelper.POSITION_GOTO_GALLERY, 1013 R.string.camera_gallery_photos_text) 1014 .setOnMenuItemClickListener( 1015 new OnMenuItemClickListener() { 1016 public boolean onMenuItemClick(MenuItem item) { 1017 gotoGallery(); 1018 return true; 1019 } 1020 }); 1021 gallery.setIcon(android.R.drawable.ic_menu_gallery); 1022 mGalleryItems.add(gallery); 1023 } 1024 1025 private PreferenceGroup filterPreferenceScreenByIntent( 1026 PreferenceGroup screen) { 1027 Intent intent = getIntent(); 1028 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 1029 CameraSettings.removePreferenceFromScreen(screen, 1030 CameraSettings.KEY_VIDEO_QUALITY); 1031 } 1032 1033 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 1034 CameraSettings.removePreferenceFromScreen(screen, 1035 CameraSettings.KEY_VIDEO_QUALITY); 1036 } 1037 return screen; 1038 } 1039 1040 // from MediaRecorder.OnErrorListener 1041 public void onError(MediaRecorder mr, int what, int extra) { 1042 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 1043 // We may have run out of space on the sdcard. 1044 stopVideoRecording(); 1045 updateAndShowStorageHint(true); 1046 } 1047 } 1048 1049 // from MediaRecorder.OnInfoListener 1050 public void onInfo(MediaRecorder mr, int what, int extra) { 1051 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1052 if (mMediaRecorderRecording) onStopVideoRecording(true); 1053 } else if (what 1054 == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1055 if (mMediaRecorderRecording) onStopVideoRecording(true); 1056 1057 // Show the toast. 1058 Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit, 1059 Toast.LENGTH_LONG).show(); 1060 } 1061 } 1062 1063 /* 1064 * Make sure we're not recording music playing in the background, ask the 1065 * MediaPlaybackService to pause playback. 1066 */ 1067 private void pauseAudioPlayback() { 1068 // Shamelessly copied from MediaPlaybackService.java, which 1069 // should be public, but isn't. 1070 Intent i = new Intent("com.android.music.musicservicecommand"); 1071 i.putExtra("command", "pause"); 1072 1073 sendBroadcast(i); 1074 } 1075 1076 private void startVideoRecording() { 1077 Log.v(TAG, "startVideoRecording"); 1078 if (!mMediaRecorderRecording) { 1079 1080 if (mStorageStatus != STORAGE_STATUS_OK) { 1081 Log.v(TAG, "Storage issue, ignore the start request"); 1082 return; 1083 } 1084 1085 // Check mMediaRecorder to see whether it is initialized or not. 1086 if (mMediaRecorder == null) { 1087 Log.e(TAG, "MediaRecorder is not initialized."); 1088 return; 1089 } 1090 1091 pauseAudioPlayback(); 1092 1093 try { 1094 mMediaRecorder.setOnErrorListener(this); 1095 mMediaRecorder.setOnInfoListener(this); 1096 mMediaRecorder.start(); // Recording is now started 1097 } catch (RuntimeException e) { 1098 Log.e(TAG, "Could not start media recorder. ", e); 1099 return; 1100 } 1101 mHeadUpDisplay.setEnabled(false); 1102 1103 mMediaRecorderRecording = true; 1104 mRecordingStartTime = SystemClock.uptimeMillis(); 1105 updateRecordingIndicator(false); 1106 mRecordingTimeView.setText(""); 1107 mRecordingTimeView.setVisibility(View.VISIBLE); 1108 updateRecordingTime(); 1109 keepScreenOn(); 1110 } 1111 } 1112 1113 private void updateRecordingIndicator(boolean showRecording) { 1114 int drawableId = 1115 showRecording ? R.drawable.btn_ic_video_record 1116 : R.drawable.btn_ic_video_record_stop; 1117 Drawable drawable = getResources().getDrawable(drawableId); 1118 mShutterButton.setImageDrawable(drawable); 1119 } 1120 1121 private void stopVideoRecordingAndGetThumbnail() { 1122 stopVideoRecording(); 1123 acquireVideoThumb(); 1124 } 1125 1126 private void stopVideoRecordingAndReturn(boolean valid) { 1127 stopVideoRecording(); 1128 doReturnToCaller(valid); 1129 } 1130 1131 private void stopVideoRecordingAndShowAlert() { 1132 stopVideoRecording(); 1133 showAlert(); 1134 } 1135 1136 private void showAlert() { 1137 fadeOut(findViewById(R.id.shutter_button)); 1138 if (mCurrentVideoFilename != null) { 1139 mVideoFrame.setImageBitmap( 1140 ThumbnailUtils.createVideoThumbnail(mCurrentVideoFilename, 1141 Video.Thumbnails.MINI_KIND)); 1142 mVideoFrame.setVisibility(View.VISIBLE); 1143 } 1144 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1145 for (int id : pickIds) { 1146 View button = findViewById(id); 1147 fadeIn(((View) button.getParent())); 1148 } 1149 } 1150 1151 private void hideAlert() { 1152 mVideoFrame.setVisibility(View.INVISIBLE); 1153 fadeIn(findViewById(R.id.shutter_button)); 1154 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1155 for (int id : pickIds) { 1156 View button = findViewById(id); 1157 fadeOut(((View) button.getParent())); 1158 } 1159 } 1160 1161 private static void fadeIn(View view) { 1162 view.setVisibility(View.VISIBLE); 1163 Animation animation = new AlphaAnimation(0F, 1F); 1164 animation.setDuration(500); 1165 view.startAnimation(animation); 1166 } 1167 1168 private static void fadeOut(View view) { 1169 view.setVisibility(View.INVISIBLE); 1170 Animation animation = new AlphaAnimation(1F, 0F); 1171 animation.setDuration(500); 1172 view.startAnimation(animation); 1173 } 1174 1175 private boolean isAlertVisible() { 1176 return this.mVideoFrame.getVisibility() == View.VISIBLE; 1177 } 1178 1179 private void viewLastVideo() { 1180 Intent intent = null; 1181 if (mThumbController.isUriValid()) { 1182 intent = new Intent(Util.REVIEW_ACTION, mThumbController.getUri()); 1183 try { 1184 startActivity(intent); 1185 } catch (ActivityNotFoundException ex) { 1186 try { 1187 intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri()); 1188 } catch (ActivityNotFoundException e) { 1189 Log.e(TAG, "review video fail", e); 1190 } 1191 } 1192 } else { 1193 Log.e(TAG, "Can't view last video."); 1194 } 1195 } 1196 1197 private void stopVideoRecording() { 1198 Log.v(TAG, "stopVideoRecording"); 1199 boolean needToRegisterRecording = false; 1200 if (mMediaRecorderRecording || mMediaRecorder != null) { 1201 if (mMediaRecorderRecording && mMediaRecorder != null) { 1202 try { 1203 mMediaRecorder.setOnErrorListener(null); 1204 mMediaRecorder.setOnInfoListener(null); 1205 mMediaRecorder.stop(); 1206 } catch (RuntimeException e) { 1207 Log.e(TAG, "stop fail: " + e.getMessage()); 1208 } 1209 mHeadUpDisplay.setEnabled(true); 1210 mCurrentVideoFilename = mCameraVideoFilename; 1211 Log.v(TAG, "Setting current video filename: " 1212 + mCurrentVideoFilename); 1213 needToRegisterRecording = true; 1214 mMediaRecorderRecording = false; 1215 } 1216 releaseMediaRecorder(); 1217 updateRecordingIndicator(true); 1218 mRecordingTimeView.setVisibility(View.GONE); 1219 keepScreenOnAwhile(); 1220 } 1221 if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) { 1222 registerVideo(); 1223 } 1224 1225 mCameraVideoFilename = null; 1226 mCameraVideoFileDescriptor = null; 1227 } 1228 1229 private void resetScreenOn() { 1230 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1231 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1232 } 1233 1234 private void keepScreenOnAwhile() { 1235 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1236 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1237 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1238 } 1239 1240 private void keepScreenOn() { 1241 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1242 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1243 } 1244 1245 private void hideAlertAndInitializeRecorder() { 1246 hideAlert(); 1247 mHandler.sendEmptyMessage(INIT_RECORDER); 1248 } 1249 1250 private void acquireVideoThumb() { 1251 Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail( 1252 mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); 1253 mThumbController.setData(mCurrentVideoUri, videoFrame); 1254 } 1255 1256 private static ImageManager.DataLocation dataLocation() { 1257 return ImageManager.DataLocation.EXTERNAL; 1258 } 1259 1260 private void updateLastVideo() { 1261 IImageList list = ImageManager.makeImageList( 1262 mContentResolver, 1263 dataLocation(), 1264 ImageManager.INCLUDE_VIDEOS, 1265 ImageManager.SORT_ASCENDING, 1266 ImageManager.CAMERA_IMAGE_BUCKET_ID); 1267 int count = list.getCount(); 1268 if (count > 0) { 1269 IImage image = list.getImageAt(count - 1); 1270 Uri uri = image.fullSizeImageUri(); 1271 mThumbController.setData(uri, image.miniThumbBitmap()); 1272 } else { 1273 mThumbController.setData(null, null); 1274 } 1275 list.close(); 1276 } 1277 1278 private void updateRecordingTime() { 1279 if (!mMediaRecorderRecording) { 1280 return; 1281 } 1282 long now = SystemClock.uptimeMillis(); 1283 long delta = now - mRecordingStartTime; 1284 1285 // Starting a minute before reaching the max duration 1286 // limit, we'll countdown the remaining time instead. 1287 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0 1288 && delta >= mMaxVideoDurationInMs - 60000); 1289 1290 long next_update_delay = 1000 - (delta % 1000); 1291 long seconds; 1292 if (countdownRemainingTime) { 1293 delta = Math.max(0, mMaxVideoDurationInMs - delta); 1294 seconds = (delta + 999) / 1000; 1295 } else { 1296 seconds = delta / 1000; // round to nearest 1297 } 1298 1299 long minutes = seconds / 60; 1300 long hours = minutes / 60; 1301 long remainderMinutes = minutes - (hours * 60); 1302 long remainderSeconds = seconds - (minutes * 60); 1303 1304 String secondsString = Long.toString(remainderSeconds); 1305 if (secondsString.length() < 2) { 1306 secondsString = "0" + secondsString; 1307 } 1308 String minutesString = Long.toString(remainderMinutes); 1309 if (minutesString.length() < 2) { 1310 minutesString = "0" + minutesString; 1311 } 1312 String text = minutesString + ":" + secondsString; 1313 if (hours > 0) { 1314 String hoursString = Long.toString(hours); 1315 if (hoursString.length() < 2) { 1316 hoursString = "0" + hoursString; 1317 } 1318 text = hoursString + ":" + text; 1319 } 1320 mRecordingTimeView.setText(text); 1321 1322 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1323 // Avoid setting the color on every update, do it only 1324 // when it needs changing. 1325 mRecordingTimeCountsDown = countdownRemainingTime; 1326 1327 int color = getResources().getColor(countdownRemainingTime 1328 ? R.color.recording_time_remaining_text 1329 : R.color.recording_time_elapsed_text); 1330 1331 mRecordingTimeView.setTextColor(color); 1332 } 1333 1334 mHandler.sendEmptyMessageDelayed( 1335 UPDATE_RECORD_TIME, next_update_delay); 1336 } 1337 1338 private static boolean isSupported(String value, List<String> supported) { 1339 return supported == null ? false : supported.indexOf(value) >= 0; 1340 } 1341 1342 private void setCameraParameters() { 1343 mParameters = mCameraDevice.getParameters(); 1344 1345 mParameters.setPreviewSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); 1346 mParameters.setPreviewFrameRate(mProfile.videoFrameRate); 1347 1348 // Set flash mode. 1349 String flashMode = mPreferences.getString( 1350 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 1351 getString(R.string.pref_camera_video_flashmode_default)); 1352 List<String> supportedFlash = mParameters.getSupportedFlashModes(); 1353 if (isSupported(flashMode, supportedFlash)) { 1354 mParameters.setFlashMode(flashMode); 1355 } else { 1356 flashMode = mParameters.getFlashMode(); 1357 if (flashMode == null) { 1358 flashMode = getString( 1359 R.string.pref_camera_flashmode_no_flash); 1360 } 1361 } 1362 1363 // Set white balance parameter. 1364 String whiteBalance = mPreferences.getString( 1365 CameraSettings.KEY_WHITE_BALANCE, 1366 getString(R.string.pref_camera_whitebalance_default)); 1367 if (isSupported(whiteBalance, 1368 mParameters.getSupportedWhiteBalance())) { 1369 mParameters.setWhiteBalance(whiteBalance); 1370 } else { 1371 whiteBalance = mParameters.getWhiteBalance(); 1372 if (whiteBalance == null) { 1373 whiteBalance = Parameters.WHITE_BALANCE_AUTO; 1374 } 1375 } 1376 1377 // Set color effect parameter. 1378 String colorEffect = mPreferences.getString( 1379 CameraSettings.KEY_COLOR_EFFECT, 1380 getString(R.string.pref_camera_coloreffect_default)); 1381 if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) { 1382 mParameters.setColorEffect(colorEffect); 1383 } 1384 1385 mCameraDevice.setParameters(mParameters); 1386 } 1387 1388 private boolean switchToCameraMode() { 1389 if (isFinishing() || mMediaRecorderRecording) return false; 1390 MenuHelper.gotoCameraMode(this); 1391 ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView); 1392 finish(); 1393 return true; 1394 } 1395 1396 public boolean onSwitchChanged(Switcher source, boolean onOff) { 1397 if (onOff == SWITCH_CAMERA) { 1398 return switchToCameraMode(); 1399 } else { 1400 return true; 1401 } 1402 } 1403 1404 private void resetCameraParameters() { 1405 // We need to restart the preview if preview size is changed. 1406 Size size = mParameters.getPreviewSize(); 1407 if (size.width != mProfile.videoFrameWidth 1408 || size.height != mProfile.videoFrameHeight) { 1409 // It is assumed media recorder is released before 1410 // onSharedPreferenceChanged, so we can close the camera here. 1411 closeCamera(); 1412 try { 1413 resizeForPreviewAspectRatio(); 1414 startPreview(); // Parameters will be set in startPreview(). 1415 } catch (CameraHardwareException e) { 1416 showCameraBusyAndFinish(); 1417 } 1418 } else { 1419 try { 1420 // We need to lock the camera before writing parameters. 1421 mCameraDevice.lock(); 1422 } catch (RuntimeException e) { 1423 // When preferences are added for the first time, this method 1424 // will be called. But OnScreenSetting is not displayed yet and 1425 // media recorder still owns the camera. Lock will fail and we 1426 // just ignore it. 1427 return; 1428 } 1429 setCameraParameters(); 1430 mCameraDevice.unlock(); 1431 } 1432 } 1433 1434 public void onSizeChanged() { 1435 // TODO: update the content on GLRootView 1436 } 1437 1438 private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener { 1439 public void onSharedPreferencesChanged() { 1440 mHandler.post(new Runnable() { 1441 public void run() { 1442 VideoCamera.this.onSharedPreferencesChanged(); 1443 } 1444 }); 1445 } 1446 1447 public void onRestorePreferencesClicked() { 1448 mHandler.post(new Runnable() { 1449 public void run() { 1450 VideoCamera.this.onRestorePreferencesClicked(); 1451 } 1452 }); 1453 } 1454 1455 public void onPopupWindowVisibilityChanged(final int visibility) { 1456 mHandler.post(new Runnable() { 1457 public void run() { 1458 VideoCamera.this.onPopupWindowVisibilityChanged(visibility); 1459 } 1460 }); 1461 } 1462 } 1463 1464 private void onPopupWindowVisibilityChanged(int visibility) { 1465 if (visibility == GLView.VISIBLE) { 1466 releaseMediaRecorder(); 1467 } else { 1468 if (!mPausing && mSurfaceHolder != null) initializeRecorder(); 1469 } 1470 } 1471 1472 private void onRestorePreferencesClicked() { 1473 Runnable runnable = new Runnable() { 1474 public void run() { 1475 mHeadUpDisplay.restorePreferences(mParameters); 1476 } 1477 }; 1478 MenuHelper.confirmAction(this, 1479 getString(R.string.confirm_restore_title), 1480 getString(R.string.confirm_restore_message), 1481 runnable); 1482 } 1483 1484 private void onSharedPreferencesChanged() { 1485 // ignore the events after "onPause()" or preview has not started yet 1486 if (mPausing) return; 1487 synchronized (mPreferences) { 1488 readVideoPreferences(); 1489 // If mCameraDevice is not ready then we can set the parameter in 1490 // startPreview(). 1491 if (mCameraDevice == null) return; 1492 resetCameraParameters(); 1493 } 1494 } 1495} 1496