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