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