1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.testingcamera; 18 19 import android.app.Activity; 20 import android.app.FragmentManager; 21 import android.hardware.Camera; 22 import android.hardware.Camera.Parameters; 23 import android.media.CamcorderProfile; 24 import android.media.MediaRecorder; 25 import android.media.MediaScannerConnection; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.Environment; 29 import android.os.Handler; 30 import android.view.View; 31 import android.view.SurfaceHolder; 32 import android.view.SurfaceView; 33 import android.view.View.OnClickListener; 34 import android.widget.AdapterView; 35 import android.widget.AdapterView.OnItemSelectedListener; 36 import android.widget.ArrayAdapter; 37 import android.widget.Button; 38 import android.widget.LinearLayout.LayoutParams; 39 import android.widget.Spinner; 40 import android.widget.TextView; 41 import android.widget.ToggleButton; 42 import android.text.Layout; 43 import android.text.method.ScrollingMovementMethod; 44 import android.util.Log; 45 46 import java.io.File; 47 import java.io.IOException; 48 import java.io.PrintWriter; 49 import java.io.StringWriter; 50 import java.text.SimpleDateFormat; 51 import java.util.ArrayList; 52 import java.util.Date; 53 import java.util.HashSet; 54 import java.util.List; 55 import java.util.Set; 56 57 /** 58 * A simple test application for the camera API. 59 * 60 * The goal of this application is to allow all camera API features to be 61 * exercised, and all information provided by the API to be shown. 62 */ 63 public class TestingCamera extends Activity implements SurfaceHolder.Callback { 64 65 /** UI elements */ 66 private SurfaceView mPreviewView; 67 private SurfaceHolder mPreviewHolder; 68 69 private Spinner mCameraSpinner; 70 private Button mInfoButton; 71 private Spinner mPreviewSizeSpinner; 72 private ToggleButton mPreviewToggle; 73 private Spinner mAutofocusModeSpinner; 74 private Button mAutofocusButton; 75 private Button mCancelAutofocusButton; 76 private Spinner mSnapshotSizeSpinner; 77 private Button mTakePictureButton; 78 private Spinner mCamcorderProfileSpinner; 79 private ToggleButton mRecordToggle; 80 81 private TextView mLogView; 82 83 private Set<View> mPreviewOnlyControls = new HashSet<View>(); 84 85 /** Camera state */ 86 private int mCameraId = 0; 87 private Camera mCamera; 88 private Camera.Parameters mParams; 89 private List<Camera.Size> mPreviewSizes; 90 private int mPreviewSize = 0; 91 private List<String> mAfModes; 92 private int mAfMode = 0; 93 private List<Camera.Size> mSnapshotSizes; 94 private int mSnapshotSize = 0; 95 private List<CamcorderProfile> mCamcorderProfiles; 96 private int mCamcorderProfile = 0; 97 98 private MediaRecorder mRecorder; 99 private File mRecordingFile; 100 101 private static final int CAMERA_UNINITIALIZED = 0; 102 private static final int CAMERA_OPEN = 1; 103 private static final int CAMERA_PREVIEW = 2; 104 private static final int CAMERA_TAKE_PICTURE = 3; 105 private static final int CAMERA_RECORD = 4; 106 private int mState = CAMERA_UNINITIALIZED; 107 108 /** Misc variables */ 109 110 private static final String TAG = "TestingCamera"; 111 112 /** Activity lifecycle */ 113 114 @Override 115 public void onCreate(Bundle savedInstanceState) { 116 super.onCreate(savedInstanceState); 117 118 setContentView(R.layout.main); 119 120 mPreviewView = (SurfaceView)findViewById(R.id.preview); 121 mPreviewView.getHolder().addCallback(this); 122 123 mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner); 124 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 125 126 mInfoButton = (Button) findViewById(R.id.info_button); 127 mInfoButton.setOnClickListener(mInfoButtonListener); 128 129 mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner); 130 mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener); 131 132 mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview); 133 mPreviewToggle.setOnClickListener(mPreviewToggleListener); 134 135 mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner); 136 mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener); 137 138 mAutofocusButton = (Button) findViewById(R.id.af_button); 139 mAutofocusButton.setOnClickListener(mAutofocusButtonListener); 140 mPreviewOnlyControls.add(mAutofocusButton); 141 142 mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button); 143 mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener); 144 mPreviewOnlyControls.add(mCancelAutofocusButton); 145 146 mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner); 147 mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener); 148 149 mTakePictureButton = (Button) findViewById(R.id.take_picture); 150 mTakePictureButton.setOnClickListener(mTakePictureListener); 151 mPreviewOnlyControls.add(mTakePictureButton); 152 153 mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner); 154 mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener); 155 156 mRecordToggle = (ToggleButton) findViewById(R.id.start_record); 157 mRecordToggle.setOnClickListener(mRecordToggleListener); 158 mPreviewOnlyControls.add(mRecordToggle); 159 160 mLogView = (TextView) findViewById(R.id.log); 161 mLogView.setMovementMethod(new ScrollingMovementMethod()); 162 163 int numCameras = Camera.getNumberOfCameras(); 164 String[] cameraNames = new String[numCameras]; 165 for (int i = 0; i < numCameras; i++) { 166 cameraNames[i] = "Camera " + i; 167 } 168 169 mCameraSpinner.setAdapter( 170 new ArrayAdapter<String>(this, 171 R.layout.spinner_item, cameraNames)); 172 } 173 174 @Override 175 public void onResume() { 176 super.onResume(); 177 log("onResume: Setting up camera"); 178 mPreviewHolder = null; 179 setUpCamera(); 180 } 181 182 @Override 183 public void onPause() { 184 super.onPause(); 185 log("onPause: Releasing camera"); 186 mCamera.release(); 187 mState = CAMERA_UNINITIALIZED; 188 } 189 190 /** SurfaceHolder.Callback methods */ 191 public void surfaceChanged(SurfaceHolder holder, 192 int format, 193 int width, 194 int height) { 195 if (mPreviewHolder != null) return; 196 197 log("Surface holder available: " + width + " x " + height); 198 mPreviewHolder = holder; 199 try { 200 mCamera.setPreviewDisplay(holder); 201 } catch (IOException e) { 202 logE("Unable to set up preview!"); 203 } 204 } 205 206 public void surfaceCreated(SurfaceHolder holder) { 207 208 } 209 210 public void surfaceDestroyed(SurfaceHolder holder) { 211 mPreviewHolder = null; 212 } 213 214 /** UI controls enable/disable */ 215 private void enablePreviewOnlyControls(boolean enabled) { 216 for (View v : mPreviewOnlyControls) { 217 v.setEnabled(enabled); 218 } 219 } 220 221 /** UI listeners */ 222 223 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 224 new AdapterView.OnItemSelectedListener() { 225 public void onItemSelected(AdapterView<?> parent, 226 View view, int pos, long id) { 227 if (mCameraId != pos) { 228 mCameraId = pos; 229 setUpCamera(); 230 } 231 } 232 233 public void onNothingSelected(AdapterView<?> parent) { 234 235 } 236 }; 237 238 private OnClickListener mInfoButtonListener = new OnClickListener() { 239 public void onClick(View v) { 240 FragmentManager fm = getFragmentManager(); 241 InfoDialogFragment infoDialog = new InfoDialogFragment(); 242 infoDialog.updateInfo(mCameraId, mCamera); 243 infoDialog.show(fm, "info_dialog_fragment"); 244 } 245 }; 246 247 private AdapterView.OnItemSelectedListener mPreviewSizeListener = 248 new AdapterView.OnItemSelectedListener() { 249 public void onItemSelected(AdapterView<?> parent, 250 View view, int pos, long id) { 251 if (pos == mPreviewSize) return; 252 if (mState == CAMERA_PREVIEW) { 253 log("Stopping preview to switch resolutions"); 254 mCamera.stopPreview(); 255 } 256 257 mPreviewSize = pos; 258 int width = mPreviewSizes.get(mPreviewSize).width; 259 int height = mPreviewSizes.get(mPreviewSize).height; 260 mParams.setPreviewSize(width, height); 261 262 log("Setting preview size to " + width + "x" + height); 263 264 mCamera.setParameters(mParams); 265 266 if (mState == CAMERA_PREVIEW) { 267 log("Restarting preview"); 268 resizePreview(width, height); 269 mCamera.startPreview(); 270 } 271 } 272 273 public void onNothingSelected(AdapterView<?> parent) { 274 275 } 276 }; 277 278 private View.OnClickListener mPreviewToggleListener = 279 new View.OnClickListener() { 280 public void onClick(View v) { 281 if (mState == CAMERA_TAKE_PICTURE) { 282 logE("Can't change preview state while taking picture!"); 283 return; 284 } 285 if (mPreviewToggle.isChecked()) { 286 log("Starting preview"); 287 resizePreview(mPreviewSizes.get(mPreviewSize).width, 288 mPreviewSizes.get(mPreviewSize).height); 289 mCamera.startPreview(); 290 mState = CAMERA_PREVIEW; 291 enablePreviewOnlyControls(true); 292 } else { 293 log("Stopping preview"); 294 mCamera.stopPreview(); 295 mState = CAMERA_OPEN; 296 297 enablePreviewOnlyControls(false); 298 } 299 } 300 }; 301 302 private OnItemSelectedListener mAutofocusModeListener = 303 new OnItemSelectedListener() { 304 public void onItemSelected(AdapterView<?> parent, 305 View view, int pos, long id) { 306 if (pos == mAfMode) return; 307 308 mAfMode = pos; 309 String focusMode = mAfModes.get(mAfMode); 310 log("Setting focus mode to " + focusMode); 311 if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE || 312 focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) { 313 mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback); 314 } 315 mParams.setFocusMode(focusMode); 316 317 mCamera.setParameters(mParams); 318 } 319 320 public void onNothingSelected(AdapterView<?> arg0) { 321 322 } 323 }; 324 325 private OnClickListener mAutofocusButtonListener = 326 new View.OnClickListener() { 327 public void onClick(View v) { 328 log("Triggering autofocus"); 329 mCamera.autoFocus(mAutofocusCallback); 330 } 331 }; 332 333 private OnClickListener mCancelAutofocusButtonListener = 334 new View.OnClickListener() { 335 public void onClick(View v) { 336 log("Cancelling autofocus"); 337 mCamera.cancelAutoFocus(); 338 } 339 }; 340 341 private Camera.AutoFocusCallback mAutofocusCallback = 342 new Camera.AutoFocusCallback() { 343 public void onAutoFocus(boolean success, Camera camera) { 344 log("Autofocus completed: " + (success ? "success" : "failure") ); 345 } 346 }; 347 348 private Camera.AutoFocusMoveCallback mAutofocusMoveCallback = 349 new Camera.AutoFocusMoveCallback() { 350 public void onAutoFocusMoving(boolean start, Camera camera) { 351 log("Autofocus movement: " + (start ? "starting" : "stopped") ); 352 } 353 }; 354 355 private AdapterView.OnItemSelectedListener mSnapshotSizeListener = 356 new AdapterView.OnItemSelectedListener() { 357 public void onItemSelected(AdapterView<?> parent, 358 View view, int pos, long id) { 359 if (pos == mSnapshotSize) return; 360 361 mSnapshotSize = pos; 362 int width = mSnapshotSizes.get(mSnapshotSize).width; 363 int height = mSnapshotSizes.get(mSnapshotSize).height; 364 log("Setting snapshot size to " + width + " x " + height); 365 366 mParams.setPictureSize(width, height); 367 368 mCamera.setParameters(mParams); 369 } 370 371 public void onNothingSelected(AdapterView<?> parent) { 372 373 } 374 }; 375 376 private View.OnClickListener mTakePictureListener = 377 new View.OnClickListener() { 378 public void onClick(View v) { 379 log("Taking picture"); 380 if (mState == CAMERA_PREVIEW) { 381 mState = CAMERA_TAKE_PICTURE; 382 enablePreviewOnlyControls(false); 383 mPreviewToggle.setChecked(false); 384 385 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb); 386 } else { 387 logE("Can't take picture while not running preview!"); 388 } 389 } 390 }; 391 392 private AdapterView.OnItemSelectedListener mCamcorderProfileListener = 393 new AdapterView.OnItemSelectedListener() { 394 public void onItemSelected(AdapterView<?> parent, 395 View view, int pos, long id) { 396 if (pos == mCamcorderProfile) return; 397 398 log("Setting camcorder profile to " + ((TextView)view).getText()); 399 mCamcorderProfile = pos; 400 } 401 402 public void onNothingSelected(AdapterView<?> parent) { 403 404 } 405 }; 406 407 private View.OnClickListener mRecordToggleListener = 408 new View.OnClickListener() { 409 public void onClick(View v) { 410 mPreviewToggle.setEnabled(false); 411 if (mState == CAMERA_PREVIEW) { 412 startRecording(); 413 } else if (mState == CAMERA_RECORD) { 414 stopRecording(false); 415 } else { 416 logE("Can't toggle recording in current state!"); 417 } 418 mPreviewToggle.setEnabled(true); 419 } 420 }; 421 422 private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() { 423 public void onShutter() { 424 log("Shutter callback received"); 425 } 426 }; 427 428 private Camera.PictureCallback mRawCb = new Camera.PictureCallback() { 429 public void onPictureTaken(byte[] data, Camera camera) { 430 log("Raw callback received"); 431 } 432 }; 433 434 private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() { 435 public void onPictureTaken(byte[] data, Camera camera) { 436 log("Postview callback received"); 437 } 438 }; 439 440 private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() { 441 public void onPictureTaken(byte[] data, Camera camera) { 442 log("JPEG picture callback received"); 443 FragmentManager fm = getFragmentManager(); 444 SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment(); 445 446 snapshotDialog.updateImage(data); 447 snapshotDialog.show(fm, "snapshot_dialog_fragment"); 448 449 mPreviewToggle.setEnabled(true); 450 451 mState = CAMERA_OPEN; 452 } 453 }; 454 455 // Internal methods 456 457 void setUpCamera() { 458 log("Setting up camera " + mCameraId); 459 logIndent(1); 460 if (mState >= CAMERA_OPEN) { 461 log("Closing old camera"); 462 mCamera.release(); 463 mState = CAMERA_UNINITIALIZED; 464 } 465 log("Opening camera " + mCameraId); 466 mCamera = Camera.open(mCameraId); 467 mState = CAMERA_OPEN; 468 469 mParams = mCamera.getParameters(); 470 471 // Set up preview size selection 472 473 log("Configuring camera"); 474 logIndent(1); 475 476 updatePreviewSizes(mParams); 477 updateAfModes(mParams); 478 updateSnapshotSizes(mParams); 479 updateCamcorderProfile(mCameraId); 480 481 // Update parameters based on above updates 482 mCamera.setParameters(mParams); 483 484 if (mPreviewHolder != null) { 485 log("Setting preview display"); 486 try { 487 mCamera.setPreviewDisplay(mPreviewHolder); 488 } catch(IOException e) { 489 Log.e(TAG, "Unable to set up preview!"); 490 } 491 } 492 493 logIndent(-1); 494 495 mPreviewToggle.setEnabled(true); 496 mPreviewToggle.setChecked(false); 497 enablePreviewOnlyControls(false); 498 499 int width = mPreviewSizes.get(mPreviewSize).width; 500 int height = mPreviewSizes.get(mPreviewSize).height; 501 resizePreview(width, height); 502 if (mPreviewToggle.isChecked()) { 503 log("Starting preview" ); 504 mCamera.startPreview(); 505 mState = CAMERA_PREVIEW; 506 } else { 507 mState = CAMERA_OPEN; 508 } 509 logIndent(-1); 510 } 511 512 private void updateAfModes(Parameters params) { 513 mAfModes = params.getSupportedFocusModes(); 514 515 mAutofocusModeSpinner.setAdapter( 516 new ArrayAdapter<String>(this, R.layout.spinner_item, 517 mAfModes.toArray(new String[0]))); 518 519 mAfMode = 0; 520 521 params.setFocusMode(mAfModes.get(mAfMode)); 522 523 log("Setting AF mode to " + mAfModes.get(mAfMode)); 524 } 525 526 private void updatePreviewSizes(Camera.Parameters params) { 527 mPreviewSizes = params.getSupportedPreviewSizes(); 528 529 String[] availableSizeNames = new String[mPreviewSizes.size()]; 530 int i = 0; 531 for (Camera.Size previewSize: mPreviewSizes) { 532 availableSizeNames[i++] = 533 Integer.toString(previewSize.width) + " x " + 534 Integer.toString(previewSize.height); 535 } 536 mPreviewSizeSpinner.setAdapter( 537 new ArrayAdapter<String>( 538 this, R.layout.spinner_item, availableSizeNames)); 539 540 mPreviewSize = 0; 541 542 int width = mPreviewSizes.get(mPreviewSize).width; 543 int height = mPreviewSizes.get(mPreviewSize).height; 544 params.setPreviewSize(width, height); 545 log("Setting preview size to " + width + " x " + height); 546 } 547 548 private void updateSnapshotSizes(Camera.Parameters params) { 549 String[] availableSizeNames; 550 mSnapshotSizes = params.getSupportedPictureSizes(); 551 552 availableSizeNames = new String[mSnapshotSizes.size()]; 553 int i = 0; 554 for (Camera.Size snapshotSize : mSnapshotSizes) { 555 availableSizeNames[i++] = 556 Integer.toString(snapshotSize.width) + " x " + 557 Integer.toString(snapshotSize.height); 558 } 559 mSnapshotSizeSpinner.setAdapter( 560 new ArrayAdapter<String>( 561 this, R.layout.spinner_item, availableSizeNames)); 562 563 mSnapshotSize = 0; 564 565 int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width; 566 int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height; 567 params.setPictureSize(snapshotWidth, snapshotHeight); 568 log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight); 569 } 570 571 private void updateCamcorderProfile(int cameraId) { 572 // Have to query all of these individually, 573 final int PROFILES[] = new int[] { 574 CamcorderProfile.QUALITY_1080P, 575 CamcorderProfile.QUALITY_480P, 576 CamcorderProfile.QUALITY_720P, 577 CamcorderProfile.QUALITY_CIF, 578 CamcorderProfile.QUALITY_HIGH, 579 CamcorderProfile.QUALITY_LOW, 580 CamcorderProfile.QUALITY_QCIF, 581 CamcorderProfile.QUALITY_QVGA, 582 CamcorderProfile.QUALITY_TIME_LAPSE_1080P, 583 CamcorderProfile.QUALITY_TIME_LAPSE_480P, 584 CamcorderProfile.QUALITY_TIME_LAPSE_720P, 585 CamcorderProfile.QUALITY_TIME_LAPSE_CIF, 586 CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, 587 CamcorderProfile.QUALITY_TIME_LAPSE_LOW, 588 CamcorderProfile.QUALITY_TIME_LAPSE_QCIF, 589 CamcorderProfile.QUALITY_TIME_LAPSE_QVGA 590 }; 591 592 final String PROFILE_NAMES[] = new String[] { 593 "1080P", 594 "480P", 595 "720P", 596 "CIF", 597 "HIGH", 598 "LOW", 599 "QCIF", 600 "QVGA", 601 "TIME_LAPSE_1080P", 602 "TIME_LAPSE_480P", 603 "TIME_LAPSE_720P", 604 "TIME_LAPSE_CIF", 605 "TIME_LAPSE_HIGH", 606 "TIME_LAPSE_LOW", 607 "TIME_LAPSE_QCIF", 608 "TIME_LAPSE_QVGA" 609 }; 610 611 List<String> availableCamcorderProfileNames = new ArrayList<String>(); 612 mCamcorderProfiles = new ArrayList<CamcorderProfile>(); 613 614 for (int i = 0; i < PROFILES.length; i++) { 615 if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) { 616 availableCamcorderProfileNames.add(PROFILE_NAMES[i]); 617 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i])); 618 } 619 } 620 String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]); 621 mCamcorderProfileSpinner.setAdapter( 622 new ArrayAdapter<String>( 623 this, R.layout.spinner_item, nameArray)); 624 625 mCamcorderProfile = 0; 626 log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); 627 628 } 629 630 void resizePreview(int width, int height) { 631 if (mPreviewHolder != null) { 632 int viewHeight = mPreviewView.getHeight(); 633 int viewWidth = (int)(((double)width)/height * viewHeight); 634 635 mPreviewView.setLayoutParams( 636 new LayoutParams(viewWidth, viewHeight)); 637 } 638 639 } 640 641 static final int MEDIA_TYPE_IMAGE = 0; 642 static final int MEDIA_TYPE_VIDEO = 1; 643 File getOutputMediaFile(int type){ 644 // To be safe, you should check that the SDCard is mounted 645 // using Environment.getExternalStorageState() before doing this. 646 647 String state = Environment.getExternalStorageState(); 648 if (!Environment.MEDIA_MOUNTED.equals(state)) { 649 return null; 650 } 651 652 File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 653 Environment.DIRECTORY_DCIM), "TestingCamera"); 654 // This location works best if you want the created images to be shared 655 // between applications and persist after your app has been uninstalled. 656 657 // Create the storage directory if it does not exist 658 if (! mediaStorageDir.exists()){ 659 if (! mediaStorageDir.mkdirs()){ 660 logE("Failed to create directory for pictures/video"); 661 return null; 662 } 663 } 664 665 // Create a media file name 666 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 667 File mediaFile; 668 if (type == MEDIA_TYPE_IMAGE){ 669 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 670 "IMG_"+ timeStamp + ".jpg"); 671 } else if(type == MEDIA_TYPE_VIDEO) { 672 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 673 "VID_"+ timeStamp + ".mp4"); 674 } else { 675 return null; 676 } 677 678 return mediaFile; 679 } 680 681 void notifyMediaScannerOfFile(File newFile, 682 final MediaScannerConnection.OnScanCompletedListener listener) { 683 final Handler h = new Handler(); 684 MediaScannerConnection.scanFile(this, 685 new String[] { newFile.toString() }, 686 null, 687 new MediaScannerConnection.OnScanCompletedListener() { 688 public void onScanCompleted(final String path, final Uri uri) { 689 h.post(new Runnable() { 690 public void run() { 691 log("MediaScanner notified: " + 692 path + " -> " + uri); 693 if (listener != null) 694 listener.onScanCompleted(path, uri); 695 } 696 }); 697 } 698 }); 699 } 700 701 private void deleteFile(File badFile) { 702 if (badFile.exists()) { 703 boolean success = badFile.delete(); 704 if (success) log("Deleted file " + badFile.toString()); 705 else log("Unable to delete file " + badFile.toString()); 706 } 707 } 708 709 private void startRecording() { 710 log("Starting recording"); 711 logIndent(1); 712 log("Configuring MediaRecoder"); 713 mCamera.unlock(); 714 if (mRecorder != null) { 715 mRecorder.release(); 716 } 717 mRecorder = new MediaRecorder(); 718 mRecorder.setOnErrorListener(mRecordingErrorListener); 719 mRecorder.setOnInfoListener(mRecordingInfoListener); 720 mRecorder.setCamera(mCamera); 721 mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 722 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 723 mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); 724 File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); 725 log("File name:" + outputFile.toString()); 726 mRecorder.setOutputFile(outputFile.toString()); 727 728 boolean ready = false; 729 log("Preparing MediaRecorder"); 730 try { 731 mRecorder.prepare(); 732 ready = true; 733 } catch (Exception e) { 734 StringWriter writer = new StringWriter(); 735 e.printStackTrace(new PrintWriter(writer)); 736 logE("Exception preparing MediaRecorder:\n" + writer.toString()); 737 } 738 739 if (ready) { 740 try { 741 log("Starting MediaRecorder"); 742 mRecorder.start(); 743 mState = CAMERA_RECORD; 744 log("Recording active"); 745 mRecordingFile = outputFile; 746 } catch (Exception e) { 747 StringWriter writer = new StringWriter(); 748 e.printStackTrace(new PrintWriter(writer)); 749 logE("Exception starting MediaRecorder:\n" + writer.toString()); 750 } 751 } else { 752 mPreviewToggle.setChecked(false); 753 } 754 logIndent(-1); 755 } 756 757 private MediaRecorder.OnErrorListener mRecordingErrorListener = 758 new MediaRecorder.OnErrorListener() { 759 public void onError(MediaRecorder mr, int what, int extra) { 760 logE("MediaRecorder reports error: " + what + ", extra " 761 + extra); 762 if (mState == CAMERA_RECORD) { 763 stopRecording(true); 764 } 765 } 766 }; 767 768 private MediaRecorder.OnInfoListener mRecordingInfoListener = 769 new MediaRecorder.OnInfoListener() { 770 public void onInfo(MediaRecorder mr, int what, int extra) { 771 log("MediaRecorder reports info: " + what + ", extra " 772 + extra); 773 } 774 }; 775 776 private void stopRecording(boolean error) { 777 log("Stopping recording"); 778 if (mRecorder != null) { 779 mRecorder.stop(); 780 mCamera.lock(); 781 mState = CAMERA_PREVIEW; 782 if (!error) { 783 notifyMediaScannerOfFile(mRecordingFile, null); 784 } else { 785 deleteFile(mRecordingFile); 786 } 787 mRecordingFile = null; 788 } else { 789 logE("Recorder is unexpectedly null!"); 790 } 791 } 792 793 private int mLogIndentLevel = 0; 794 private String mLogIndent = "\t"; 795 /** Increment or decrement log indentation level */ 796 synchronized void logIndent(int delta) { 797 mLogIndentLevel += delta; 798 if (mLogIndentLevel < 0) mLogIndentLevel = 0; 799 char[] mLogIndentArray = new char[mLogIndentLevel + 1]; 800 for (int i = -1; i < mLogIndentLevel; i++) { 801 mLogIndentArray[i + 1] = '\t'; 802 } 803 mLogIndent = new String(mLogIndentArray); 804 } 805 806 SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); 807 /** Log both to log text view and to device logcat */ 808 void log(String logLine) { 809 Log.d(TAG, logLine); 810 logAndScrollToBottom(logLine, mLogIndent); 811 } 812 813 void logE(String logLine) { 814 Log.e(TAG, logLine); 815 logAndScrollToBottom(logLine, mLogIndent + "!!! "); 816 } 817 818 synchronized private void logAndScrollToBottom(String logLine, String logIndent) { 819 StringBuffer logEntry = new StringBuffer(32); 820 logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent); 821 logEntry.append(logLine); 822 mLogView.append(logEntry); 823 final Layout layout = mLogView.getLayout(); 824 if (layout != null){ 825 int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1) 826 - mLogView.getScrollY() - mLogView.getHeight(); 827 if(scrollDelta > 0) { 828 mLogView.scrollBy(0, scrollDelta); 829 } 830 } 831 } 832 833 }