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