PanoramaActivity.java revision 5fde6d5259fccaed4bc517697a4930d3b69ae860
1/* 2 * Copyright (C) 2011 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.camera.panorama; 18 19import com.android.camera.CameraDisabledException; 20import com.android.camera.CameraHardwareException; 21import com.android.camera.CameraHolder; 22import com.android.camera.MenuHelper; 23import com.android.camera.ModePicker; 24import com.android.camera.R; 25import com.android.camera.Storage; 26import com.android.camera.Util; 27 28import android.app.Activity; 29import android.app.ProgressDialog; 30import android.content.Context; 31import android.graphics.Bitmap; 32import android.graphics.BitmapFactory; 33import android.graphics.ImageFormat; 34import android.graphics.PixelFormat; 35import android.graphics.Rect; 36import android.graphics.SurfaceTexture; 37import android.graphics.YuvImage; 38import android.hardware.Camera; 39import android.hardware.Camera.Parameters; 40import android.hardware.Camera.Size; 41import android.hardware.Sensor; 42import android.hardware.SensorEvent; 43import android.hardware.SensorEventListener; 44import android.hardware.SensorManager; 45import android.os.Bundle; 46import android.os.Handler; 47import android.os.Message; 48import android.util.Log; 49import android.view.View; 50import android.view.WindowManager; 51import android.widget.Button; 52import android.widget.ImageView; 53import android.widget.TextView; 54 55import java.io.ByteArrayOutputStream; 56import java.util.List; 57 58/** 59 * Activity to handle panorama capturing. 60 */ 61public class PanoramaActivity extends Activity implements 62 ModePicker.OnModeChangeListener, 63 SurfaceTexture.OnFrameAvailableListener, 64 MosaicRendererSurfaceViewRenderer.MosaicSurfaceCreateListener { 65 public static final int DEFAULT_SWEEP_ANGLE = 160; 66 public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; 67 public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; 68 69 private static final int MSG_FINAL_MOSAIC_READY = 1; 70 private static final int MSG_RESET_TO_PREVIEW = 2; 71 72 private static final String TAG = "PanoramaActivity"; 73 private static final int PREVIEW_STOPPED = 0; 74 private static final int PREVIEW_ACTIVE = 1; 75 private static final int CAPTURE_VIEWFINDER = 0; 76 private static final int CAPTURE_MOSAIC = 1; 77 78 // Speed is in unit of deg/sec 79 private static final float PANNING_SPEED_THRESHOLD = 30f; 80 81 // Ratio of nanosecond to second 82 private static final float NS2S = 1.0f / 1000000000.0f; 83 84 private boolean mPausing; 85 86 private View mPanoControlLayout; 87 private View mCaptureLayout; 88 private Button mStopCaptureButton; 89 private View mReviewLayout; 90 private ImageView mReview; 91 private CaptureView mCaptureView; 92 private MosaicRendererSurfaceView mMosaicView; 93 private TextView mTooFastPrompt; 94 95 private ProgressDialog mProgressDialog; 96 private String mPreparePreviewString; 97 private String mGeneratePanoramaString; 98 99 private int mPreviewWidth; 100 private int mPreviewHeight; 101 private Camera mCameraDevice; 102 private int mCameraState; 103 private int mCaptureState; 104 private SensorManager mSensorManager; 105 private Sensor mSensor; 106 private ModePicker mModePicker; 107 private MosaicFrameProcessor mMosaicFrameProcessor; 108 private String mCurrentImagePath = null; 109 private long mTimeTaken; 110 private Handler mMainHandler; 111 private SurfaceTexture mSurfaceTexture; 112 private boolean mThreadRunning; 113 private float[] mTransformMatrix; 114 private float mHorizontalViewAngle; 115 private float mVerticalViewAngle; 116 117 @Override 118 public void onCreate(Bundle icicle) { 119 super.onCreate(icicle); 120 121 getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 122 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 123 124 createContentView(); 125 126 mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 127 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); 128 if (mSensor == null) { 129 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); 130 } 131 132 mTransformMatrix = new float[16]; 133 134 mPreparePreviewString = 135 getResources().getString(R.string.pano_dialog_prepare_preview); 136 mGeneratePanoramaString = 137 getResources().getString(R.string.pano_dialog_generate_panorama); 138 139 mMainHandler = new Handler() { 140 @Override 141 public void handleMessage(Message msg) { 142 switch (msg.what) { 143 case MSG_FINAL_MOSAIC_READY: 144 onBackgroundThreadFinished(); 145 showFinalMosaic((Bitmap) msg.obj); 146 break; 147 case MSG_RESET_TO_PREVIEW: 148 onBackgroundThreadFinished(); 149 resetToPreview(); 150 break; 151 } 152 clearMosaicFrameProcessorIfNeeded(); 153 } 154 }; 155 } 156 157 private void setupCamera() { 158 openCamera(); 159 Parameters parameters = mCameraDevice.getParameters(); 160 setupCaptureParams(parameters); 161 configureCamera(parameters); 162 } 163 164 private void releaseCamera() { 165 if (mCameraDevice != null) { 166 mCameraDevice.setPreviewCallbackWithBuffer(null); 167 CameraHolder.instance().release(); 168 mCameraDevice = null; 169 mCameraState = PREVIEW_STOPPED; 170 } 171 } 172 173 private void openCamera() { 174 try { 175 mCameraDevice = Util.openCamera(this, CameraHolder.instance().getBackCameraId()); 176 } catch (CameraHardwareException e) { 177 Util.showErrorAndFinish(this, R.string.cannot_connect_camera); 178 return; 179 } catch (CameraDisabledException e) { 180 Util.showErrorAndFinish(this, R.string.camera_disabled); 181 return; 182 } 183 } 184 185 private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3, 186 boolean needSmaller) { 187 int pixelsDiff = DEFAULT_CAPTURE_PIXELS; 188 boolean hasFound = false; 189 for (Size size : supportedSizes) { 190 int h = size.height; 191 int w = size.width; 192 // we only want 4:3 format. 193 int d = DEFAULT_CAPTURE_PIXELS - h * w; 194 if (needSmaller && d < 0) { // no bigger preview than 960x720. 195 continue; 196 } 197 if (need4To3 && (h * 4 != w * 3)) { 198 continue; 199 } 200 d = Math.abs(d); 201 if (d < pixelsDiff) { 202 mPreviewWidth = w; 203 mPreviewHeight = h; 204 pixelsDiff = d; 205 hasFound = true; 206 } 207 } 208 return hasFound; 209 } 210 211 private void setupCaptureParams(Parameters parameters) { 212 List<Size> supportedSizes = parameters.getSupportedPreviewSizes(); 213 if (!findBestPreviewSize(supportedSizes, true, true)) { 214 Log.w(TAG, "No 4:3 ratio preview size supported."); 215 if (!findBestPreviewSize(supportedSizes, false, true)) { 216 Log.w(TAG, "Can't find a supported preview size smaller than 960x720."); 217 findBestPreviewSize(supportedSizes, false, false); 218 } 219 } 220 Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth); 221 parameters.setPreviewSize(mPreviewWidth, mPreviewHeight); 222 223 List<int[]> frameRates = parameters.getSupportedPreviewFpsRange(); 224 int last = frameRates.size() - 1; 225 int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX]; 226 int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX]; 227 parameters.setPreviewFpsRange(minFps, maxFps); 228 Log.v(TAG, "preview fps: " + minFps + ", " + maxFps); 229 230 parameters.setRecordingHint(false); 231 232 mHorizontalViewAngle = parameters.getHorizontalViewAngle(); 233 mVerticalViewAngle = parameters.getVerticalViewAngle(); 234 } 235 236 public int getPreviewBufSize() { 237 PixelFormat pixelInfo = new PixelFormat(); 238 PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo); 239 // TODO: remove this extra 32 byte after the driver bug is fixed. 240 return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32; 241 } 242 243 private void configureCamera(Parameters parameters) { 244 mCameraDevice.setParameters(parameters); 245 246 int orientation = Util.getDisplayOrientation(Util.getDisplayRotation(this), 247 CameraHolder.instance().getBackCameraId()); 248 mCameraDevice.setDisplayOrientation(orientation); 249 } 250 251 private boolean switchToOtherMode(int mode) { 252 if (isFinishing()) { 253 return false; 254 } 255 MenuHelper.gotoMode(mode, this); 256 finish(); 257 return true; 258 } 259 260 public boolean onModeChanged(int mode) { 261 if (mode != ModePicker.MODE_PANORAMA) { 262 return switchToOtherMode(mode); 263 } else { 264 return true; 265 } 266 } 267 268 @Override 269 public void onMosaicSurfaceCreated(final int textureID) { 270 runOnUiThread(new Runnable() { 271 @Override 272 public void run() { 273 if (mSurfaceTexture != null) { 274 mSurfaceTexture.release(); 275 } 276 mSurfaceTexture = new SurfaceTexture(textureID); 277 if (!mPausing) { 278 mSurfaceTexture.setOnFrameAvailableListener(PanoramaActivity.this); 279 startCameraPreview(); 280 } 281 } 282 }); 283 } 284 285 public void runViewFinder() { 286 mMosaicView.setWarping(false); 287 // Call preprocess to render it to low-res and high-res RGB textures. 288 mMosaicView.preprocess(mTransformMatrix); 289 mMosaicView.setReady(); 290 mMosaicView.requestRender(); 291 } 292 293 public void runMosaicCapture() { 294 mMosaicView.setWarping(true); 295 // Call preprocess to render it to low-res and high-res RGB textures. 296 mMosaicView.preprocess(mTransformMatrix); 297 // Lock the conditional variable to ensure the order of transferGPUtoCPU and 298 // mMosaicFrame.processFrame(). 299 mMosaicView.lockPreviewReadyFlag(); 300 // Now, transfer the textures from GPU to CPU memory for processing 301 mMosaicView.transferGPUtoCPU(); 302 // Wait on the condition variable (will be opened when GPU->CPU transfer is done). 303 mMosaicView.waitUntilPreviewReady(); 304 mMosaicFrameProcessor.processFrame(); 305 } 306 307 public synchronized void onFrameAvailable(SurfaceTexture surface) { 308 /* This function may be called by some random thread, 309 * so let's be safe and use synchronize. No OpenGL calls can be done here. 310 */ 311 // Updating the texture should be done in the GL thread which mMosaicView is attached. 312 mMosaicView.queueEvent(new Runnable() { 313 @Override 314 public void run() { 315 mSurfaceTexture.updateTexImage(); 316 mSurfaceTexture.getTransformMatrix(mTransformMatrix); 317 } 318 }); 319 // Update the transformation matrix for mosaic pre-process. 320 if (mCaptureState == CAPTURE_VIEWFINDER) { 321 runViewFinder(); 322 } else { 323 runMosaicCapture(); 324 } 325 } 326 327 public void startCapture() { 328 // Reset values so we can do this again. 329 mTimeTaken = System.currentTimeMillis(); 330 mCaptureState = CAPTURE_MOSAIC; 331 mTooFastPrompt.setVisibility(View.GONE); 332 333 mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { 334 @Override 335 public void onProgress(boolean isFinished, float panningRateX, float panningRateY, 336 int traversedAngleX, int traversedAngleY) { 337 if (isFinished) { 338 stopCapture(); 339 } else { 340 updateProgress(panningRateX, panningRateY, traversedAngleX, traversedAngleY); 341 } 342 } 343 }); 344 345 mStopCaptureButton.setVisibility(View.VISIBLE); 346 mCaptureView.setVisibility(View.VISIBLE); 347 mMosaicView.setVisibility(View.VISIBLE); 348 mPanoControlLayout.setVisibility(View.GONE); 349 350 } 351 352 private void stopCapture() { 353 mCaptureState = CAPTURE_VIEWFINDER; 354 355 mMosaicFrameProcessor.setProgressListener(null); 356 stopCameraPreview(); 357 358 mSurfaceTexture.setOnFrameAvailableListener(null); 359 360 // TODO: show some dialog for long computation. 361 if (!mThreadRunning) { 362 runBackgroundThread(mPreparePreviewString, new Thread() { 363 @Override 364 public void run() { 365 byte[] jpegData = generateFinalMosaic(false); 366 Bitmap bitmap = null; 367 if (jpegData != null) { 368 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 369 } 370 mMainHandler.sendMessage(mMainHandler.obtainMessage( 371 MSG_FINAL_MOSAIC_READY, bitmap)); 372 } 373 }); 374 } 375 } 376 377 private void updateProgress(float panningRateX, float panningRateY, 378 int traversedAngleX, int traversedAngleY) { 379 380 mMosaicView.setReady(); 381 mMosaicView.requestRender(); 382 383 // TODO: Now we just display warning message by the panning speed. 384 // Since we only support horizontal panning, we should display a warning message 385 // in UI when there're significant vertical movements. 386 if ((panningRateX * mHorizontalViewAngle > PANNING_SPEED_THRESHOLD) 387 || (panningRateY * mVerticalViewAngle > PANNING_SPEED_THRESHOLD)) { 388 // TODO: draw speed indication according to the UI spec. 389 mTooFastPrompt.setVisibility(View.VISIBLE); 390 mCaptureView.setSweepAngle(Math.max(traversedAngleX, traversedAngleY) + 1); 391 mCaptureView.invalidate(); 392 } else { 393 mTooFastPrompt.setVisibility(View.GONE); 394 mCaptureView.setSweepAngle(Math.max(traversedAngleX, traversedAngleY) + 1); 395 mCaptureView.invalidate(); 396 } 397 } 398 399 private void createContentView() { 400 setContentView(R.layout.panorama); 401 402 mCaptureState = CAPTURE_VIEWFINDER; 403 404 mCaptureLayout = (View) findViewById(R.id.pano_capture_layout); 405 mCaptureView = (CaptureView) findViewById(R.id.pano_capture_view); 406 mCaptureView.setStartAngle(-DEFAULT_SWEEP_ANGLE / 2); 407 mStopCaptureButton = (Button) findViewById(R.id.pano_capture_stop_button); 408 mTooFastPrompt = (TextView) findViewById(R.id.pano_capture_too_fast_textview); 409 410 mReviewLayout = (View) findViewById(R.id.pano_review_layout); 411 mReview = (ImageView) findViewById(R.id.pano_reviewarea); 412 mMosaicView = (MosaicRendererSurfaceView) findViewById(R.id.pano_renderer); 413 mMosaicView.getRenderer().setMosaicSurfaceCreateListener(this); 414 mMosaicView.setVisibility(View.VISIBLE); 415 416 mPanoControlLayout = (View) findViewById(R.id.pano_control_layout); 417 mModePicker = (ModePicker) findViewById(R.id.mode_picker); 418 mModePicker.setVisibility(View.VISIBLE); 419 mModePicker.setOnModeChangeListener(this); 420 mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA); 421 } 422 423 @OnClickAttr 424 public void onStartButtonClicked(View v) { 425 // If mSurfaceTexture == null then GL setup is not finished yet. 426 // No buttons can be pressed. 427 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 428 startCapture(); 429 } 430 431 @OnClickAttr 432 public void onStopButtonClicked(View v) { 433 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 434 stopCapture(); 435 } 436 437 @OnClickAttr 438 public void onOkButtonClicked(View v) { 439 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 440 runBackgroundThread(mGeneratePanoramaString, new Thread() { 441 @Override 442 public void run() { 443 byte[] jpegData = generateFinalMosaic(true); 444 savePanorama(jpegData); 445 mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW)); 446 } 447 }); 448 } 449 450 private void runBackgroundThread(String str, Thread thread) { 451 mThreadRunning = true; 452 mProgressDialog = ProgressDialog.show(this, "", str); 453 thread.start(); 454 } 455 456 private void onBackgroundThreadFinished() { 457 mThreadRunning = false; 458 mProgressDialog.dismiss(); 459 mProgressDialog = null; 460 } 461 462 @OnClickAttr 463 public void onRetakeButtonClicked(View v) { 464 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 465 resetToPreview(); 466 } 467 468 private void resetToPreview() { 469 mCaptureState = CAPTURE_VIEWFINDER; 470 471 mReviewLayout.setVisibility(View.GONE); 472 mStopCaptureButton.setVisibility(View.GONE); 473 mCaptureView.setVisibility(View.GONE); 474 mPanoControlLayout.setVisibility(View.VISIBLE); 475 mCaptureLayout.setVisibility(View.VISIBLE); 476 mMosaicFrameProcessor.reset(); 477 478 mSurfaceTexture.setOnFrameAvailableListener(this); 479 480 if (!mPausing) startCameraPreview(); 481 482 mMosaicView.setVisibility(View.VISIBLE); 483 } 484 485 private void showFinalMosaic(Bitmap bitmap) { 486 if (bitmap != null) { 487 mReview.setImageBitmap(bitmap); 488 } 489 mCaptureLayout.setVisibility(View.GONE); 490 mReviewLayout.setVisibility(View.VISIBLE); 491 mCaptureView.setSweepAngle(0); 492 } 493 494 private void savePanorama(byte[] jpegData) { 495 if (jpegData != null) { 496 String imagePath = PanoUtil.createName( 497 getResources().getString(R.string.pano_file_name_format), mTimeTaken); 498 Storage.addImage(getContentResolver(), imagePath, mTimeTaken, null, 0, 499 jpegData); 500 } 501 } 502 503 private void clearMosaicFrameProcessorIfNeeded() { 504 if (!mPausing || mThreadRunning) return; 505 mMosaicFrameProcessor.clear(); 506 } 507 508 private void initMosaicFrameProcessorIfNeeded() { 509 if (mPausing || mThreadRunning) return; 510 if (mMosaicFrameProcessor == null) { 511 // Start the activity for the first time. 512 mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5, 513 mPreviewWidth, mPreviewHeight, getPreviewBufSize()); 514 } 515 mMosaicFrameProcessor.initialize(); 516 } 517 518 @Override 519 protected void onPause() { 520 super.onPause(); 521 522 releaseCamera(); 523 mPausing = true; 524 mMosaicView.onPause(); 525 mSensorManager.unregisterListener(mListener); 526 clearMosaicFrameProcessorIfNeeded(); 527 System.gc(); 528 } 529 530 @Override 531 protected void onResume() { 532 super.onResume(); 533 534 mPausing = false; 535 /* 536 * It is not necessary to get accelerometer events at a very high rate, 537 * by using a slower rate (SENSOR_DELAY_UI), we get an automatic 538 * low-pass filter, which "extracts" the gravity component of the 539 * acceleration. As an added benefit, we use less power and CPU 540 * resources. 541 */ 542 mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); 543 mCaptureState = CAPTURE_VIEWFINDER; 544 setupCamera(); 545 if (mSurfaceTexture != null) { 546 mSurfaceTexture.setOnFrameAvailableListener(this); 547 startCameraPreview(); 548 } 549 // Camera must be initialized before MosaicFrameProcessor is initialized. The preview size 550 // has to be decided by camera device. 551 initMosaicFrameProcessorIfNeeded(); 552 mMosaicView.onResume(); 553 } 554 555 private final SensorEventListener mListener = new SensorEventListener() { 556 private float mCompassCurrX; // degrees 557 private float mCompassCurrY; // degrees 558 private float mTimestamp; 559 560 public void onSensorChanged(SensorEvent event) { 561 if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { 562 if (mTimestamp != 0) { 563 final float dT = (event.timestamp - mTimestamp) * NS2S; 564 mCompassCurrX += event.values[1] * dT * 180.0f / Math.PI; 565 mCompassCurrY += event.values[0] * dT * 180.0f / Math.PI; 566 } 567 mTimestamp = event.timestamp; 568 569 } else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) { 570 mCompassCurrX = event.values[0]; 571 mCompassCurrY = event.values[1]; 572 } 573 574 if (mMosaicFrameProcessor != null) { 575 mMosaicFrameProcessor.updateCompassValue(mCompassCurrX, mCompassCurrY); 576 } 577 } 578 579 @Override 580 public void onAccuracyChanged(Sensor sensor, int accuracy) { 581 } 582 }; 583 584 public byte[] generateFinalMosaic(boolean highRes) { 585 mMosaicFrameProcessor.createMosaic(highRes); 586 587 byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); 588 if (imageData == null) { 589 Log.e(TAG, "getFinalMosaicNV21() returned null."); 590 return null; 591 } 592 593 int len = imageData.length - 8; 594 int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) 595 + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); 596 int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) 597 + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); 598 Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height); 599 600 if (width <= 0 || height <= 0) { 601 // TODO: pop up a error meesage indicating that the final result is not generated. 602 Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " + 603 height); 604 return null; 605 } 606 607 YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); 608 ByteArrayOutputStream out = new ByteArrayOutputStream(); 609 yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); 610 try { 611 out.close(); 612 } catch (Exception e) { 613 Log.e(TAG, "Exception in storing final mosaic", e); 614 return null; 615 } 616 return out.toByteArray(); 617 } 618 619 private void setPreviewTexture(SurfaceTexture surface) { 620 try { 621 mCameraDevice.setPreviewTexture(surface); 622 } catch (Throwable ex) { 623 releaseCamera(); 624 throw new RuntimeException("setPreviewTexture failed", ex); 625 } 626 } 627 628 private void startCameraPreview() { 629 // If we're previewing already, stop the preview first (this will blank 630 // the screen). 631 if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); 632 633 setPreviewTexture(mSurfaceTexture); 634 635 try { 636 Log.v(TAG, "startPreview"); 637 mCameraDevice.startPreview(); 638 } catch (Throwable ex) { 639 releaseCamera(); 640 throw new RuntimeException("startPreview failed", ex); 641 } 642 mCameraState = PREVIEW_ACTIVE; 643 } 644 645 private void stopCameraPreview() { 646 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 647 Log.v(TAG, "stopPreview"); 648 mCameraDevice.stopPreview(); 649 } 650 mCameraState = PREVIEW_STOPPED; 651 } 652} 653