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