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