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 handleMessage(Message msg)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 onReceive(Context context, Intent intent)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 createName(long dateTaken)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 showCameraErrorAndFinish()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 restartPreview()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 onCreate(Bundle icicle)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 changeHeadUpDisplayState()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 initializeHeadUpDisplay()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 attachHeadUpDisplay()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 detachHeadUpDisplay()430 private void detachHeadUpDisplay() { 431 mHeadUpDisplay.collapse(); 432 ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView); 433 mGLRootView = null; 434 } 435 roundOrientation(int orientation)436 public static int roundOrientation(int orientation) { 437 return ((orientation + 45) / 90 * 90) % 360; 438 } 439 440 private class MyOrientationEventListener 441 extends OrientationEventListener { MyOrientationEventListener(Context context)442 public MyOrientationEventListener(Context context) { 443 super(context); 444 } 445 446 @Override onOrientationChanged(int orientation)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 setOrientationIndicator(int degree)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 onStart()478 protected void onStart() { 479 super.onStart(); 480 if (!mIsVideoCaptureIntent) { 481 mSwitcher.setSwitch(SWITCH_VIDEO); 482 } 483 } 484 startPlayVideoActivity()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 onClick(View v)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 onShutterButtonFocus(ShutterButton button, boolean pressed)515 public void onShutterButtonFocus(ShutterButton button, boolean pressed) { 516 // Do nothing (everything happens in onShutterButtonClick). 517 } 518 onStopVideoRecording(boolean valid)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 onShutterButtonClick(ShutterButton button)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 updateAndShowStorageHint(boolean mayHaveSd)550 private void updateAndShowStorageHint(boolean mayHaveSd) { 551 mStorageStatus = getStorageStatus(mayHaveSd); 552 showStorageHint(); 553 } 554 showStorageHint()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 getStorageStatus(boolean mayHaveSd)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 readVideoPreferences()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 resizeForPreviewAspectRatio()624 private void resizeForPreviewAspectRatio() { 625 mPreviewFrameLayout.setAspectRatio( 626 (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); 627 } 628 629 @Override onResume()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 setPreviewDisplay(SurfaceHolder holder)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 startPreview()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 closeCamera()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 onPause()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 onUserInteraction()759 public void onUserInteraction() { 760 super.onUserInteraction(); 761 if (!mMediaRecorderRecording) keepScreenOnAwhile(); 762 } 763 764 @Override onBackPressed()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 onKeyDown(int keyCode, KeyEvent event)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 onKeyUp(int keyCode, KeyEvent event)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 surfaceChanged(SurfaceHolder holder, int format, int w, int h)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 surfaceCreated(SurfaceHolder holder)849 public void surfaceCreated(SurfaceHolder holder) { 850 } 851 surfaceDestroyed(SurfaceHolder holder)852 public void surfaceDestroyed(SurfaceHolder holder) { 853 mSurfaceHolder = null; 854 } 855 gotoGallery()856 private void gotoGallery() { 857 MenuHelper.gotoCameraVideoGallery(this); 858 } 859 860 @Override onCreateOptionsMenu(Menu menu)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 isVideoCaptureIntent()873 private boolean isVideoCaptureIntent() { 874 String action = getIntent().getAction(); 875 return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); 876 } 877 doReturnToCaller(boolean valid)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 */ getAvailableStorage()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 cleanupEmptyFile()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. initializeRecorder()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 releaseMediaRecorder()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. Camera 1041 // device may be null if the activity is paused. 1042 if (mCameraDevice != null) mCameraDevice.lock(); 1043 } 1044 createVideoPath()1045 private void createVideoPath() { 1046 long dateTaken = System.currentTimeMillis(); 1047 String title = createName(dateTaken); 1048 String filename = title + ".3gp"; // Used when emailing. 1049 String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME; 1050 String filePath = cameraDirPath + "/" + filename; 1051 File cameraDir = new File(cameraDirPath); 1052 cameraDir.mkdirs(); 1053 ContentValues values = new ContentValues(7); 1054 values.put(Video.Media.TITLE, title); 1055 values.put(Video.Media.DISPLAY_NAME, filename); 1056 values.put(Video.Media.DATE_TAKEN, dateTaken); 1057 values.put(Video.Media.MIME_TYPE, "video/3gpp"); 1058 values.put(Video.Media.DATA, filePath); 1059 mVideoFilename = filePath; 1060 Log.v(TAG, "Current camera video filename: " + mVideoFilename); 1061 mCurrentVideoValues = values; 1062 } 1063 registerVideo()1064 private void registerVideo() { 1065 if (mVideoFileDescriptor == null) { 1066 Uri videoTable = Uri.parse("content://media/external/video/media"); 1067 mCurrentVideoValues.put(Video.Media.SIZE, 1068 new File(mCurrentVideoFilename).length()); 1069 try { 1070 mCurrentVideoUri = mContentResolver.insert(videoTable, 1071 mCurrentVideoValues); 1072 } catch (Exception e) { 1073 // We failed to insert into the database. This can happen if 1074 // the SD card is unmounted. 1075 mCurrentVideoUri = null; 1076 mCurrentVideoFilename = null; 1077 } finally { 1078 Log.v(TAG, "Current video URI: " + mCurrentVideoUri); 1079 } 1080 } 1081 mCurrentVideoValues = null; 1082 } 1083 deleteCurrentVideo()1084 private void deleteCurrentVideo() { 1085 if (mCurrentVideoFilename != null) { 1086 deleteVideoFile(mCurrentVideoFilename); 1087 mCurrentVideoFilename = null; 1088 } 1089 if (mCurrentVideoUri != null) { 1090 mContentResolver.delete(mCurrentVideoUri, null, null); 1091 mCurrentVideoUri = null; 1092 } 1093 updateAndShowStorageHint(true); 1094 } 1095 deleteVideoFile(String fileName)1096 private void deleteVideoFile(String fileName) { 1097 Log.v(TAG, "Deleting video " + fileName); 1098 File f = new File(fileName); 1099 if (!f.delete()) { 1100 Log.v(TAG, "Could not delete " + fileName); 1101 } 1102 } 1103 addBaseMenuItems(Menu menu)1104 private void addBaseMenuItems(Menu menu) { 1105 MenuHelper.addSwitchModeMenuItem(menu, false, new Runnable() { 1106 public void run() { 1107 switchToCameraMode(); 1108 } 1109 }); 1110 MenuItem gallery = menu.add(Menu.NONE, Menu.NONE, 1111 MenuHelper.POSITION_GOTO_GALLERY, 1112 R.string.camera_gallery_photos_text) 1113 .setOnMenuItemClickListener( 1114 new OnMenuItemClickListener() { 1115 public boolean onMenuItemClick(MenuItem item) { 1116 gotoGallery(); 1117 return true; 1118 } 1119 }); 1120 gallery.setIcon(android.R.drawable.ic_menu_gallery); 1121 mGalleryItems.add(gallery); 1122 1123 if (mNumberOfCameras > 1) { 1124 menu.add(Menu.NONE, Menu.NONE, 1125 MenuHelper.POSITION_SWITCH_CAMERA_ID, 1126 R.string.switch_camera_id) 1127 .setOnMenuItemClickListener(new OnMenuItemClickListener() { 1128 public boolean onMenuItemClick(MenuItem item) { 1129 switchCameraId((mCameraId + 1) % mNumberOfCameras); 1130 return true; 1131 } 1132 }).setIcon(android.R.drawable.ic_menu_camera); 1133 } 1134 } 1135 switchCameraId(int cameraId)1136 private void switchCameraId(int cameraId) { 1137 if (mPausing) return; 1138 mCameraId = cameraId; 1139 CameraSettings.writePreferredCameraId(mPreferences, cameraId); 1140 1141 // This is similar to what mShutterButton.performClick() does, 1142 // but not quite the same. 1143 if (mMediaRecorderRecording) { 1144 if (mIsVideoCaptureIntent) { 1145 stopVideoRecording(); 1146 showAlert(); 1147 } else { 1148 stopVideoRecordingAndGetThumbnail(); 1149 } 1150 } else { 1151 stopVideoRecording(); 1152 } 1153 closeCamera(); 1154 1155 // Reload the preferences. 1156 mPreferences.setLocalId(this, mCameraId); 1157 CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); 1158 // Read media profile again because camera id is changed. 1159 readVideoPreferences(); 1160 resizeForPreviewAspectRatio(); 1161 restartPreview(); 1162 1163 // Reload the UI. 1164 initializeHeadUpDisplay(); 1165 } 1166 filterPreferenceScreenByIntent( PreferenceGroup screen)1167 private PreferenceGroup filterPreferenceScreenByIntent( 1168 PreferenceGroup screen) { 1169 Intent intent = getIntent(); 1170 if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) { 1171 CameraSettings.removePreferenceFromScreen(screen, 1172 CameraSettings.KEY_VIDEO_QUALITY); 1173 } 1174 1175 if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) { 1176 CameraSettings.removePreferenceFromScreen(screen, 1177 CameraSettings.KEY_VIDEO_QUALITY); 1178 } 1179 return screen; 1180 } 1181 1182 // from MediaRecorder.OnErrorListener onError(MediaRecorder mr, int what, int extra)1183 public void onError(MediaRecorder mr, int what, int extra) { 1184 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 1185 // We may have run out of space on the sdcard. 1186 stopVideoRecording(); 1187 updateAndShowStorageHint(true); 1188 } 1189 } 1190 1191 // from MediaRecorder.OnInfoListener onInfo(MediaRecorder mr, int what, int extra)1192 public void onInfo(MediaRecorder mr, int what, int extra) { 1193 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1194 if (mMediaRecorderRecording) onStopVideoRecording(true); 1195 } else if (what 1196 == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1197 if (mMediaRecorderRecording) onStopVideoRecording(true); 1198 1199 // Show the toast. 1200 Toast.makeText(VideoCamera.this, R.string.video_reach_size_limit, 1201 Toast.LENGTH_LONG).show(); 1202 } 1203 } 1204 1205 /* 1206 * Make sure we're not recording music playing in the background, ask the 1207 * MediaPlaybackService to pause playback. 1208 */ pauseAudioPlayback()1209 private void pauseAudioPlayback() { 1210 // Shamelessly copied from MediaPlaybackService.java, which 1211 // should be public, but isn't. 1212 Intent i = new Intent("com.android.music.musicservicecommand"); 1213 i.putExtra("command", "pause"); 1214 1215 sendBroadcast(i); 1216 } 1217 startVideoRecording()1218 private void startVideoRecording() { 1219 Log.v(TAG, "startVideoRecording"); 1220 if (mStorageStatus != STORAGE_STATUS_OK) { 1221 Log.v(TAG, "Storage issue, ignore the start request"); 1222 return; 1223 } 1224 1225 initializeRecorder(); 1226 if (mMediaRecorder == null) { 1227 Log.e(TAG, "Fail to initialize media recorder"); 1228 return; 1229 } 1230 1231 pauseAudioPlayback(); 1232 1233 try { 1234 mMediaRecorder.start(); // Recording is now started 1235 } catch (RuntimeException e) { 1236 Log.e(TAG, "Could not start media recorder. ", e); 1237 releaseMediaRecorder(); 1238 return; 1239 } 1240 mHeadUpDisplay.setEnabled(false); 1241 1242 mMediaRecorderRecording = true; 1243 mRecordingStartTime = SystemClock.uptimeMillis(); 1244 updateRecordingIndicator(false); 1245 // Rotate the recording time. 1246 mRecordingTimeRect.setOrientation(mOrientationCompensation); 1247 mRecordingTimeView.setText(""); 1248 mRecordingTimeView.setVisibility(View.VISIBLE); 1249 updateRecordingTime(); 1250 keepScreenOn(); 1251 } 1252 updateRecordingIndicator(boolean showRecording)1253 private void updateRecordingIndicator(boolean showRecording) { 1254 int drawableId = 1255 showRecording ? R.drawable.btn_ic_video_record 1256 : R.drawable.btn_ic_video_record_stop; 1257 Drawable drawable = getResources().getDrawable(drawableId); 1258 mShutterButton.setImageDrawable(drawable); 1259 } 1260 stopVideoRecordingAndGetThumbnail()1261 private void stopVideoRecordingAndGetThumbnail() { 1262 stopVideoRecording(); 1263 acquireVideoThumb(); 1264 } 1265 stopVideoRecordingAndReturn(boolean valid)1266 private void stopVideoRecordingAndReturn(boolean valid) { 1267 stopVideoRecording(); 1268 doReturnToCaller(valid); 1269 } 1270 stopVideoRecordingAndShowAlert()1271 private void stopVideoRecordingAndShowAlert() { 1272 stopVideoRecording(); 1273 showAlert(); 1274 } 1275 showAlert()1276 private void showAlert() { 1277 fadeOut(findViewById(R.id.shutter_button)); 1278 if (mCurrentVideoFilename != null) { 1279 Bitmap src = ThumbnailUtils.createVideoThumbnail( 1280 mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); 1281 // MetadataRetriever already rotates the thumbnail. We should rotate 1282 // it back (and mirror if it is front-facing camera). 1283 CameraInfo[] info = CameraHolder.instance().getCameraInfo(); 1284 if (info[mCameraId].facing == CameraInfo.CAMERA_FACING_BACK) { 1285 src = Util.rotateAndMirror(src, -mOrientationHint, false); 1286 } else { 1287 src = Util.rotateAndMirror(src, -mOrientationHint, true); 1288 } 1289 mVideoFrame.setImageBitmap(src); 1290 mVideoFrame.setVisibility(View.VISIBLE); 1291 } 1292 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1293 for (int id : pickIds) { 1294 View button = findViewById(id); 1295 fadeIn(((View) button.getParent())); 1296 } 1297 } 1298 hideAlert()1299 private void hideAlert() { 1300 mVideoFrame.setVisibility(View.INVISIBLE); 1301 fadeIn(findViewById(R.id.shutter_button)); 1302 int[] pickIds = {R.id.btn_retake, R.id.btn_done, R.id.btn_play}; 1303 for (int id : pickIds) { 1304 View button = findViewById(id); 1305 fadeOut(((View) button.getParent())); 1306 } 1307 } 1308 fadeIn(View view)1309 private static void fadeIn(View view) { 1310 view.setVisibility(View.VISIBLE); 1311 Animation animation = new AlphaAnimation(0F, 1F); 1312 animation.setDuration(500); 1313 view.startAnimation(animation); 1314 } 1315 fadeOut(View view)1316 private static void fadeOut(View view) { 1317 view.setVisibility(View.INVISIBLE); 1318 Animation animation = new AlphaAnimation(1F, 0F); 1319 animation.setDuration(500); 1320 view.startAnimation(animation); 1321 } 1322 isAlertVisible()1323 private boolean isAlertVisible() { 1324 return this.mVideoFrame.getVisibility() == View.VISIBLE; 1325 } 1326 viewLastVideo()1327 private void viewLastVideo() { 1328 Intent intent = null; 1329 if (mThumbController.isUriValid()) { 1330 intent = new Intent(Util.REVIEW_ACTION, mThumbController.getUri()); 1331 try { 1332 startActivity(intent); 1333 } catch (ActivityNotFoundException ex) { 1334 try { 1335 intent = new Intent(Intent.ACTION_VIEW, mThumbController.getUri()); 1336 startActivity(intent); 1337 } catch (ActivityNotFoundException e) { 1338 Log.e(TAG, "review video fail", e); 1339 } 1340 } 1341 } else { 1342 Log.e(TAG, "Can't view last video."); 1343 } 1344 } 1345 stopVideoRecording()1346 private void stopVideoRecording() { 1347 Log.v(TAG, "stopVideoRecording"); 1348 if (mMediaRecorderRecording) { 1349 boolean needToRegisterRecording = false; 1350 mMediaRecorder.setOnErrorListener(null); 1351 mMediaRecorder.setOnInfoListener(null); 1352 try { 1353 mMediaRecorder.stop(); 1354 mCurrentVideoFilename = mVideoFilename; 1355 Log.v(TAG, "Setting current video filename: " 1356 + mCurrentVideoFilename); 1357 needToRegisterRecording = true; 1358 } catch (RuntimeException e) { 1359 Log.e(TAG, "stop fail: " + e.getMessage()); 1360 deleteVideoFile(mVideoFilename); 1361 } 1362 mMediaRecorderRecording = false; 1363 mHeadUpDisplay.setEnabled(true); 1364 updateRecordingIndicator(true); 1365 mRecordingTimeView.setVisibility(View.GONE); 1366 keepScreenOnAwhile(); 1367 if (needToRegisterRecording && mStorageStatus == STORAGE_STATUS_OK) { 1368 registerVideo(); 1369 } 1370 mVideoFilename = null; 1371 mVideoFileDescriptor = null; 1372 } 1373 releaseMediaRecorder(); // always release media recorder 1374 } 1375 resetScreenOn()1376 private void resetScreenOn() { 1377 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1378 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1379 } 1380 keepScreenOnAwhile()1381 private void keepScreenOnAwhile() { 1382 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1383 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1384 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1385 } 1386 keepScreenOn()1387 private void keepScreenOn() { 1388 mHandler.removeMessages(CLEAR_SCREEN_DELAY); 1389 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1390 } 1391 acquireVideoThumb()1392 private void acquireVideoThumb() { 1393 Bitmap videoFrame = ThumbnailUtils.createVideoThumbnail( 1394 mCurrentVideoFilename, Video.Thumbnails.MINI_KIND); 1395 mThumbController.setData(mCurrentVideoUri, videoFrame); 1396 mThumbController.updateDisplayIfNeeded(); 1397 } 1398 dataLocation()1399 private static ImageManager.DataLocation dataLocation() { 1400 return ImageManager.DataLocation.EXTERNAL; 1401 } 1402 updateThumbnailButton()1403 private void updateThumbnailButton() { 1404 // Update the last video thumbnail. 1405 if (!mIsVideoCaptureIntent) { 1406 if (!mThumbController.isUriValid()) { 1407 updateLastVideo(); 1408 } 1409 mThumbController.updateDisplayIfNeeded(); 1410 } 1411 } 1412 updateLastVideo()1413 private void updateLastVideo() { 1414 IImageList list = ImageManager.makeImageList( 1415 mContentResolver, 1416 dataLocation(), 1417 ImageManager.INCLUDE_VIDEOS, 1418 ImageManager.SORT_ASCENDING, 1419 ImageManager.CAMERA_IMAGE_BUCKET_ID); 1420 int count = list.getCount(); 1421 if (count > 0) { 1422 IImage image = list.getImageAt(count - 1); 1423 Uri uri = image.fullSizeImageUri(); 1424 mThumbController.setData(uri, image.miniThumbBitmap()); 1425 } else { 1426 mThumbController.setData(null, null); 1427 } 1428 list.close(); 1429 } 1430 updateRecordingTime()1431 private void updateRecordingTime() { 1432 if (!mMediaRecorderRecording) { 1433 return; 1434 } 1435 long now = SystemClock.uptimeMillis(); 1436 long delta = now - mRecordingStartTime; 1437 1438 // Starting a minute before reaching the max duration 1439 // limit, we'll countdown the remaining time instead. 1440 boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0 1441 && delta >= mMaxVideoDurationInMs - 60000); 1442 1443 long next_update_delay = 1000 - (delta % 1000); 1444 long seconds; 1445 if (countdownRemainingTime) { 1446 delta = Math.max(0, mMaxVideoDurationInMs - delta); 1447 seconds = (delta + 999) / 1000; 1448 } else { 1449 seconds = delta / 1000; // round to nearest 1450 } 1451 1452 long minutes = seconds / 60; 1453 long hours = minutes / 60; 1454 long remainderMinutes = minutes - (hours * 60); 1455 long remainderSeconds = seconds - (minutes * 60); 1456 1457 String secondsString = Long.toString(remainderSeconds); 1458 if (secondsString.length() < 2) { 1459 secondsString = "0" + secondsString; 1460 } 1461 String minutesString = Long.toString(remainderMinutes); 1462 if (minutesString.length() < 2) { 1463 minutesString = "0" + minutesString; 1464 } 1465 String text = minutesString + ":" + secondsString; 1466 if (hours > 0) { 1467 String hoursString = Long.toString(hours); 1468 if (hoursString.length() < 2) { 1469 hoursString = "0" + hoursString; 1470 } 1471 text = hoursString + ":" + text; 1472 } 1473 mRecordingTimeView.setText(text); 1474 1475 if (mRecordingTimeCountsDown != countdownRemainingTime) { 1476 // Avoid setting the color on every update, do it only 1477 // when it needs changing. 1478 mRecordingTimeCountsDown = countdownRemainingTime; 1479 1480 int color = getResources().getColor(countdownRemainingTime 1481 ? R.color.recording_time_remaining_text 1482 : R.color.recording_time_elapsed_text); 1483 1484 mRecordingTimeView.setTextColor(color); 1485 } 1486 1487 mHandler.sendEmptyMessageDelayed( 1488 UPDATE_RECORD_TIME, next_update_delay); 1489 } 1490 isSupported(String value, List<String> supported)1491 private static boolean isSupported(String value, List<String> supported) { 1492 return supported == null ? false : supported.indexOf(value) >= 0; 1493 } 1494 setCameraParameters()1495 private void setCameraParameters() { 1496 mParameters = mCameraDevice.getParameters(); 1497 1498 mParameters.setPreviewSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); 1499 mParameters.setPreviewFrameRate(mProfile.videoFrameRate); 1500 1501 // Set flash mode. 1502 String flashMode = mPreferences.getString( 1503 CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, 1504 getString(R.string.pref_camera_video_flashmode_default)); 1505 List<String> supportedFlash = mParameters.getSupportedFlashModes(); 1506 if (isSupported(flashMode, supportedFlash)) { 1507 mParameters.setFlashMode(flashMode); 1508 } else { 1509 flashMode = mParameters.getFlashMode(); 1510 if (flashMode == null) { 1511 flashMode = getString( 1512 R.string.pref_camera_flashmode_no_flash); 1513 } 1514 } 1515 1516 // Set white balance parameter. 1517 String whiteBalance = mPreferences.getString( 1518 CameraSettings.KEY_WHITE_BALANCE, 1519 getString(R.string.pref_camera_whitebalance_default)); 1520 if (isSupported(whiteBalance, 1521 mParameters.getSupportedWhiteBalance())) { 1522 mParameters.setWhiteBalance(whiteBalance); 1523 } else { 1524 whiteBalance = mParameters.getWhiteBalance(); 1525 if (whiteBalance == null) { 1526 whiteBalance = Parameters.WHITE_BALANCE_AUTO; 1527 } 1528 } 1529 1530 // Set color effect parameter. 1531 String colorEffect = mPreferences.getString( 1532 CameraSettings.KEY_COLOR_EFFECT, 1533 getString(R.string.pref_camera_coloreffect_default)); 1534 if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) { 1535 mParameters.setColorEffect(colorEffect); 1536 } 1537 1538 mCameraDevice.setParameters(mParameters); 1539 // Keep preview size up to date. 1540 mParameters = mCameraDevice.getParameters(); 1541 } 1542 switchToCameraMode()1543 private boolean switchToCameraMode() { 1544 if (isFinishing() || mMediaRecorderRecording) return false; 1545 MenuHelper.gotoCameraMode(this); 1546 finish(); 1547 return true; 1548 } 1549 onSwitchChanged(Switcher source, boolean onOff)1550 public boolean onSwitchChanged(Switcher source, boolean onOff) { 1551 if (onOff == SWITCH_CAMERA) { 1552 return switchToCameraMode(); 1553 } else { 1554 return true; 1555 } 1556 } 1557 1558 @Override onConfigurationChanged(Configuration config)1559 public void onConfigurationChanged(Configuration config) { 1560 super.onConfigurationChanged(config); 1561 1562 // If the camera resumes behind the lock screen, the orientation 1563 // will be portrait. That causes OOM when we try to allocation GPU 1564 // memory for the GLSurfaceView again when the orientation changes. So, 1565 // we delayed initialization of HeadUpDisplay until the orientation 1566 // becomes landscape. 1567 changeHeadUpDisplayState(); 1568 } 1569 resetCameraParameters()1570 private void resetCameraParameters() { 1571 // We need to restart the preview if preview size is changed. 1572 Size size = mParameters.getPreviewSize(); 1573 if (size.width != mProfile.videoFrameWidth 1574 || size.height != mProfile.videoFrameHeight) { 1575 // It is assumed media recorder is released before 1576 // onSharedPreferenceChanged, so we can close the camera here. 1577 closeCamera(); 1578 resizeForPreviewAspectRatio(); 1579 restartPreview(); // Parameters will be set in startPreview(). 1580 } else { 1581 setCameraParameters(); 1582 } 1583 } 1584 onSizeChanged()1585 public void onSizeChanged() { 1586 // TODO: update the content on GLRootView 1587 } 1588 1589 private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener { onSharedPreferencesChanged()1590 public void onSharedPreferencesChanged() { 1591 mHandler.post(new Runnable() { 1592 public void run() { 1593 VideoCamera.this.onSharedPreferencesChanged(); 1594 } 1595 }); 1596 } 1597 onRestorePreferencesClicked()1598 public void onRestorePreferencesClicked() { 1599 mHandler.post(new Runnable() { 1600 public void run() { 1601 VideoCamera.this.onRestorePreferencesClicked(); 1602 } 1603 }); 1604 } 1605 onPopupWindowVisibilityChanged(final int visibility)1606 public void onPopupWindowVisibilityChanged(final int visibility) { 1607 } 1608 } 1609 onRestorePreferencesClicked()1610 private void onRestorePreferencesClicked() { 1611 Runnable runnable = new Runnable() { 1612 public void run() { 1613 mHeadUpDisplay.restorePreferences(mParameters); 1614 } 1615 }; 1616 MenuHelper.confirmAction(this, 1617 getString(R.string.confirm_restore_title), 1618 getString(R.string.confirm_restore_message), 1619 runnable); 1620 } 1621 onSharedPreferencesChanged()1622 private void onSharedPreferencesChanged() { 1623 // ignore the events after "onPause()" or preview has not started yet 1624 if (mPausing) return; 1625 synchronized (mPreferences) { 1626 readVideoPreferences(); 1627 // If mCameraDevice is not ready then we can set the parameter in 1628 // startPreview(). 1629 if (mCameraDevice == null) return; 1630 1631 // Check if camera id is changed. 1632 int cameraId = CameraSettings.readPreferredCameraId(mPreferences); 1633 if (mCameraId != cameraId) { 1634 switchCameraId(cameraId); 1635 } else { 1636 resetCameraParameters(); 1637 } 1638 } 1639 } 1640 } 1641