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