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