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.Manifest; 20 import android.annotation.SuppressLint; 21 import android.app.Activity; 22 import android.app.FragmentManager; 23 import android.content.pm.PackageManager; 24 import android.content.res.Resources; 25 import android.graphics.ImageFormat; 26 import android.hardware.Camera; 27 import android.hardware.Camera.Parameters; 28 import android.hardware.Camera.ErrorCallback; 29 import android.media.CamcorderProfile; 30 import android.media.MediaRecorder; 31 import android.media.MediaScannerConnection; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.os.Environment; 35 import android.os.Handler; 36 import android.os.SystemClock; 37 import android.view.View; 38 import android.view.Surface; 39 import android.view.SurfaceHolder; 40 import android.view.SurfaceView; 41 import android.view.View.OnClickListener; 42 import android.widget.AdapterView; 43 import android.widget.AdapterView.OnItemSelectedListener; 44 import android.widget.ArrayAdapter; 45 import android.widget.Button; 46 import android.widget.CheckBox; 47 import android.widget.LinearLayout; 48 import android.widget.LinearLayout.LayoutParams; 49 import android.widget.SeekBar; 50 import android.widget.Spinner; 51 import android.widget.TextView; 52 import android.widget.ToggleButton; 53 import android.renderscript.RenderScript; 54 import android.text.Layout; 55 import android.text.method.ScrollingMovementMethod; 56 import android.util.Log; 57 import android.util.SparseArray; 58 59 import java.io.File; 60 import java.io.IOException; 61 import java.io.PrintWriter; 62 import java.io.StringWriter; 63 import java.text.SimpleDateFormat; 64 import java.util.ArrayList; 65 import java.util.Date; 66 import java.util.HashSet; 67 import java.util.List; 68 import java.util.Set; 69 70 /** 71 * A simple test application for the camera API. 72 * 73 * The goal of this application is to allow all camera API features to be 74 * exercised, and all information provided by the API to be shown. 75 */ 76 public class TestingCamera extends Activity 77 implements SurfaceHolder.Callback, Camera.PreviewCallback, 78 Camera.ErrorCallback { 79 80 /** UI elements */ 81 private SurfaceView mPreviewView; 82 private SurfaceHolder mPreviewHolder; 83 private LinearLayout mPreviewColumn; 84 85 private SurfaceView mCallbackView; 86 private SurfaceHolder mCallbackHolder; 87 88 private Spinner mCameraSpinner; 89 private CheckBox mKeepOpenCheckBox; 90 private Button mInfoButton; 91 private Spinner mPreviewSizeSpinner; 92 private Spinner mPreviewFrameRateSpinner; 93 private ToggleButton mPreviewToggle; 94 private ToggleButton mHDRToggle; 95 private Spinner mAutofocusModeSpinner; 96 private Button mAutofocusButton; 97 private Button mCancelAutofocusButton; 98 private TextView mFlashModeSpinnerLabel; 99 private Spinner mFlashModeSpinner; 100 private ToggleButton mExposureLockToggle; 101 private Spinner mSnapshotSizeSpinner; 102 private Button mTakePictureButton; 103 private Spinner mCamcorderProfileSpinner; 104 private Spinner mVideoRecordSizeSpinner; 105 private Spinner mVideoFrameRateSpinner; 106 private ToggleButton mRecordToggle; 107 private CheckBox mRecordHandoffCheckBox; 108 private ToggleButton mRecordStabilizationToggle; 109 private ToggleButton mRecordHintToggle; 110 private ToggleButton mLockCameraToggle; 111 private Spinner mCallbackFormatSpinner; 112 private ToggleButton mCallbackToggle; 113 private TextView mColorEffectSpinnerLabel; 114 private Spinner mColorEffectSpinner; 115 private SeekBar mZoomSeekBar; 116 117 private TextView mLogView; 118 119 SnapshotDialogFragment mSnapshotDialog = null; 120 121 private Set<View> mOpenOnlyControls = new HashSet<View>(); 122 private Set<View> mPreviewOnlyControls = new HashSet<View>(); 123 124 private SparseArray<String> mFormatNames; 125 126 /** Camera state */ 127 private int mCameraId; 128 private Camera mCamera; 129 private Camera.Parameters mParams; 130 private List<Camera.Size> mPreviewSizes; 131 private int mPreviewSize = 0; 132 private List<Integer> mPreviewFrameRates; 133 private int mPreviewFrameRate = 0; 134 private List<Integer> mPreviewFormats; 135 private int mPreviewFormat = 0; 136 private List<String> mAfModes; 137 private int mAfMode = 0; 138 private List<String> mFlashModes; 139 private int mFlashMode = 0; 140 private List<Camera.Size> mSnapshotSizes; 141 private int mSnapshotSize = 0; 142 private List<CamcorderProfile> mCamcorderProfiles; 143 private int mCamcorderProfile = 0; 144 private List<Camera.Size> mVideoRecordSizes; 145 private int mVideoRecordSize = 0; 146 private List<Integer> mVideoFrameRates; 147 private int mVideoFrameRate = 0; 148 private List<String> mColorEffects; 149 private int mColorEffect = 0; 150 private int mZoom = 0; 151 152 private MediaRecorder mRecorder; 153 private File mRecordingFile; 154 155 private RenderScript mRS; 156 157 private boolean mCallbacksEnabled = false; 158 private CallbackProcessor mCallbackProcessor = null; 159 long mLastCallbackTimestamp = -1; 160 float mCallbackAvgFrameDuration = 30; 161 int mCallbackFrameCount = 0; 162 private static final float MEAN_FPS_HISTORY_COEFF = 0.9f; 163 private static final float MEAN_FPS_MEASUREMENT_COEFF = 0.1f; 164 private static final int FPS_REPORTING_PERIOD = 200; // frames 165 private static final int CALLBACK_BUFFER_COUNT = 3; 166 167 private static final int CAMERA_UNINITIALIZED = 0; 168 private static final int CAMERA_OPEN = 1; 169 private static final int CAMERA_PREVIEW = 2; 170 private static final int CAMERA_TAKE_PICTURE = 3; 171 private static final int CAMERA_RECORD = 4; 172 private int mState = CAMERA_UNINITIALIZED; 173 174 private static final int NO_CAMERA_ID = -1; 175 176 /** Misc variables */ 177 178 private static final String TAG = "TestingCamera"; 179 private static final int PERMISSIONS_REQUEST_CAMERA = 1; 180 private static final int PERMISSIONS_REQUEST_RECORDING = 2; 181 static final int PERMISSIONS_REQUEST_SNAPSHOT = 3; 182 183 /** Activity lifecycle */ 184 185 @Override 186 public void onCreate(Bundle savedInstanceState) { 187 super.onCreate(savedInstanceState); 188 189 setContentView(R.layout.main); 190 191 mPreviewColumn = (LinearLayout) findViewById(R.id.preview_column); 192 193 mPreviewView = (SurfaceView) findViewById(R.id.preview); 194 mPreviewView.getHolder().addCallback(this); 195 196 mCallbackView = (SurfaceView)findViewById(R.id.callback_view); 197 198 mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner); 199 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 200 201 mKeepOpenCheckBox = (CheckBox) findViewById(R.id.keep_open_checkbox); 202 203 mInfoButton = (Button) findViewById(R.id.info_button); 204 mInfoButton.setOnClickListener(mInfoButtonListener); 205 mOpenOnlyControls.add(mInfoButton); 206 207 mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner); 208 mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener); 209 mOpenOnlyControls.add(mPreviewSizeSpinner); 210 211 mPreviewFrameRateSpinner = (Spinner) findViewById(R.id.preview_frame_rate_spinner); 212 mPreviewFrameRateSpinner.setOnItemSelectedListener(mPreviewFrameRateListener); 213 mOpenOnlyControls.add(mPreviewFrameRateSpinner); 214 215 mHDRToggle = (ToggleButton) findViewById(R.id.hdr_mode); 216 mHDRToggle.setOnClickListener(mHDRToggleListener); 217 mOpenOnlyControls.add(mHDRToggle); 218 219 mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview); 220 mPreviewToggle.setOnClickListener(mPreviewToggleListener); 221 mOpenOnlyControls.add(mPreviewToggle); 222 223 mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner); 224 mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener); 225 mOpenOnlyControls.add(mAutofocusModeSpinner); 226 227 mAutofocusButton = (Button) findViewById(R.id.af_button); 228 mAutofocusButton.setOnClickListener(mAutofocusButtonListener); 229 mPreviewOnlyControls.add(mAutofocusButton); 230 231 mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button); 232 mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener); 233 mPreviewOnlyControls.add(mCancelAutofocusButton); 234 235 mFlashModeSpinnerLabel = (TextView) findViewById(R.id.flash_mode_spinner_label); 236 237 mFlashModeSpinner = (Spinner) findViewById(R.id.flash_mode_spinner); 238 mFlashModeSpinner.setOnItemSelectedListener(mFlashModeListener); 239 mOpenOnlyControls.add(mFlashModeSpinner); 240 241 mExposureLockToggle = (ToggleButton) findViewById(R.id.exposure_lock); 242 mExposureLockToggle.setOnClickListener(mExposureLockToggleListener); 243 mOpenOnlyControls.add(mExposureLockToggle); 244 245 mZoomSeekBar = (SeekBar) findViewById(R.id.zoom_seekbar); 246 mZoomSeekBar.setOnSeekBarChangeListener(mZoomSeekBarListener); 247 248 mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner); 249 mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener); 250 mOpenOnlyControls.add(mSnapshotSizeSpinner); 251 252 mTakePictureButton = (Button) findViewById(R.id.take_picture); 253 mTakePictureButton.setOnClickListener(mTakePictureListener); 254 mPreviewOnlyControls.add(mTakePictureButton); 255 256 mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner); 257 mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener); 258 mOpenOnlyControls.add(mCamcorderProfileSpinner); 259 260 mVideoRecordSizeSpinner = (Spinner) findViewById(R.id.video_record_size_spinner); 261 mVideoRecordSizeSpinner.setOnItemSelectedListener(mVideoRecordSizeListener); 262 mOpenOnlyControls.add(mVideoRecordSizeSpinner); 263 264 mVideoFrameRateSpinner = (Spinner) findViewById(R.id.video_frame_rate_spinner); 265 mVideoFrameRateSpinner.setOnItemSelectedListener(mVideoFrameRateListener); 266 mOpenOnlyControls.add(mVideoFrameRateSpinner); 267 268 mRecordToggle = (ToggleButton) findViewById(R.id.start_record); 269 mRecordToggle.setOnClickListener(mRecordToggleListener); 270 mPreviewOnlyControls.add(mRecordToggle); 271 272 mRecordHandoffCheckBox = (CheckBox) findViewById(R.id.record_handoff_checkbox); 273 274 mRecordStabilizationToggle = (ToggleButton) findViewById(R.id.record_stabilization); 275 mRecordStabilizationToggle.setOnClickListener(mRecordStabilizationToggleListener); 276 mOpenOnlyControls.add(mRecordStabilizationToggle); 277 278 mRecordHintToggle = (ToggleButton) findViewById(R.id.record_hint); 279 mRecordHintToggle.setOnClickListener(mRecordHintToggleListener); 280 mOpenOnlyControls.add(mRecordHintToggle); 281 282 mLockCameraToggle = (ToggleButton) findViewById(R.id.lock_camera); 283 mLockCameraToggle.setOnClickListener(mLockCameraToggleListener); 284 mLockCameraToggle.setChecked(true); // ON by default 285 mOpenOnlyControls.add(mLockCameraToggle); 286 287 mCallbackFormatSpinner = (Spinner) findViewById(R.id.callback_format_spinner); 288 mCallbackFormatSpinner.setOnItemSelectedListener(mCallbackFormatListener); 289 mOpenOnlyControls.add(mCallbackFormatSpinner); 290 291 mCallbackToggle = (ToggleButton) findViewById(R.id.enable_callbacks); 292 mCallbackToggle.setOnClickListener(mCallbackToggleListener); 293 mOpenOnlyControls.add(mCallbackToggle); 294 295 mColorEffectSpinnerLabel = (TextView) findViewById(R.id.color_effect_spinner_label); 296 297 mColorEffectSpinner = (Spinner) findViewById(R.id.color_effect_spinner); 298 mColorEffectSpinner.setOnItemSelectedListener(mColorEffectListener); 299 mOpenOnlyControls.add(mColorEffectSpinner); 300 301 mLogView = (TextView) findViewById(R.id.log); 302 mLogView.setMovementMethod(new ScrollingMovementMethod()); 303 304 mOpenOnlyControls.addAll(mPreviewOnlyControls); 305 306 mFormatNames = new SparseArray<String>(7); 307 mFormatNames.append(ImageFormat.JPEG, "JPEG"); 308 mFormatNames.append(ImageFormat.NV16, "NV16"); 309 mFormatNames.append(ImageFormat.NV21, "NV21"); 310 mFormatNames.append(ImageFormat.RGB_565, "RGB_565"); 311 mFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN"); 312 mFormatNames.append(ImageFormat.YUY2, "YUY2"); 313 mFormatNames.append(ImageFormat.YV12, "YV12"); 314 315 int numCameras = Camera.getNumberOfCameras(); 316 String[] cameraNames = new String[numCameras + 1]; 317 cameraNames[0] = "None"; 318 for (int i = 0; i < numCameras; i++) { 319 cameraNames[i + 1] = "Camera " + i; 320 } 321 322 mCameraSpinner.setAdapter( 323 new ArrayAdapter<String>(this, 324 R.layout.spinner_item, cameraNames)); 325 if (numCameras > 0) { 326 mCameraId = 0; 327 mCameraSpinner.setSelection(mCameraId + 1); 328 } else { 329 resetCamera(); 330 mCameraSpinner.setSelection(0); 331 } 332 333 mRS = RenderScript.create(this); 334 } 335 336 @Override 337 public void onResume() { 338 super.onResume(); 339 log("onResume: Setting up"); 340 setUpCamera(); 341 } 342 343 @Override 344 public void onPause() { 345 super.onPause(); 346 if (mState == CAMERA_RECORD) { 347 stopRecording(false); 348 } 349 if (mKeepOpenCheckBox.isChecked()) { 350 log("onPause: Not releasing camera"); 351 352 if (mState == CAMERA_PREVIEW) { 353 mCamera.stopPreview(); 354 mState = CAMERA_OPEN; 355 } 356 } else { 357 log("onPause: Releasing camera"); 358 359 if (mCamera != null) { 360 mCamera.release(); 361 } 362 mState = CAMERA_UNINITIALIZED; 363 } 364 } 365 366 @Override 367 public void onRequestPermissionsResult (int requestCode, String[] permissions, 368 int[] grantResults) { 369 if (requestCode == PERMISSIONS_REQUEST_CAMERA) { 370 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 371 log("Camera permission granted"); 372 setUpCamera(); 373 } else { 374 log("Camera permission denied, can't do anything"); 375 finish(); 376 } 377 } else if (requestCode == PERMISSIONS_REQUEST_RECORDING) { 378 mRecordToggle.setChecked(false); 379 for (int i = 0; i < grantResults.length; i++) { 380 if (grantResults[i] == PackageManager.PERMISSION_DENIED) { 381 log("Recording permission " + permissions[i] + " denied"); 382 return; 383 } 384 log("Recording permissions granted"); 385 setUpCamera(); 386 } 387 } else if (requestCode == PERMISSIONS_REQUEST_SNAPSHOT) { 388 if (mSnapshotDialog != null) { 389 mSnapshotDialog.onRequestPermissionsResult(requestCode, permissions, 390 grantResults); 391 } 392 } 393 394 } 395 396 /** SurfaceHolder.Callback methods */ 397 @Override 398 public void surfaceChanged(SurfaceHolder holder, 399 int format, 400 int width, 401 int height) { 402 if (holder == mPreviewView.getHolder()) { 403 if (mState >= CAMERA_OPEN) { 404 final int previewWidth = 405 mPreviewSizes.get(mPreviewSize).width; 406 final int previewHeight = 407 mPreviewSizes.get(mPreviewSize).height; 408 409 if ( Math.abs((float)previewWidth / previewHeight - 410 (float)width/height) > 0.01f) { 411 Handler h = new Handler(); 412 h.post(new Runnable() { 413 @Override 414 public void run() { 415 layoutPreview(); 416 } 417 }); 418 } 419 } 420 421 if (mPreviewHolder != null) { 422 return; 423 } 424 log("Surface holder available: " + width + " x " + height); 425 mPreviewHolder = holder; 426 try { 427 if (mCamera != null) { 428 mCamera.setPreviewDisplay(holder); 429 } 430 } catch (IOException e) { 431 logE("Unable to set up preview!"); 432 } 433 } else if (holder == mCallbackView.getHolder()) { 434 mCallbackHolder = holder; 435 } 436 } 437 438 @Override 439 public void surfaceCreated(SurfaceHolder holder) { 440 441 } 442 443 @Override 444 public void surfaceDestroyed(SurfaceHolder holder) { 445 mPreviewHolder = null; 446 } 447 448 public void setCameraDisplayOrientation() { 449 android.hardware.Camera.CameraInfo info = 450 new android.hardware.Camera.CameraInfo(); 451 android.hardware.Camera.getCameraInfo(mCameraId, info); 452 int rotation = getWindowManager().getDefaultDisplay() 453 .getRotation(); 454 int degrees = 0; 455 switch (rotation) { 456 case Surface.ROTATION_0: degrees = 0; break; 457 case Surface.ROTATION_90: degrees = 90; break; 458 case Surface.ROTATION_180: degrees = 180; break; 459 case Surface.ROTATION_270: degrees = 270; break; 460 } 461 462 int result; 463 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 464 result = (info.orientation + degrees) % 360; 465 result = (360 - result) % 360; // compensate the mirror 466 } else { // back-facing 467 result = (info.orientation - degrees + 360) % 360; 468 } 469 log(String.format( 470 "Camera sensor orientation %d, UI rotation %d, facing %s. Final orientation %d", 471 info.orientation, rotation, 472 info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? "FRONT" : "BACK", 473 result)); 474 mCamera.setDisplayOrientation(result); 475 } 476 477 /** UI controls enable/disable for all open-only controls */ 478 private void enableOpenOnlyControls(boolean enabled) { 479 for (View v : mOpenOnlyControls) { 480 v.setEnabled(enabled); 481 } 482 } 483 484 /** UI controls enable/disable for all preview-only controls */ 485 private void enablePreviewOnlyControls(boolean enabled) { 486 for (View v : mPreviewOnlyControls) { 487 v.setEnabled(enabled); 488 } 489 } 490 491 /** UI listeners */ 492 493 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 494 new AdapterView.OnItemSelectedListener() { 495 @Override 496 public void onItemSelected(AdapterView<?> parent, 497 View view, int pos, long id) { 498 int cameraId = pos - 1; 499 if (mCameraId != cameraId) { 500 resetCamera(); 501 mCameraId = cameraId; 502 mPreviewToggle.setChecked(false); 503 setUpCamera(); 504 } 505 } 506 507 @Override 508 public void onNothingSelected(AdapterView<?> parent) { 509 510 } 511 }; 512 513 private OnClickListener mInfoButtonListener = new OnClickListener() { 514 @Override 515 public void onClick(View v) { 516 if (mCameraId != NO_CAMERA_ID) { 517 FragmentManager fm = getFragmentManager(); 518 InfoDialogFragment infoDialog = new InfoDialogFragment(); 519 infoDialog.updateInfo(mCameraId, mCamera); 520 infoDialog.show(fm, "info_dialog_fragment"); 521 } 522 } 523 }; 524 525 private AdapterView.OnItemSelectedListener mPreviewSizeListener = 526 new AdapterView.OnItemSelectedListener() { 527 @Override 528 public void onItemSelected(AdapterView<?> parent, 529 View view, int pos, long id) { 530 if (pos == mPreviewSize) return; 531 if (mState == CAMERA_PREVIEW) { 532 log("Stopping preview and callbacks to switch resolutions"); 533 stopCallbacks(); 534 mCamera.stopPreview(); 535 } 536 537 mPreviewSize = pos; 538 int width = mPreviewSizes.get(mPreviewSize).width; 539 int height = mPreviewSizes.get(mPreviewSize).height; 540 mParams.setPreviewSize(width, height); 541 542 log("Setting preview size to " + width + "x" + height); 543 544 mCamera.setParameters(mParams); 545 resizePreview(); 546 547 if (mState == CAMERA_PREVIEW) { 548 log("Restarting preview"); 549 mCamera.startPreview(); 550 } 551 } 552 553 @Override 554 public void onNothingSelected(AdapterView<?> parent) { 555 556 } 557 }; 558 559 private AdapterView.OnItemSelectedListener mPreviewFrameRateListener = 560 new AdapterView.OnItemSelectedListener() { 561 @Override 562 public void onItemSelected(AdapterView<?> parent, 563 View view, int pos, long id) { 564 if (pos == mPreviewFrameRate) return; 565 mPreviewFrameRate = pos; 566 mParams.setPreviewFrameRate(mPreviewFrameRates.get(mPreviewFrameRate)); 567 568 log("Setting preview frame rate to " + ((TextView)view).getText()); 569 570 mCamera.setParameters(mParams); 571 } 572 573 @Override 574 public void onNothingSelected(AdapterView<?> parent) { 575 576 } 577 }; 578 579 private View.OnClickListener mHDRToggleListener = 580 new View.OnClickListener() { 581 @Override 582 public void onClick(View v) { 583 if (mState == CAMERA_TAKE_PICTURE) { 584 logE("Can't change preview state while taking picture!"); 585 return; 586 } 587 588 if (mHDRToggle.isChecked()) { 589 log("Turning on HDR"); 590 mParams.setSceneMode(Camera.Parameters.SCENE_MODE_HDR); 591 } else { 592 log("Turning off HDR"); 593 mParams.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO); 594 } 595 mCamera.setParameters(mParams); 596 } 597 }; 598 599 private View.OnClickListener mPreviewToggleListener = 600 new View.OnClickListener() { 601 @Override 602 public void onClick(View v) { 603 if (mState == CAMERA_TAKE_PICTURE) { 604 logE("Can't change preview state while taking picture!"); 605 return; 606 } 607 if (mPreviewToggle.isChecked()) { 608 log("Starting preview"); 609 mCamera.startPreview(); 610 mState = CAMERA_PREVIEW; 611 enablePreviewOnlyControls(true); 612 } else { 613 log("Stopping preview"); 614 mCamera.stopPreview(); 615 mState = CAMERA_OPEN; 616 617 enablePreviewOnlyControls(false); 618 } 619 } 620 }; 621 622 private OnItemSelectedListener mAutofocusModeListener = 623 new OnItemSelectedListener() { 624 @Override 625 public void onItemSelected(AdapterView<?> parent, 626 View view, int pos, long id) { 627 if (pos == mAfMode) return; 628 629 mAfMode = pos; 630 String focusMode = mAfModes.get(mAfMode); 631 log("Setting focus mode to " + focusMode); 632 if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE || 633 focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) { 634 mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback); 635 } 636 mParams.setFocusMode(focusMode); 637 638 mCamera.setParameters(mParams); 639 } 640 641 @Override 642 public void onNothingSelected(AdapterView<?> arg0) { 643 644 } 645 }; 646 647 private OnClickListener mAutofocusButtonListener = 648 new View.OnClickListener() { 649 @Override 650 public void onClick(View v) { 651 log("Triggering autofocus"); 652 mCamera.autoFocus(mAutofocusCallback); 653 } 654 }; 655 656 private OnClickListener mCancelAutofocusButtonListener = 657 new View.OnClickListener() { 658 @Override 659 public void onClick(View v) { 660 log("Cancelling autofocus"); 661 mCamera.cancelAutoFocus(); 662 } 663 }; 664 665 private Camera.AutoFocusCallback mAutofocusCallback = 666 new Camera.AutoFocusCallback() { 667 @Override 668 public void onAutoFocus(boolean success, Camera camera) { 669 log("Autofocus completed: " + (success ? "success" : "failure") ); 670 } 671 }; 672 673 private Camera.AutoFocusMoveCallback mAutofocusMoveCallback = 674 new Camera.AutoFocusMoveCallback() { 675 @Override 676 public void onAutoFocusMoving(boolean start, Camera camera) { 677 log("Autofocus movement: " + (start ? "starting" : "stopped") ); 678 } 679 }; 680 681 private OnItemSelectedListener mFlashModeListener = 682 new OnItemSelectedListener() { 683 @Override 684 public void onItemSelected(AdapterView<?> parent, 685 View view, int pos, long id) { 686 if (pos == mFlashMode) return; 687 688 mFlashMode = pos; 689 String flashMode = mFlashModes.get(mFlashMode); 690 log("Setting flash mode to " + flashMode); 691 mParams.setFlashMode(flashMode); 692 mCamera.setParameters(mParams); 693 } 694 695 @Override 696 public void onNothingSelected(AdapterView<?> arg0) { 697 698 } 699 }; 700 701 702 private AdapterView.OnItemSelectedListener mSnapshotSizeListener = 703 new AdapterView.OnItemSelectedListener() { 704 @Override 705 public void onItemSelected(AdapterView<?> parent, 706 View view, int pos, long id) { 707 if (pos == mSnapshotSize) return; 708 709 mSnapshotSize = pos; 710 int width = mSnapshotSizes.get(mSnapshotSize).width; 711 int height = mSnapshotSizes.get(mSnapshotSize).height; 712 log("Setting snapshot size to " + width + " x " + height); 713 714 mParams.setPictureSize(width, height); 715 716 mCamera.setParameters(mParams); 717 } 718 719 @Override 720 public void onNothingSelected(AdapterView<?> parent) { 721 722 } 723 }; 724 725 private View.OnClickListener mTakePictureListener = 726 new View.OnClickListener() { 727 @Override 728 public void onClick(View v) { 729 log("Taking picture"); 730 if (mState == CAMERA_PREVIEW) { 731 mState = CAMERA_TAKE_PICTURE; 732 enablePreviewOnlyControls(false); 733 mPreviewToggle.setChecked(false); 734 735 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb); 736 } else { 737 logE("Can't take picture while not running preview!"); 738 } 739 } 740 }; 741 742 private AdapterView.OnItemSelectedListener mCamcorderProfileListener = 743 new AdapterView.OnItemSelectedListener() { 744 @Override 745 public void onItemSelected(AdapterView<?> parent, 746 View view, int pos, long id) { 747 if (pos != mCamcorderProfile) { 748 log("Setting camcorder profile to " + ((TextView)view).getText()); 749 mCamcorderProfile = pos; 750 } 751 752 // Additionally change video recording size to match 753 mVideoRecordSize = 0; // "default", in case it's not found 754 int width = mCamcorderProfiles.get(pos).videoFrameWidth; 755 int height = mCamcorderProfiles.get(pos).videoFrameHeight; 756 for (int i = 0; i < mVideoRecordSizes.size(); i++) { 757 Camera.Size s = mVideoRecordSizes.get(i); 758 if (width == s.width && height == s.height) { 759 mVideoRecordSize = i; 760 break; 761 } 762 } 763 log("Setting video record size to " + mVideoRecordSize); 764 mVideoRecordSizeSpinner.setSelection(mVideoRecordSize); 765 } 766 767 @Override 768 public void onNothingSelected(AdapterView<?> parent) { 769 770 } 771 }; 772 773 private AdapterView.OnItemSelectedListener mVideoRecordSizeListener = 774 new AdapterView.OnItemSelectedListener() { 775 @Override 776 public void onItemSelected(AdapterView<?> parent, 777 View view, int pos, long id) { 778 if (pos == mVideoRecordSize) return; 779 780 log("Setting video record size to " + ((TextView)view).getText()); 781 mVideoRecordSize = pos; 782 } 783 784 @Override 785 public void onNothingSelected(AdapterView<?> parent) { 786 787 } 788 }; 789 790 private AdapterView.OnItemSelectedListener mVideoFrameRateListener = 791 new AdapterView.OnItemSelectedListener() { 792 @Override 793 public void onItemSelected(AdapterView<?> parent, 794 View view, int pos, long id) { 795 if (pos == mVideoFrameRate) return; 796 797 log("Setting video frame rate to " + ((TextView)view).getText()); 798 mVideoFrameRate = pos; 799 } 800 801 @Override 802 public void onNothingSelected(AdapterView<?> parent) { 803 804 } 805 }; 806 807 private View.OnClickListener mRecordToggleListener = 808 new View.OnClickListener() { 809 @Override 810 public void onClick(View v) { 811 if (!mLockCameraToggle.isChecked()) { 812 logE("Re-lock camera before recording"); 813 return; 814 } 815 816 mPreviewToggle.setEnabled(false); 817 if (mState == CAMERA_PREVIEW) { 818 startRecording(); 819 } else if (mState == CAMERA_RECORD) { 820 stopRecording(false); 821 } else { 822 logE("Can't toggle recording in current state!"); 823 } 824 mPreviewToggle.setEnabled(true); 825 } 826 }; 827 828 private View.OnClickListener mRecordStabilizationToggleListener = 829 new View.OnClickListener() { 830 @Override 831 public void onClick(View v) { 832 boolean on = ((ToggleButton) v).isChecked(); 833 mParams.setVideoStabilization(on); 834 835 mCamera.setParameters(mParams); 836 } 837 }; 838 839 private View.OnClickListener mRecordHintToggleListener = 840 new View.OnClickListener() { 841 @Override 842 public void onClick(View v) { 843 boolean on = ((ToggleButton) v).isChecked(); 844 mParams.setRecordingHint(on); 845 846 mCamera.setParameters(mParams); 847 } 848 }; 849 850 private View.OnClickListener mLockCameraToggleListener = 851 new View.OnClickListener() { 852 @Override 853 public void onClick(View v) { 854 855 if (mState == CAMERA_RECORD) { 856 logE("Stop recording before toggling lock"); 857 return; 858 } 859 860 boolean on = ((ToggleButton) v).isChecked(); 861 862 if (on) { 863 mCamera.lock(); 864 log("Locked camera"); 865 } else { 866 mCamera.unlock(); 867 log("Unlocked camera"); 868 } 869 } 870 }; 871 872 private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() { 873 @Override 874 public void onShutter() { 875 log("Shutter callback received"); 876 } 877 }; 878 879 private Camera.PictureCallback mRawCb = new Camera.PictureCallback() { 880 @Override 881 public void onPictureTaken(byte[] data, Camera camera) { 882 log("Raw callback received"); 883 } 884 }; 885 886 private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() { 887 @Override 888 public void onPictureTaken(byte[] data, Camera camera) { 889 log("Postview callback received"); 890 } 891 }; 892 893 private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() { 894 @Override 895 public void onPictureTaken(byte[] data, Camera camera) { 896 log("JPEG picture callback received"); 897 FragmentManager fm = getFragmentManager(); 898 mSnapshotDialog = new SnapshotDialogFragment(); 899 900 mSnapshotDialog.updateImage(data); 901 mSnapshotDialog.show(fm, "snapshot_dialog_fragment"); 902 903 mPreviewToggle.setEnabled(true); 904 905 mState = CAMERA_OPEN; 906 } 907 }; 908 909 private AdapterView.OnItemSelectedListener mCallbackFormatListener = 910 new AdapterView.OnItemSelectedListener() { 911 public void onItemSelected(AdapterView<?> parent, 912 View view, int pos, long id) { 913 mPreviewFormat = pos; 914 915 log("Setting preview format to " + 916 mFormatNames.get(mPreviewFormats.get(mPreviewFormat))); 917 918 switch (mState) { 919 case CAMERA_UNINITIALIZED: 920 return; 921 case CAMERA_OPEN: 922 break; 923 case CAMERA_PREVIEW: 924 if (mCallbacksEnabled) { 925 log("Stopping preview and callbacks to switch formats"); 926 stopCallbacks(); 927 mCamera.stopPreview(); 928 } 929 break; 930 case CAMERA_RECORD: 931 logE("Can't update format while recording active"); 932 return; 933 } 934 935 mParams.setPreviewFormat(mPreviewFormats.get(mPreviewFormat)); 936 mCamera.setParameters(mParams); 937 938 if (mCallbacksEnabled) { 939 if (mState == CAMERA_PREVIEW) { 940 mCamera.startPreview(); 941 } 942 } 943 944 configureCallbacks(mCallbackView.getWidth(), mCallbackView.getHeight()); 945 } 946 947 public void onNothingSelected(AdapterView<?> parent) { 948 949 } 950 }; 951 952 private View.OnClickListener mCallbackToggleListener = 953 new View.OnClickListener() { 954 public void onClick(View v) { 955 if (mCallbacksEnabled) { 956 log("Disabling preview callbacks"); 957 stopCallbacks(); 958 mCallbacksEnabled = false; 959 resizePreview(); 960 mCallbackView.setVisibility(View.GONE); 961 962 } else { 963 log("Enabling preview callbacks"); 964 mCallbacksEnabled = true; 965 resizePreview(); 966 mCallbackView.setVisibility(View.VISIBLE); 967 } 968 } 969 }; 970 971 972 // Internal methods 973 974 void setUpCamera() { 975 if (mCameraId == NO_CAMERA_ID) return; 976 977 log("Setting up camera " + mCameraId); 978 logIndent(1); 979 980 if (mState < CAMERA_OPEN) { 981 log("Opening camera " + mCameraId); 982 983 if (checkSelfPermission(Manifest.permission.CAMERA) 984 != PackageManager.PERMISSION_GRANTED) { 985 log("Requested camera permission"); 986 requestPermissions(new String[] {Manifest.permission.CAMERA}, 987 PERMISSIONS_REQUEST_CAMERA); 988 return; 989 } 990 991 992 try { 993 mCamera = Camera.open(mCameraId); 994 } catch (RuntimeException e) { 995 logE("Exception opening camera: " + e.getMessage()); 996 resetCamera(); 997 mCameraSpinner.setSelection(0); 998 logIndent(-1); 999 return; 1000 } 1001 mState = CAMERA_OPEN; 1002 } 1003 1004 mCamera.setErrorCallback(this); 1005 1006 setCameraDisplayOrientation(); 1007 mParams = mCamera.getParameters(); 1008 mHDRToggle.setEnabled(false); 1009 if (mParams != null) { 1010 List<String> sceneModes = mParams.getSupportedSceneModes(); 1011 if (sceneModes != null) { 1012 for (String mode : sceneModes) { 1013 if (Camera.Parameters.SCENE_MODE_HDR.equals(mode)){ 1014 mHDRToggle.setEnabled(true); 1015 break; 1016 } 1017 } 1018 } else { 1019 Log.i(TAG, "Supported scene modes is null"); 1020 } 1021 } 1022 1023 // Set up preview size selection 1024 1025 log("Configuring camera"); 1026 logIndent(1); 1027 1028 updatePreviewSizes(mParams); 1029 updatePreviewFrameRate(mCameraId); 1030 updatePreviewFormats(mParams); 1031 updateAfModes(mParams); 1032 updateFlashModes(mParams); 1033 updateSnapshotSizes(mParams); 1034 updateCamcorderProfile(mCameraId); 1035 updateVideoRecordSize(mCameraId); 1036 updateVideoFrameRate(mCameraId); 1037 updateColorEffects(mParams); 1038 1039 // Trigger updating video record size to match camcorder profile 1040 mCamcorderProfileSpinner.setSelection(mCamcorderProfile); 1041 1042 if (mParams.isVideoStabilizationSupported()) { 1043 log("Video stabilization is supported"); 1044 mRecordStabilizationToggle.setEnabled(true); 1045 } else { 1046 log("Video stabilization not supported"); 1047 mRecordStabilizationToggle.setEnabled(false); 1048 } 1049 1050 if (mParams.isAutoExposureLockSupported()) { 1051 log("Auto-Exposure locking is supported"); 1052 mExposureLockToggle.setEnabled(true); 1053 } else { 1054 log("Auto-Exposure locking is not supported"); 1055 mExposureLockToggle.setEnabled(false); 1056 } 1057 1058 if (mParams.isZoomSupported()) { 1059 int maxZoom = mParams.getMaxZoom(); 1060 mZoomSeekBar.setMax(maxZoom); 1061 log("Zoom is supported, set max to " + maxZoom); 1062 mZoomSeekBar.setEnabled(true); 1063 } else { 1064 log("Zoom is not supported"); 1065 mZoomSeekBar.setEnabled(false); 1066 } 1067 1068 // Update parameters based on above updates 1069 mCamera.setParameters(mParams); 1070 1071 if (mPreviewHolder != null) { 1072 log("Setting preview display"); 1073 try { 1074 mCamera.setPreviewDisplay(mPreviewHolder); 1075 } catch(IOException e) { 1076 Log.e(TAG, "Unable to set up preview!"); 1077 } 1078 } 1079 1080 logIndent(-1); 1081 1082 enableOpenOnlyControls(true); 1083 1084 resizePreview(); 1085 if (mPreviewToggle.isChecked()) { 1086 log("Starting preview" ); 1087 mCamera.startPreview(); 1088 mState = CAMERA_PREVIEW; 1089 } else { 1090 mState = CAMERA_OPEN; 1091 enablePreviewOnlyControls(false); 1092 } 1093 logIndent(-1); 1094 } 1095 1096 private void resetCamera() { 1097 if (mState >= CAMERA_OPEN) { 1098 log("Closing old camera"); 1099 mCamera.release(); 1100 } 1101 mCamera = null; 1102 mCameraId = NO_CAMERA_ID; 1103 mState = CAMERA_UNINITIALIZED; 1104 1105 enableOpenOnlyControls(false); 1106 } 1107 1108 private void updateAfModes(Parameters params) { 1109 mAfModes = params.getSupportedFocusModes(); 1110 1111 mAutofocusModeSpinner.setAdapter( 1112 new ArrayAdapter<String>(this, R.layout.spinner_item, 1113 mAfModes.toArray(new String[0]))); 1114 1115 mAfMode = 0; 1116 1117 params.setFocusMode(mAfModes.get(mAfMode)); 1118 1119 log("Setting AF mode to " + mAfModes.get(mAfMode)); 1120 } 1121 1122 private void updateFlashModes(Parameters params) { 1123 mFlashModes = params.getSupportedFlashModes(); 1124 1125 if (mFlashModes != null) { 1126 mFlashModeSpinnerLabel.setVisibility(View.VISIBLE); 1127 mFlashModeSpinner.setVisibility(View.VISIBLE); 1128 mFlashModeSpinner.setAdapter( 1129 new ArrayAdapter<String>(this, R.layout.spinner_item, 1130 mFlashModes.toArray(new String[0]))); 1131 1132 mFlashMode = 0; 1133 1134 params.setFlashMode(mFlashModes.get(mFlashMode)); 1135 1136 log("Setting Flash mode to " + mFlashModes.get(mFlashMode)); 1137 } else { 1138 // this camera has no flash 1139 mFlashModeSpinnerLabel.setVisibility(View.GONE); 1140 mFlashModeSpinner.setVisibility(View.GONE); 1141 } 1142 } 1143 1144 private View.OnClickListener mExposureLockToggleListener = 1145 new View.OnClickListener() { 1146 public void onClick(View v) { 1147 boolean on = ((ToggleButton) v).isChecked(); 1148 log("Auto-Exposure was " + mParams.getAutoExposureLock()); 1149 mParams.setAutoExposureLock(on); 1150 log("Auto-Exposure is now " + mParams.getAutoExposureLock()); 1151 } 1152 }; 1153 1154 private final SeekBar.OnSeekBarChangeListener mZoomSeekBarListener = 1155 new SeekBar.OnSeekBarChangeListener() { 1156 @Override 1157 public void onProgressChanged(SeekBar seekBar, int progress, 1158 boolean fromUser) { 1159 mZoom = progress; 1160 mParams.setZoom(mZoom); 1161 mCamera.setParameters(mParams); 1162 } 1163 @Override 1164 public void onStartTrackingTouch(SeekBar seekBar) { } 1165 @Override 1166 public void onStopTrackingTouch(SeekBar seekBar) { 1167 log("Zoom set to " + mZoom + " / " + mParams.getMaxZoom() + " (" + 1168 ((float)(mParams.getZoomRatios().get(mZoom))/100) + "x)"); 1169 } 1170 }; 1171 1172 private void updatePreviewSizes(Camera.Parameters params) { 1173 mPreviewSizes = params.getSupportedPreviewSizes(); 1174 1175 String[] availableSizeNames = new String[mPreviewSizes.size()]; 1176 int i = 0; 1177 for (Camera.Size previewSize: mPreviewSizes) { 1178 availableSizeNames[i++] = 1179 Integer.toString(previewSize.width) + " x " + 1180 Integer.toString(previewSize.height); 1181 } 1182 mPreviewSizeSpinner.setAdapter( 1183 new ArrayAdapter<String>( 1184 this, R.layout.spinner_item, availableSizeNames)); 1185 1186 mPreviewSize = 0; 1187 1188 int width = mPreviewSizes.get(mPreviewSize).width; 1189 int height = mPreviewSizes.get(mPreviewSize).height; 1190 params.setPreviewSize(width, height); 1191 log("Setting preview size to " + width + " x " + height); 1192 } 1193 1194 private void updatePreviewFrameRate(int cameraId) { 1195 List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); 1196 int defaultPreviewFrameRate = mParams.getPreviewFrameRate(); 1197 1198 List<String> frameRateStrings = new ArrayList<String>(); 1199 mPreviewFrameRates = new ArrayList<Integer>(); 1200 1201 int currentIndex = 0; 1202 for (Integer frameRate : frameRates) { 1203 mPreviewFrameRates.add(frameRate); 1204 if(frameRate == defaultPreviewFrameRate) { 1205 frameRateStrings.add(frameRate.toString() + " (Default)"); 1206 mPreviewFrameRate = currentIndex; 1207 } else { 1208 frameRateStrings.add(frameRate.toString()); 1209 } 1210 currentIndex++; 1211 } 1212 1213 String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); 1214 mPreviewFrameRateSpinner.setAdapter( 1215 new ArrayAdapter<String>( 1216 this, R.layout.spinner_item, nameArray)); 1217 1218 mPreviewFrameRateSpinner.setSelection(mPreviewFrameRate); 1219 log("Setting preview frame rate to " + nameArray[mPreviewFrameRate]); 1220 } 1221 1222 private void updatePreviewFormats(Camera.Parameters params) { 1223 mPreviewFormats = params.getSupportedPreviewFormats(); 1224 1225 String[] availableFormatNames = new String[mPreviewFormats.size()]; 1226 int i = 0; 1227 for (Integer previewFormat: mPreviewFormats) { 1228 availableFormatNames[i++] = mFormatNames.get(previewFormat); 1229 } 1230 mCallbackFormatSpinner.setAdapter( 1231 new ArrayAdapter<String>( 1232 this, R.layout.spinner_item, availableFormatNames)); 1233 1234 mPreviewFormat = 0; 1235 mCallbacksEnabled = false; 1236 mCallbackToggle.setChecked(false); 1237 mCallbackView.setVisibility(View.GONE); 1238 1239 params.setPreviewFormat(mPreviewFormats.get(mPreviewFormat)); 1240 log("Setting preview format to " + 1241 mFormatNames.get(mPreviewFormats.get(mPreviewFormat))); 1242 } 1243 1244 private void updateSnapshotSizes(Camera.Parameters params) { 1245 String[] availableSizeNames; 1246 mSnapshotSizes = params.getSupportedPictureSizes(); 1247 1248 availableSizeNames = new String[mSnapshotSizes.size()]; 1249 int i = 0; 1250 for (Camera.Size snapshotSize : mSnapshotSizes) { 1251 availableSizeNames[i++] = 1252 Integer.toString(snapshotSize.width) + " x " + 1253 Integer.toString(snapshotSize.height); 1254 } 1255 mSnapshotSizeSpinner.setAdapter( 1256 new ArrayAdapter<String>( 1257 this, R.layout.spinner_item, availableSizeNames)); 1258 1259 mSnapshotSize = 0; 1260 1261 int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width; 1262 int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height; 1263 params.setPictureSize(snapshotWidth, snapshotHeight); 1264 log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight); 1265 } 1266 1267 private void updateCamcorderProfile(int cameraId) { 1268 // Have to query all of these individually, 1269 final int PROFILES[] = new int[] { 1270 CamcorderProfile.QUALITY_2160P, 1271 CamcorderProfile.QUALITY_1080P, 1272 CamcorderProfile.QUALITY_480P, 1273 CamcorderProfile.QUALITY_720P, 1274 CamcorderProfile.QUALITY_CIF, 1275 CamcorderProfile.QUALITY_HIGH, 1276 CamcorderProfile.QUALITY_LOW, 1277 CamcorderProfile.QUALITY_QCIF, 1278 CamcorderProfile.QUALITY_QVGA, 1279 CamcorderProfile.QUALITY_TIME_LAPSE_2160P, 1280 CamcorderProfile.QUALITY_TIME_LAPSE_1080P, 1281 CamcorderProfile.QUALITY_TIME_LAPSE_480P, 1282 CamcorderProfile.QUALITY_TIME_LAPSE_720P, 1283 CamcorderProfile.QUALITY_TIME_LAPSE_CIF, 1284 CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, 1285 CamcorderProfile.QUALITY_TIME_LAPSE_LOW, 1286 CamcorderProfile.QUALITY_TIME_LAPSE_QCIF, 1287 CamcorderProfile.QUALITY_TIME_LAPSE_QVGA 1288 }; 1289 1290 final String PROFILE_NAMES[] = new String[] { 1291 "2160P", 1292 "1080P", 1293 "480P", 1294 "720P", 1295 "CIF", 1296 "HIGH", 1297 "LOW", 1298 "QCIF", 1299 "QVGA", 1300 "TIME_LAPSE_2160P", 1301 "TIME_LAPSE_1080P", 1302 "TIME_LAPSE_480P", 1303 "TIME_LAPSE_720P", 1304 "TIME_LAPSE_CIF", 1305 "TIME_LAPSE_HIGH", 1306 "TIME_LAPSE_LOW", 1307 "TIME_LAPSE_QCIF", 1308 "TIME_LAPSE_QVGA" 1309 }; 1310 1311 List<String> availableCamcorderProfileNames = new ArrayList<String>(); 1312 mCamcorderProfiles = new ArrayList<CamcorderProfile>(); 1313 1314 for (int i = 0; i < PROFILES.length; i++) { 1315 if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) { 1316 availableCamcorderProfileNames.add(PROFILE_NAMES[i]); 1317 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i])); 1318 } 1319 } 1320 String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]); 1321 mCamcorderProfileSpinner.setAdapter( 1322 new ArrayAdapter<String>( 1323 this, R.layout.spinner_item, nameArray)); 1324 1325 mCamcorderProfile = 0; 1326 log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); 1327 1328 } 1329 1330 private void updateVideoRecordSize(int cameraId) { 1331 List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes(); 1332 if (videoSizes == null) { // TODO: surface this to the user 1333 log("Failed to get video size list, using preview sizes instead"); 1334 videoSizes = mParams.getSupportedPreviewSizes(); 1335 } 1336 1337 List<String> availableVideoRecordSizes = new ArrayList<String>(); 1338 mVideoRecordSizes = new ArrayList<Camera.Size>(); 1339 1340 availableVideoRecordSizes.add("Default"); 1341 mVideoRecordSizes.add(mCamera.new Size(0,0)); 1342 1343 for (Camera.Size s : videoSizes) { 1344 availableVideoRecordSizes.add(s.width + "x" + s.height); 1345 mVideoRecordSizes.add(s); 1346 } 1347 String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]); 1348 mVideoRecordSizeSpinner.setAdapter( 1349 new ArrayAdapter<String>( 1350 this, R.layout.spinner_item, nameArray)); 1351 1352 mVideoRecordSize = 0; 1353 log("Setting video record profile to " + nameArray[mVideoRecordSize]); 1354 } 1355 1356 private void updateVideoFrameRate(int cameraId) { 1357 // Use preview framerates as video framerates 1358 List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); 1359 1360 List<String> frameRateStrings = new ArrayList<String>(); 1361 mVideoFrameRates = new ArrayList<Integer>(); 1362 1363 frameRateStrings.add("Default"); 1364 mVideoFrameRates.add(0); 1365 1366 for (Integer frameRate : frameRates) { 1367 frameRateStrings.add(frameRate.toString()); 1368 mVideoFrameRates.add(frameRate); 1369 } 1370 String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); 1371 mVideoFrameRateSpinner.setAdapter( 1372 new ArrayAdapter<String>( 1373 this, R.layout.spinner_item, nameArray)); 1374 1375 mVideoFrameRate = 0; 1376 log("Setting recording frame rate to " + nameArray[mVideoFrameRate]); 1377 } 1378 1379 void resizePreview() { 1380 // Reset preview layout parameters, to trigger layout pass 1381 // This will eventually call layoutPreview below 1382 Resources res = getResources(); 1383 mPreviewView.setLayoutParams( 1384 new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1385 mCallbacksEnabled ? 1386 res.getInteger(R.integer.preview_with_callback_weight): 1387 res.getInteger(R.integer.preview_only_weight) )); 1388 } 1389 1390 void layoutPreview() { 1391 int width = mPreviewSizes.get(mPreviewSize).width; 1392 int height = mPreviewSizes.get(mPreviewSize).height; 1393 float previewAspect = ((float) width) / height; 1394 1395 int viewHeight = mPreviewView.getHeight(); 1396 int viewWidth = mPreviewView.getWidth(); 1397 float viewAspect = ((float) viewWidth) / viewHeight; 1398 if ( previewAspect > viewAspect) { 1399 viewHeight = (int) (viewWidth / previewAspect); 1400 } else { 1401 viewWidth = (int) (viewHeight * previewAspect); 1402 } 1403 mPreviewView.setLayoutParams( 1404 new LayoutParams(viewWidth, viewHeight)); 1405 1406 if (mCallbacksEnabled) { 1407 int callbackHeight = mCallbackView.getHeight(); 1408 int callbackWidth = mCallbackView.getWidth(); 1409 float callbackAspect = ((float) callbackWidth) / callbackHeight; 1410 if ( previewAspect > callbackAspect) { 1411 callbackHeight = (int) (callbackWidth / previewAspect); 1412 } else { 1413 callbackWidth = (int) (callbackHeight * previewAspect); 1414 } 1415 mCallbackView.setLayoutParams( 1416 new LayoutParams(callbackWidth, callbackHeight)); 1417 configureCallbacks(callbackWidth, callbackHeight); 1418 } 1419 } 1420 1421 1422 private void configureCallbacks(int callbackWidth, int callbackHeight) { 1423 if (mState >= CAMERA_OPEN && mCallbacksEnabled) { 1424 mCamera.setPreviewCallbackWithBuffer(null); 1425 int width = mPreviewSizes.get(mPreviewSize).width; 1426 int height = mPreviewSizes.get(mPreviewSize).height; 1427 int format = mPreviewFormats.get(mPreviewFormat); 1428 1429 mCallbackProcessor = new CallbackProcessor(width, height, format, 1430 getResources(), mCallbackView, 1431 callbackWidth, callbackHeight, mRS); 1432 1433 int size = getCallbackBufferSize(width, height, format); 1434 log("Configuring callbacks:" + width + " x " + height + 1435 " , format " + format); 1436 for (int i = 0; i < CALLBACK_BUFFER_COUNT; i++) { 1437 mCamera.addCallbackBuffer(new byte[size]); 1438 } 1439 mCamera.setPreviewCallbackWithBuffer(this); 1440 } 1441 mLastCallbackTimestamp = -1; 1442 mCallbackFrameCount = 0; 1443 mCallbackAvgFrameDuration = 30; 1444 } 1445 1446 private void stopCallbacks() { 1447 if (mState >= CAMERA_OPEN) { 1448 mCamera.setPreviewCallbackWithBuffer(null); 1449 if (mCallbackProcessor != null) { 1450 if (!mCallbackProcessor.stop()) { 1451 logE("Can't stop preview callback processing!"); 1452 } 1453 } 1454 } 1455 } 1456 1457 @Override 1458 public void onPreviewFrame(byte[] data, Camera camera) { 1459 long timestamp = SystemClock.elapsedRealtime(); 1460 if (mLastCallbackTimestamp != -1) { 1461 long frameDuration = timestamp - mLastCallbackTimestamp; 1462 mCallbackAvgFrameDuration = 1463 mCallbackAvgFrameDuration * MEAN_FPS_HISTORY_COEFF + 1464 frameDuration * MEAN_FPS_MEASUREMENT_COEFF; 1465 } 1466 mLastCallbackTimestamp = timestamp; 1467 if (mState < CAMERA_PREVIEW || !mCallbacksEnabled) { 1468 mCamera.addCallbackBuffer(data); 1469 return; 1470 } 1471 mCallbackFrameCount++; 1472 if (mCallbackFrameCount % FPS_REPORTING_PERIOD == 0) { 1473 log("Got " + FPS_REPORTING_PERIOD + " callback frames, fps " 1474 + 1e3/mCallbackAvgFrameDuration); 1475 } 1476 mCallbackProcessor.displayCallback(data); 1477 1478 mCamera.addCallbackBuffer(data); 1479 } 1480 1481 @Override 1482 public void onError(int error, Camera camera) { 1483 String errorName; 1484 switch (error) { 1485 case Camera.CAMERA_ERROR_SERVER_DIED: 1486 errorName = "SERVER_DIED"; 1487 break; 1488 case Camera.CAMERA_ERROR_UNKNOWN: 1489 errorName = "UNKNOWN"; 1490 break; 1491 default: 1492 errorName = "?"; 1493 break; 1494 } 1495 logE("Camera error received: " + errorName + " (" + error + ")" ); 1496 logE("Shutting down camera"); 1497 resetCamera(); 1498 mCameraSpinner.setSelection(0); 1499 } 1500 1501 static final int MEDIA_TYPE_IMAGE = 0; 1502 static final int MEDIA_TYPE_VIDEO = 1; 1503 @SuppressLint("SimpleDateFormat") 1504 File getOutputMediaFile(int type){ 1505 // To be safe, you should check that the SDCard is mounted 1506 // using Environment.getExternalStorageState() before doing this. 1507 1508 String state = Environment.getExternalStorageState(); 1509 if (!Environment.MEDIA_MOUNTED.equals(state)) { 1510 return null; 1511 } 1512 1513 File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 1514 Environment.DIRECTORY_DCIM), "TestingCamera"); 1515 // This location works best if you want the created images to be shared 1516 // between applications and persist after your app has been uninstalled. 1517 1518 // Create the storage directory if it does not exist 1519 if (! mediaStorageDir.exists()){ 1520 if (! mediaStorageDir.mkdirs()){ 1521 logE("Failed to create directory for pictures/video"); 1522 return null; 1523 } 1524 } 1525 1526 // Create a media file name 1527 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 1528 File mediaFile; 1529 if (type == MEDIA_TYPE_IMAGE){ 1530 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 1531 "IMG_"+ timeStamp + ".jpg"); 1532 } else if(type == MEDIA_TYPE_VIDEO) { 1533 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 1534 "VID_"+ timeStamp + ".mp4"); 1535 } else { 1536 return null; 1537 } 1538 1539 return mediaFile; 1540 } 1541 1542 void notifyMediaScannerOfFile(File newFile, 1543 final MediaScannerConnection.OnScanCompletedListener listener) { 1544 final Handler h = new Handler(); 1545 MediaScannerConnection.scanFile(this, 1546 new String[] { newFile.toString() }, 1547 null, 1548 new MediaScannerConnection.OnScanCompletedListener() { 1549 @Override 1550 public void onScanCompleted(final String path, final Uri uri) { 1551 h.post(new Runnable() { 1552 @Override 1553 public void run() { 1554 log("MediaScanner notified: " + 1555 path + " -> " + uri); 1556 if (listener != null) 1557 listener.onScanCompleted(path, uri); 1558 } 1559 }); 1560 } 1561 }); 1562 } 1563 1564 private void deleteFile(File badFile) { 1565 if (badFile.exists()) { 1566 boolean success = badFile.delete(); 1567 if (success) log("Deleted file " + badFile.toString()); 1568 else log("Unable to delete file " + badFile.toString()); 1569 } 1570 } 1571 1572 private void startRecording() { 1573 log("Starting recording"); 1574 1575 if ((checkSelfPermission(Manifest.permission.RECORD_AUDIO) 1576 != PackageManager.PERMISSION_GRANTED) 1577 || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) 1578 != PackageManager.PERMISSION_GRANTED)) { 1579 log("Requesting recording permissions (audio, storage)"); 1580 requestPermissions(new String[] { 1581 Manifest.permission.RECORD_AUDIO, 1582 Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1583 PERMISSIONS_REQUEST_RECORDING); 1584 return; 1585 } 1586 1587 logIndent(1); 1588 log("Configuring MediaRecoder"); 1589 1590 mRecordHandoffCheckBox.setEnabled(false); 1591 if (mRecordHandoffCheckBox.isChecked()) { 1592 mCamera.release(); 1593 } else { 1594 mCamera.unlock(); 1595 } 1596 1597 if (mRecorder != null) { 1598 mRecorder.release(); 1599 } 1600 1601 mRecorder = new MediaRecorder(); 1602 mRecorder.setOnErrorListener(mRecordingErrorListener); 1603 mRecorder.setOnInfoListener(mRecordingInfoListener); 1604 if (!mRecordHandoffCheckBox.isChecked()) { 1605 mRecorder.setCamera(mCamera); 1606 } 1607 mRecorder.setPreviewDisplay(mPreviewHolder.getSurface()); 1608 1609 mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1610 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1611 mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); 1612 Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize); 1613 if (videoRecordSize.width > 0 && videoRecordSize.height > 0) { 1614 mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height); 1615 } 1616 if (mVideoFrameRates.get(mVideoFrameRate) > 0) { 1617 mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate)); 1618 } 1619 File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); 1620 log("File name:" + outputFile.toString()); 1621 mRecorder.setOutputFile(outputFile.toString()); 1622 1623 boolean ready = false; 1624 log("Preparing MediaRecorder"); 1625 try { 1626 mRecorder.prepare(); 1627 ready = true; 1628 } catch (Exception e) { 1629 StringWriter writer = new StringWriter(); 1630 e.printStackTrace(new PrintWriter(writer)); 1631 logE("Exception preparing MediaRecorder:\n" + writer.toString()); 1632 } 1633 1634 if (ready) { 1635 try { 1636 log("Starting MediaRecorder"); 1637 mRecorder.start(); 1638 mState = CAMERA_RECORD; 1639 log("Recording active"); 1640 mRecordingFile = outputFile; 1641 } catch (Exception e) { 1642 StringWriter writer = new StringWriter(); 1643 e.printStackTrace(new PrintWriter(writer)); 1644 logE("Exception starting MediaRecorder:\n" + writer.toString()); 1645 ready = false; 1646 } 1647 } 1648 1649 if (!ready) { 1650 mRecordToggle.setChecked(false); 1651 mRecordHandoffCheckBox.setEnabled(true); 1652 1653 if (mRecordHandoffCheckBox.isChecked()) { 1654 mState = CAMERA_UNINITIALIZED; 1655 setUpCamera(); 1656 } 1657 } 1658 logIndent(-1); 1659 } 1660 1661 private MediaRecorder.OnErrorListener mRecordingErrorListener = 1662 new MediaRecorder.OnErrorListener() { 1663 @Override 1664 public void onError(MediaRecorder mr, int what, int extra) { 1665 logE("MediaRecorder reports error: " + what + ", extra " 1666 + extra); 1667 if (mState == CAMERA_RECORD) { 1668 stopRecording(true); 1669 } 1670 } 1671 }; 1672 1673 private MediaRecorder.OnInfoListener mRecordingInfoListener = 1674 new MediaRecorder.OnInfoListener() { 1675 @Override 1676 public void onInfo(MediaRecorder mr, int what, int extra) { 1677 log("MediaRecorder reports info: " + what + ", extra " 1678 + extra); 1679 } 1680 }; 1681 1682 private void stopRecording(boolean error) { 1683 log("Stopping recording"); 1684 mRecordHandoffCheckBox.setEnabled(true); 1685 mRecordToggle.setChecked(false); 1686 if (mRecorder != null) { 1687 try { 1688 mRecorder.stop(); 1689 } catch (RuntimeException e) { 1690 // this can happen if there were no frames received by recorder 1691 logE("Could not create output file"); 1692 error = true; 1693 } 1694 1695 if (mRecordHandoffCheckBox.isChecked()) { 1696 mState = CAMERA_UNINITIALIZED; 1697 setUpCamera(); 1698 } else { 1699 mCamera.lock(); 1700 mState = CAMERA_PREVIEW; 1701 } 1702 1703 if (!error) { 1704 notifyMediaScannerOfFile(mRecordingFile, null); 1705 } else { 1706 deleteFile(mRecordingFile); 1707 } 1708 mRecordingFile = null; 1709 } else { 1710 logE("Recorder is unexpectedly null!"); 1711 } 1712 } 1713 1714 static int getCallbackBufferSize(int width, int height, int format) { 1715 int size = -1; 1716 switch (format) { 1717 case ImageFormat.NV21: 1718 size = width * height * 3 / 2; 1719 break; 1720 case ImageFormat.YV12: 1721 int y_stride = (int) (Math.ceil( width / 16.) * 16); 1722 int y_size = y_stride * height; 1723 int c_stride = (int) (Math.ceil(y_stride / 32.) * 16); 1724 int c_size = c_stride * height/2; 1725 size = y_size + c_size * 2; 1726 break; 1727 case ImageFormat.NV16: 1728 case ImageFormat.RGB_565: 1729 case ImageFormat.YUY2: 1730 size = 2 * width * height; 1731 break; 1732 case ImageFormat.JPEG: 1733 Log.e(TAG, "JPEG callback buffers not supported!"); 1734 size = 0; 1735 break; 1736 case ImageFormat.UNKNOWN: 1737 Log.e(TAG, "Unknown-format callback buffers not supported!"); 1738 size = 0; 1739 break; 1740 } 1741 return size; 1742 } 1743 1744 private OnItemSelectedListener mColorEffectListener = 1745 new OnItemSelectedListener() { 1746 @Override 1747 public void onItemSelected(AdapterView<?> parent, 1748 View view, int pos, long id) { 1749 if (pos == mColorEffect) return; 1750 1751 mColorEffect = pos; 1752 String colorEffect = mColorEffects.get(mColorEffect); 1753 log("Setting color effect to " + colorEffect); 1754 mParams.setColorEffect(colorEffect); 1755 mCamera.setParameters(mParams); 1756 } 1757 1758 @Override 1759 public void onNothingSelected(AdapterView<?> arg0) { 1760 } 1761 }; 1762 1763 private void updateColorEffects(Parameters params) { 1764 mColorEffects = params.getSupportedColorEffects(); 1765 if (mColorEffects != null) { 1766 mColorEffectSpinnerLabel.setVisibility(View.VISIBLE); 1767 mColorEffectSpinner.setVisibility(View.VISIBLE); 1768 mColorEffectSpinner.setAdapter( 1769 new ArrayAdapter<String>(this, R.layout.spinner_item, 1770 mColorEffects.toArray(new String[0]))); 1771 mColorEffect = 0; 1772 params.setColorEffect(mColorEffects.get(mColorEffect)); 1773 log("Setting Color Effect to " + mColorEffects.get(mColorEffect)); 1774 } else { 1775 mColorEffectSpinnerLabel.setVisibility(View.GONE); 1776 mColorEffectSpinner.setVisibility(View.GONE); 1777 } 1778 } 1779 1780 private int mLogIndentLevel = 0; 1781 private String mLogIndent = "\t"; 1782 /** Increment or decrement log indentation level */ 1783 synchronized void logIndent(int delta) { 1784 mLogIndentLevel += delta; 1785 if (mLogIndentLevel < 0) mLogIndentLevel = 0; 1786 char[] mLogIndentArray = new char[mLogIndentLevel + 1]; 1787 for (int i = -1; i < mLogIndentLevel; i++) { 1788 mLogIndentArray[i + 1] = '\t'; 1789 } 1790 mLogIndent = new String(mLogIndentArray); 1791 } 1792 1793 @SuppressLint("SimpleDateFormat") 1794 SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); 1795 /** Log both to log text view and to device logcat */ 1796 void log(String logLine) { 1797 Log.d(TAG, logLine); 1798 logAndScrollToBottom(logLine, mLogIndent); 1799 } 1800 1801 void logE(String logLine) { 1802 Log.e(TAG, logLine); 1803 logAndScrollToBottom(logLine, mLogIndent + "!!! "); 1804 } 1805 1806 synchronized private void logAndScrollToBottom(String logLine, String logIndent) { 1807 StringBuffer logEntry = new StringBuffer(32); 1808 logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent); 1809 logEntry.append(logLine); 1810 mLogView.append(logEntry); 1811 final Layout layout = mLogView.getLayout(); 1812 if (layout != null){ 1813 int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1) 1814 - mLogView.getScrollY() - mLogView.getHeight(); 1815 if(scrollDelta > 0) { 1816 mLogView.scrollBy(0, scrollDelta); 1817 } 1818 } 1819 } 1820 } 1821