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