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