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