VideoCamera.java revision dee42a6e53cb1d0c7194b0cb92028cca353d7c5c
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.camera; 18 19import java.io.File; 20import java.io.IOException; 21import java.io.InputStream; 22import java.io.OutputStream; 23import java.text.SimpleDateFormat; 24import java.util.ArrayList; 25import java.util.Date; 26 27import android.app.Activity; 28import android.app.AlertDialog; 29import android.content.BroadcastReceiver; 30import android.content.ContentResolver; 31import android.content.ContentValues; 32import android.content.Context; 33import android.content.Intent; 34import android.content.IntentFilter; 35import android.content.SharedPreferences; 36import android.graphics.Bitmap; 37import android.graphics.drawable.ColorDrawable; 38import android.graphics.drawable.Drawable; 39import android.location.LocationManager; 40import android.media.MediaMetadataRetriever; 41import android.media.MediaRecorder; 42import android.net.Uri; 43import android.os.Bundle; 44import android.os.Environment; 45import android.os.Handler; 46import android.os.Message; 47import android.os.StatFs; 48import android.os.SystemClock; 49import android.preference.PreferenceManager; 50import android.provider.MediaStore; 51import android.provider.MediaStore.Video; 52import android.text.format.DateFormat; 53import android.util.Log; 54import android.view.KeyEvent; 55import android.view.Menu; 56import android.view.MenuItem; 57import android.view.SurfaceHolder; 58import android.view.View; 59import android.view.Window; 60import android.view.WindowManager; 61import android.view.MenuItem.OnMenuItemClickListener; 62import android.view.animation.AlphaAnimation; 63import android.view.animation.Animation; 64import android.widget.ImageView; 65import android.widget.TextView; 66import android.widget.Toast; 67 68public class VideoCamera extends Activity implements View.OnClickListener, 69 ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback { 70 71 private static final String TAG = "videocamera"; 72 73 private static final boolean DEBUG = true; 74 private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = DEBUG && false; 75 private static final boolean DEBUG_DO_NOT_REUSE_MEDIA_RECORDER = DEBUG && true; 76 private static final boolean DEBUG_LOG_APP_LIFECYCLE = DEBUG && false; 77 78 private static final int CLEAR_SCREEN_DELAY = 4; 79 private static final int UPDATE_RECORD_TIME = 5; 80 81 private static final int SCREEN_DELAY = 2 * 60 * 1000; 82 83 private static final long NO_STORAGE_ERROR = -1L; 84 private static final long CANNOT_STAT_ERROR = -2L; 85 private static final long LOW_STORAGE_THRESHOLD = 512L * 1024L; 86 87 public static final int MENU_SETTINGS = 6; 88 public static final int MENU_GALLERY_PHOTOS = 7; 89 public static final int MENU_GALLERY_VIDEOS = 8; 90 public static final int MENU_SAVE_GALLERY_PHOTO = 34; 91 public static final int MENU_SAVE_PLAY_VIDEO = 35; 92 public static final int MENU_SAVE_SELECT_VIDEO = 36; 93 public static final int MENU_SAVE_NEW_VIDEO = 37; 94 95 SharedPreferences mPreferences; 96 97 private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f; 98 VideoPreview mVideoPreview; 99 SurfaceHolder mSurfaceHolder = null; 100 ImageView mBlackout = null; 101 ImageView mVideoFrame; 102 Bitmap mVideoFrameBitmap; 103 104 private MediaRecorder mMediaRecorder; 105 private boolean mMediaRecorderRecording = false; 106 private boolean mNeedToRegisterRecording; 107 private long mRecordingStartTime; 108 // The video file that the hardware camera is about to record into 109 // (or is recording into.) 110 private String mCameraVideoFilename; 111 112 // The video file that has already been recorded, and that is being 113 // examined by the user. 114 private String mCurrentVideoFilename; 115 private Uri mCurrentVideoUri; 116 private ContentValues mCurrentVideoValues; 117 118 boolean mPausing = false; 119 120 static ContentResolver mContentResolver; 121 boolean mDidRegister = false; 122 123 int mCurrentZoomIndex = 0; 124 125 private ShutterButton mShutterButton; 126 private TextView mRecordingTimeView; 127 private boolean mHasSdCard; 128 129 ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); 130 131 View mPostPictureAlert; 132 LocationManager mLocationManager = null; 133 134 private Handler mHandler = new MainHandler(); 135 136 /** This Handler is used to post message back onto the main thread of the application */ 137 private class MainHandler extends Handler { 138 @Override 139 public void handleMessage(Message msg) { 140 switch (msg.what) { 141 142 case CLEAR_SCREEN_DELAY: { 143 clearScreenOnFlag(); 144 break; 145 } 146 147 case UPDATE_RECORD_TIME: { 148 if (mMediaRecorderRecording) { 149 long now = SystemClock.uptimeMillis(); 150 long delta = now - mRecordingStartTime; 151 long seconds = delta / 1000; 152 long minutes = seconds / 60; 153 long remainderSeconds = seconds - (minutes * 60); 154 155 String secondsString = Long.toString(remainderSeconds); 156 if (secondsString.length() < 2) { 157 secondsString = "0" + secondsString; 158 } 159 String minutesString = Long.toString(minutes); 160 if (minutesString.length() < 2) { 161 minutesString = "0" + minutesString; 162 } 163 String text = minutesString + ":" + secondsString; 164 mRecordingTimeView.setText(text); 165 // Work around a limitation of the T-Mobile G1: The T-Mobile 166 // hardware blitter can't pixel-accurately scale and clip at the same time, 167 // and the SurfaceFlinger doesn't attempt to work around this limitation. 168 // In order to avoid visual corruption we must manually refresh the entire 169 // surface view when changing any overlapping view's contents. 170 mVideoPreview.invalidate(); 171 mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000); 172 } 173 break; 174 } 175 176 default: 177 Log.v(TAG, "Unhandled message: " + msg.what); 178 break; 179 } 180 } 181 }; 182 183 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 184 @Override 185 public void onReceive(Context context, Intent intent) { 186 String action = intent.getAction(); 187 if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 188 // SD card available 189 // TODO put up a "please wait" message 190 // TODO also listen for the media scanner finished message 191 showStorageToast(); 192 mHasSdCard = true; 193 } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { 194 // SD card unavailable 195 showStorageToast(); 196 mHasSdCard = false; 197 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { 198 Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000); 199 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { 200 showStorageToast(); 201 } 202 } 203 }; 204 205 static private String createName(long dateTaken) { 206 return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); 207 } 208 209 /** Called with the activity is first created. */ 210 @Override 211 public void onCreate(Bundle icicle) { 212 if (DEBUG_LOG_APP_LIFECYCLE) { 213 Log.v(TAG, "onCreate " + this.hashCode()); 214 } 215 super.onCreate(icicle); 216 217 mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); 218 219 mPreferences = PreferenceManager.getDefaultSharedPreferences(this); 220 mContentResolver = getContentResolver(); 221 222 //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); 223 requestWindowFeature(Window.FEATURE_PROGRESS); 224 setContentView(R.layout.video_camera); 225 226 mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview); 227 mVideoPreview.setAspectRatio(VIDEO_ASPECT_RATIO); 228 229 // don't set mSurfaceHolder here. We have it set ONLY within 230 // surfaceCreated / surfaceDestroyed, other parts of the code 231 // assume that when it is set, the surface is also set. 232 SurfaceHolder holder = mVideoPreview.getHolder(); 233 holder.addCallback(this); 234 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 235 236 mBlackout = (ImageView) findViewById(R.id.blackout); 237 mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); 238 239 mPostPictureAlert = findViewById(R.id.post_picture_panel); 240 241 int[] ids = new int[]{R.id.play, R.id.share, R.id.discard, 242 R.id.cancel, R.id.attach}; 243 for (int id : ids) { 244 findViewById(id).setOnClickListener(this); 245 } 246 247 mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); 248 mShutterButton.setOnShutterButtonListener(this); 249 mRecordingTimeView = (TextView) findViewById(R.id.recording_time); 250 mVideoFrame = (ImageView) findViewById(R.id.video_frame); 251 } 252 253 @Override 254 public void onStart() { 255 if (DEBUG_LOG_APP_LIFECYCLE) { 256 Log.v(TAG, "onStart " + this.hashCode()); 257 } 258 super.onStart(); 259 260 Thread t = new Thread(new Runnable() { 261 public void run() { 262 final boolean storageOK = getAvailableStorage() >= LOW_STORAGE_THRESHOLD; 263 264 if (!storageOK) { 265 mHandler.post(new Runnable() { 266 public void run() { 267 showStorageToast(); 268 } 269 }); 270 } 271 } 272 }); 273 t.start(); 274 } 275 276 public void onClick(View v) { 277 switch (v.getId()) { 278 279 case R.id.attach: 280 doReturnToCaller(true); 281 break; 282 283 case R.id.cancel: 284 doReturnToCaller(false); 285 break; 286 287 case R.id.discard: { 288 discardCurrentVideoAndStartPreview(); 289 break; 290 } 291 292 case R.id.share: { 293 Intent intent = new Intent(); 294 intent.setAction(Intent.ACTION_SEND); 295 intent.setType("video/3gpp"); 296 intent.putExtra(Intent.EXTRA_STREAM, mCurrentVideoUri); 297 try { 298 startActivity(Intent.createChooser(intent, getText(R.string.sendVideo))); 299 } catch (android.content.ActivityNotFoundException ex) { 300 Toast.makeText(VideoCamera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); 301 } 302 303 break; 304 } 305 306 case R.id.play: { 307 doPlayCurrentVideo(); 308 break; 309 } 310 } 311 } 312 313 public void onShutterButtonFocus(ShutterButton button, boolean pressed) { 314 switch (button.getId()) { 315 case R.id.shutter_button: 316 if (pressed) { 317 if (mMediaRecorderRecording) { 318 stopVideoRecordingAndDisplayDialog(); 319 } else if (mVideoFrame.getVisibility() == View.VISIBLE) { 320 doStartCaptureMode(); 321 } else { 322 startVideoRecording(); 323 } 324 } 325 break; 326 } 327 } 328 329 public void onShutterButtonClick(ShutterButton button) { 330 // Do nothing (everything happens in onShutterButtonFocus). 331 } 332 333 private void doStartCaptureMode() { 334 if (isVideoCaptureIntent()) { 335 discardCurrentVideoAndStartPreview(); 336 } else { 337 hideVideoFrameAndStartPreview(); 338 } 339 } 340 341 private void doPlayCurrentVideo() { 342 Log.e(TAG, "Playing current video: " + mCurrentVideoUri); 343 Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); 344 try { 345 startActivity(intent); 346 } catch (android.content.ActivityNotFoundException ex) { 347 Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); 348 } 349 } 350 351 private void discardCurrentVideoAndStartPreview() { 352 deleteCurrentVideo(); 353 hideVideoFrameAndStartPreview(); 354 } 355 356 private void showStorageToast() { 357 long remaining = getAvailableStorage(); 358 359 if (remaining == NO_STORAGE_ERROR) { 360 Toast.makeText(this, getString(R.string.no_storage), Toast.LENGTH_LONG).show(); 361 } else if (remaining < LOW_STORAGE_THRESHOLD) { 362 new AlertDialog.Builder(this).setTitle(R.string.spaceIsLow_title) 363 .setMessage(R.string.spaceIsLow_content) 364 .show(); 365 } 366 } 367 368 @Override 369 public void onResume() { 370 if (DEBUG_LOG_APP_LIFECYCLE) { 371 Log.v(TAG, "onResume " + this.hashCode()); 372 } 373 super.onResume(); 374 375 setScreenTimeoutLong(); 376 377 mPausing = false; 378 379 // install an intent filter to receive SD card related events. 380 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); 381 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 382 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); 383 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); 384 intentFilter.addDataScheme("file"); 385 registerReceiver(mReceiver, intentFilter); 386 mDidRegister = true; 387 mHasSdCard = ImageManager.hasStorage(); 388 389 mBlackout.setVisibility(View.INVISIBLE); 390 if (mVideoFrameBitmap == null) { 391 initializeVideo(); 392 } else { 393 showPostRecordingAlert(); 394 } 395 } 396 397 @Override 398 public void onStop() { 399 if (DEBUG_LOG_APP_LIFECYCLE) { 400 Log.v(TAG, "onStop " + this.hashCode()); 401 } 402 stopVideoRecording(); 403 setScreenTimeoutSystemDefault(); 404 super.onStop(); 405 } 406 407 @Override 408 protected void onPause() { 409 if (DEBUG_LOG_APP_LIFECYCLE) { 410 Log.v(TAG, "onPause " + this.hashCode()); 411 } 412 super.onPause(); 413 414 stopVideoRecording(); 415 hidePostPictureAlert(); 416 417 mPausing = true; 418 419 if (mDidRegister) { 420 unregisterReceiver(mReceiver); 421 mDidRegister = false; 422 } 423 mBlackout.setVisibility(View.VISIBLE); 424 setScreenTimeoutSystemDefault(); 425 } 426 427 @Override 428 public boolean onKeyDown(int keyCode, KeyEvent event) { 429 setScreenTimeoutLong(); 430 431 switch (keyCode) { 432 case KeyEvent.KEYCODE_BACK: 433 if (mMediaRecorderRecording) { 434 Log.v(TAG, "onKeyBack"); 435 stopVideoRecordingAndDisplayDialog(); 436 return true; 437 } else if(isPostRecordingAlertVisible()) { 438 hideVideoFrameAndStartPreview(); 439 return true; 440 } 441 break; 442 case KeyEvent.KEYCODE_CAMERA: 443 if (event.getRepeatCount() == 0) { 444 // If we get a dpad center event without any focused view, move the 445 // focus to the shutter button and press it. 446 if (mShutterButton.isInTouchMode()) { 447 mShutterButton.requestFocusFromTouch(); 448 } else { 449 mShutterButton.requestFocus(); 450 } 451 mShutterButton.setPressed(true); 452 return true; 453 } 454 return true; 455 case KeyEvent.KEYCODE_DPAD_CENTER: 456 if (event.getRepeatCount() == 0) { 457 // If we get a dpad center event without any focused view, move the 458 // focus to the shutter button and press it. 459 if (mShutterButton.isInTouchMode()) { 460 mShutterButton.requestFocusFromTouch(); 461 } else { 462 mShutterButton.requestFocus(); 463 } 464 mShutterButton.setPressed(true); 465 } 466 break; 467 case KeyEvent.KEYCODE_MENU: 468 if (mMediaRecorderRecording) { 469 stopVideoRecordingAndDisplayDialog(); 470 return true; 471 } 472 break; 473 } 474 475 return super.onKeyDown(keyCode, event); 476 } 477 478 @Override 479 public boolean onKeyUp(int keyCode, KeyEvent event) { 480 switch(keyCode) { 481 case KeyEvent.KEYCODE_CAMERA: 482 mShutterButton.setPressed(false); 483 return true; 484 } 485 return super.onKeyUp(keyCode, event); 486 } 487 488 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 489 stopVideoRecording(); 490 initializeVideo(); 491 } 492 493 public void surfaceCreated(SurfaceHolder holder) { 494 mSurfaceHolder = holder; 495 } 496 497 public void surfaceDestroyed(SurfaceHolder holder) { 498 mSurfaceHolder = null; 499 } 500 501 void gotoGallery() { 502 MenuHelper.gotoCameraVideoGallery(this); 503 } 504 505 @Override 506 public boolean onPrepareOptionsMenu(Menu menu) { 507 super.onPrepareOptionsMenu(menu); 508 509 for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) { 510 if (i != MenuHelper.GENERIC_ITEM) { 511 menu.setGroupVisible(i, false); 512 } 513 } 514 515 menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true); 516 return true; 517 } 518 519 @Override 520 public boolean onCreateOptionsMenu(Menu menu) { 521 super.onCreateOptionsMenu(menu); 522 523 if (isVideoCaptureIntent()) { 524 // No options menu for attach mode. 525 return false; 526 } else { 527 addBaseMenuItems(menu); 528 MenuHelper.addImageMenuItems( 529 menu, 530 MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU, 531 false, 532 VideoCamera.this, 533 mHandler, 534 535 // Handler for deletion 536 new Runnable() { 537 public void run() { 538 // What do we do here? 539 // mContentResolver.delete(uri, null, null); 540 } 541 }, 542 new MenuHelper.MenuInvoker() { 543 public void run(final MenuHelper.MenuCallback cb) { 544 } 545 }); 546 547 MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, 548 R.string.camera_gallery_photos_text).setOnMenuItemClickListener( 549 new MenuItem.OnMenuItemClickListener() { 550 public boolean onMenuItemClick(MenuItem item) { 551 gotoGallery(); 552 return true; 553 } 554 }); 555 gallery.setIcon(android.R.drawable.ic_menu_gallery); 556 } 557 return true; 558 } 559 560 private boolean isVideoCaptureIntent() { 561 String action = getIntent().getAction(); 562 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 563 } 564 565 private void doReturnToCaller(boolean success) { 566 Intent resultIntent = new Intent(); 567 int resultCode; 568 if (success) { 569 resultCode = RESULT_OK; 570 Uri saveUri = null; 571 572 Bundle myExtras = getIntent().getExtras(); 573 if (myExtras != null) { 574 saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 575 } 576 577 if (saveUri != null) { 578 // TODO: Record the video directly into the content provider stream when 579 // bug 1582062 is fixed. Until then we copy the video data from the 580 // original location to the requested location and then delete the original. 581 OutputStream outputStream = null; 582 InputStream inputStream = null; 583 584 try { 585 inputStream = mContentResolver.openInputStream(mCurrentVideoUri); 586 outputStream = mContentResolver.openOutputStream(saveUri); 587 byte[] buffer = new byte[64*1024]; 588 while(true) { 589 int bytesRead = inputStream.read(buffer); 590 if (bytesRead < 0) { 591 break; 592 } 593 outputStream.write(buffer, 0, bytesRead); 594 } 595 } catch (IOException ex) { 596 Log.e(TAG, "Could not copy video file to Uri", ex); 597 } finally { 598 if (inputStream != null) { 599 try { 600 inputStream.close(); 601 } catch (IOException ex) { 602 Log.e(TAG, "Could not close video file", ex); 603 } 604 } 605 if (outputStream != null) { 606 try { 607 outputStream.close(); 608 } catch (IOException ex) { 609 Log.e(TAG, "Could not close output uri", ex); 610 } 611 } 612 deleteCurrentVideo(); 613 } 614 } else { 615 resultIntent.setData(mCurrentVideoUri); 616 } 617 } else { 618 resultCode = RESULT_CANCELED; 619 } 620 setResult(resultCode, resultIntent); 621 finish(); 622 } 623 624 /** 625 * Returns 626 * @return number of bytes available, or an ERROR code. 627 */ 628 private static long getAvailableStorage() { 629 try { 630 if (!ImageManager.hasStorage()) { 631 return NO_STORAGE_ERROR; 632 } else { 633 String storageDirectory = Environment.getExternalStorageDirectory().toString(); 634 StatFs stat = new StatFs(storageDirectory); 635 return ((long)stat.getAvailableBlocks() * (long)stat.getBlockSize()); 636 } 637 } catch (Exception ex) { 638 // if we can't stat the filesystem then we don't know how many 639 // free bytes exist. It might be zero but just leave it 640 // blank since we really don't know. 641 return CANNOT_STAT_ERROR; 642 } 643 } 644 645 private void initializeVideo() { 646 Log.v(TAG, "initializeVideo"); 647 releaseMediaRecorder(); 648 649 if (mSurfaceHolder == null) { 650 Log.v(TAG, "SurfaceHolder is null"); 651 return; 652 } 653 654 mMediaRecorder = new MediaRecorder(); 655 mNeedToRegisterRecording = false; 656 657 if (DEBUG_SUPPRESS_AUDIO_RECORDING) { 658 Log.v(TAG, "DEBUG_SUPPRESS_AUDIO_RECORDING is true."); 659 } else { 660 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 661 } 662 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 663 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 664 createVideoPath(); 665 mMediaRecorder.setOutputFile(mCameraVideoFilename); 666 boolean videoQualityHigh = getBooleanPreference(CameraSettings.KEY_VIDEO_QUALITY, 667 CameraSettings.DEFAULT_VIDEO_QUALITY_VALUE); 668 669 { 670 Intent intent = getIntent(); 671 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 672 int extraVideoQuality = intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); 673 videoQualityHigh = (extraVideoQuality > 0); 674 } 675 } 676 677 // Use the same frame rate for both, since internally 678 // if the frame rate is too large, it can cause camera to become 679 // unstable. We need to fix the MediaRecorder to disable the support 680 // of setting frame rate for now. 681 mMediaRecorder.setVideoFrameRate(20); 682 if (videoQualityHigh) { 683 mMediaRecorder.setVideoSize(352,288); 684 } else { 685 mMediaRecorder.setVideoSize(176,144); 686 } 687 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); 688 if (!DEBUG_SUPPRESS_AUDIO_RECORDING) { 689 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 690 } 691 mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); 692 try { 693 mMediaRecorder.prepare(); 694 } catch (IOException exception) { 695 Log.e(TAG, "prepare failed for " + mCameraVideoFilename); 696 releaseMediaRecorder(); 697 // TODO: add more exception handling logic here 698 return; 699 } 700 mMediaRecorderRecording = false; 701 } 702 703 private void releaseMediaRecorder() { 704 Log.v(TAG, "Releasing media recorder."); 705 if (mMediaRecorder != null) { 706 mMediaRecorder.reset(); 707 mMediaRecorder.release(); 708 mMediaRecorder = null; 709 } 710 } 711 712 private void restartPreview() { 713 if (DEBUG_DO_NOT_REUSE_MEDIA_RECORDER) { 714 Log.v(TAG, "DEBUG_DO_NOT_REUSE_MEDIA_RECORDER recreating mMediaRecorder."); 715 initializeVideo(); 716 } else { 717 try { 718 mMediaRecorder.prepare(); 719 } catch (IOException exception) { 720 Log.e(TAG, "prepare failed for " + mCameraVideoFilename); 721 releaseMediaRecorder(); 722 // TODO: add more exception handling logic here 723 } 724 } 725 } 726 727 private int getIntPreference(String key, int defaultValue) { 728 String s = mPreferences.getString(key, ""); 729 int result = defaultValue; 730 try { 731 result = Integer.parseInt(s); 732 } catch (NumberFormatException e) { 733 // Ignore, result is already the default value. 734 } 735 return result; 736 } 737 738 private boolean getBooleanPreference(String key, boolean defaultValue) { 739 return getIntPreference(key, defaultValue ? 1 : 0) != 0; 740 } 741 742 private void createVideoPath() { 743 long dateTaken = System.currentTimeMillis(); 744 String title = createName(dateTaken); 745 String displayName = title + ".3gp"; // Used when emailing. 746 String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; 747 File cameraDir = new File(cameraDirPath); 748 cameraDir.mkdirs(); 749 SimpleDateFormat dateFormat = new SimpleDateFormat( 750 getString(R.string.video_file_name_format)); 751 Date date = new Date(dateTaken); 752 String filepart = dateFormat.format(date); 753 String filename = cameraDirPath + "/" + filepart + ".3gp"; 754 ContentValues values = new ContentValues(7); 755 values.put(Video.Media.TITLE, title); 756 values.put(Video.Media.DISPLAY_NAME, displayName); 757 values.put(Video.Media.DESCRIPTION, ""); 758 values.put(Video.Media.DATE_TAKEN, dateTaken); 759 values.put(Video.Media.MIME_TYPE, "video/3gpp"); 760 values.put(Video.Media.DATA, filename); 761 mCameraVideoFilename = filename; 762 Log.v(TAG, "Current camera video filename: " + mCameraVideoFilename); 763 mCurrentVideoValues = values; 764 } 765 766 private void registerVideo() { 767 Uri videoTable = Uri.parse("content://media/external/video/media"); 768 mCurrentVideoUri = mContentResolver.insert(videoTable, 769 mCurrentVideoValues); 770 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 771 mCurrentVideoValues = null; 772 } 773 774 private void deleteCurrentVideo() { 775 if (mCurrentVideoFilename != null) { 776 deleteVideoFile(mCurrentVideoFilename); 777 mCurrentVideoFilename = null; 778 } 779 if (mCurrentVideoUri != null) { 780 mContentResolver.delete(mCurrentVideoUri, null, null); 781 mCurrentVideoUri = null; 782 } 783 } 784 785 private void deleteVideoFile(String fileName) { 786 Log.v(TAG, "Deleting video " + fileName); 787 File f = new File(fileName); 788 if (! f.delete()) { 789 Log.v(TAG, "Could not delete " + fileName); 790 } 791 } 792 793 private void addBaseMenuItems(Menu menu) { 794 MenuHelper.addSwitchModeMenuItem(menu, this, false); 795 { 796 MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { 797 public boolean onMenuItemClick(MenuItem item) { 798 gotoGallery(); 799 return true; 800 } 801 }); 802 gallery.setIcon(android.R.drawable.ic_menu_gallery); 803 mGalleryItems.add(gallery); 804 } 805 { 806 MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { 807 public boolean onMenuItemClick(MenuItem item) { 808 gotoGallery(); 809 return true; 810 } 811 }); 812 gallery.setIcon(android.R.drawable.ic_menu_gallery); 813 mGalleryItems.add(gallery); 814 } 815 816 MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() { 817 public boolean onMenuItemClick(MenuItem item) { 818 Intent intent = new Intent(); 819 intent.setClass(VideoCamera.this, CameraSettings.class); 820 startActivity(intent); 821 return true; 822 } 823 }); 824 item.setIcon(android.R.drawable.ic_menu_preferences); 825 } 826 827 private void startVideoRecording() { 828 Log.v(TAG, "startVideoRecording"); 829 if (!mMediaRecorderRecording) { 830 831 if (!mHasSdCard) { 832 Toast.makeText(this, getString( 833 R.string.no_storage), Toast.LENGTH_LONG).show(); 834 Log.v(TAG, "No SD card, ignore start recording"); 835 return; 836 } 837 838 // Check mMediaRecorder to see whether it is initialized or not. 839 if (mMediaRecorder == null) { 840 initializeVideo(); 841 } 842 try { 843 mMediaRecorder.start(); // Recording is now started 844 } catch (RuntimeException e) { 845 Log.e(TAG, "Could not start media recorder. ", e); 846 return; 847 } 848 mMediaRecorderRecording = true; 849 mRecordingStartTime = SystemClock.uptimeMillis(); 850 updateRecordingIndicator(true); 851 mRecordingTimeView.setText(""); 852 mRecordingTimeView.setVisibility(View.VISIBLE); 853 mHandler.sendEmptyMessage(UPDATE_RECORD_TIME); 854 setScreenTimeoutInfinite(); 855 } 856 } 857 858 private void updateRecordingIndicator(boolean showRecording) { 859 int drawableId = showRecording ? R.drawable.ic_camera_bar_indicator_record 860 : R.drawable.ic_camera_indicator_video; 861 Drawable drawable = getResources().getDrawable(drawableId); 862 mShutterButton.setImageDrawable(drawable); 863 } 864 865 private void stopVideoRecordingAndDisplayDialog() { 866 Log.v(TAG, "stopVideoRecordingAndDisplayDialog"); 867 if (mMediaRecorderRecording) { 868 stopVideoRecording(); 869 acquireAndShowVideoFrame(); 870 showPostRecordingAlert(); 871 } 872 } 873 874 private void showPostRecordingAlert() { 875 int[] pickIds = {R.id.attach, R.id.cancel}; 876 int[] normalIds = {R.id.share, R.id.discard}; 877 int[] alwaysOnIds = {R.id.play}; 878 int[] hideIds = pickIds; 879 int[] connectIds = normalIds; 880 if (isVideoCaptureIntent()) { 881 hideIds = normalIds; 882 connectIds = pickIds; 883 } 884 for(int id : hideIds) { 885 mPostPictureAlert.findViewById(id).setVisibility(View.GONE); 886 } 887 connectAndFadeIn(connectIds); 888 connectAndFadeIn(alwaysOnIds); 889 mPostPictureAlert.setVisibility(View.VISIBLE); 890 } 891 892 private void connectAndFadeIn(int[] connectIds) { 893 for(int id : connectIds) { 894 View view = mPostPictureAlert.findViewById(id); 895 view.setOnClickListener(this); 896 Animation animation = new AlphaAnimation(0F, 1F); 897 animation.setDuration(500); 898 view.setAnimation(animation); 899 } 900 } 901 902 private void hidePostPictureAlert() { 903 mPostPictureAlert.setVisibility(View.INVISIBLE); 904 } 905 906 private boolean isPostRecordingAlertVisible() { 907 return mPostPictureAlert.getVisibility() == View.VISIBLE; 908 } 909 910 private void stopVideoRecording() { 911 Log.v(TAG, "stopVideoRecording"); 912 if (mMediaRecorderRecording || mMediaRecorder != null) { 913 if (mMediaRecorderRecording) { 914 mMediaRecorder.stop(); 915 mCurrentVideoFilename = mCameraVideoFilename; 916 Log.v(TAG, "Setting current video filename: " + mCurrentVideoFilename); 917 mCameraVideoFilename = null; 918 mNeedToRegisterRecording = true; 919 mMediaRecorderRecording = false; 920 } 921 releaseMediaRecorder(); 922 updateRecordingIndicator(false); 923 mRecordingTimeView.setVisibility(View.GONE); 924 setScreenTimeoutLong(); 925 } 926 if (mNeedToRegisterRecording) { 927 registerVideo(); 928 mNeedToRegisterRecording = false; 929 } 930 mCameraVideoFilename = null; 931 } 932 933 private void setScreenTimeoutSystemDefault() { 934 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 935 clearScreenOnFlag(); 936 } 937 938 private void setScreenTimeoutLong() { 939 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 940 setScreenOnFlag(); 941 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 942 } 943 944 private void setScreenTimeoutInfinite() { 945 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 946 setScreenOnFlag(); 947 } 948 949 private void clearScreenOnFlag() { 950 Window w = getWindow(); 951 final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 952 if ((w.getAttributes().flags & keepScreenOnFlag) != 0) { 953 w.clearFlags(keepScreenOnFlag); 954 } 955 } 956 957 private void setScreenOnFlag() { 958 Window w = getWindow(); 959 final int keepScreenOnFlag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 960 if ((w.getAttributes().flags & keepScreenOnFlag) == 0) { 961 w.addFlags(keepScreenOnFlag); 962 } 963 } 964 965 private void hideVideoFrameAndStartPreview() { 966 hidePostPictureAlert(); 967 hideVideoFrame(); 968 restartPreview(); 969 } 970 971 private void acquireAndShowVideoFrame() { 972 recycleVideoFrameBitmap(); 973 mVideoFrameBitmap = ImageManager.createVideoThumbnail(mCurrentVideoFilename); 974 mVideoFrame.setImageBitmap(mVideoFrameBitmap); 975 mVideoFrame.setVisibility(View.VISIBLE); 976 } 977 978 private void hideVideoFrame() { 979 recycleVideoFrameBitmap(); 980 mVideoFrame.setVisibility(View.GONE); 981 } 982 983 private void recycleVideoFrameBitmap() { 984 if (mVideoFrameBitmap != null) { 985 mVideoFrame.setImageDrawable(null); 986 mVideoFrameBitmap.recycle(); 987 mVideoFrameBitmap = null; 988 } 989 } 990} 991 992