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 17 package com.android.camera; 18 19 import android.annotation.TargetApi; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.BitmapFactory; 27 import android.graphics.Canvas; 28 import android.graphics.ImageFormat; 29 import android.graphics.Matrix; 30 import android.graphics.PixelFormat; 31 import android.graphics.Rect; 32 import android.graphics.SurfaceTexture; 33 import android.graphics.YuvImage; 34 import android.graphics.drawable.BitmapDrawable; 35 import android.graphics.drawable.Drawable; 36 import android.hardware.Camera.Parameters; 37 import android.hardware.Camera.Size; 38 import android.net.Uri; 39 import android.os.AsyncTask; 40 import android.os.Handler; 41 import android.os.Message; 42 import android.os.PowerManager; 43 import android.util.Log; 44 import android.view.KeyEvent; 45 import android.view.LayoutInflater; 46 import android.view.MotionEvent; 47 import android.view.OrientationEventListener; 48 import android.view.View; 49 import android.view.View.OnClickListener; 50 import android.view.ViewGroup; 51 import android.view.WindowManager; 52 import android.widget.ImageView; 53 import android.widget.LinearLayout; 54 import android.widget.TextView; 55 56 import com.android.camera.CameraManager.CameraProxy; 57 import com.android.camera.ui.LayoutChangeNotifier; 58 import com.android.camera.ui.LayoutNotifyView; 59 import com.android.camera.ui.PopupManager; 60 import com.android.camera.ui.Rotatable; 61 import com.android.gallery3d.common.ApiHelper; 62 import com.android.gallery3d.exif.ExifData; 63 import com.android.gallery3d.exif.ExifInvalidFormatException; 64 import com.android.gallery3d.exif.ExifOutputStream; 65 import com.android.gallery3d.exif.ExifReader; 66 import com.android.gallery3d.exif.ExifTag; 67 import com.android.gallery3d.ui.GLRootView; 68 69 import java.io.ByteArrayInputStream; 70 import java.io.ByteArrayOutputStream; 71 import java.io.File; 72 import java.io.FileOutputStream; 73 import java.io.IOException; 74 import java.io.InputStream; 75 import java.util.List; 76 import java.util.TimeZone; 77 78 /** 79 * Activity to handle panorama capturing. 80 */ 81 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture 82 public class PanoramaModule implements CameraModule, 83 SurfaceTexture.OnFrameAvailableListener, 84 ShutterButton.OnShutterButtonListener, 85 LayoutChangeNotifier.Listener { 86 87 public static final int DEFAULT_SWEEP_ANGLE = 160; 88 public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; 89 public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; 90 91 private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1; 92 private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2; 93 private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3; 94 private static final int MSG_CLEAR_SCREEN_DELAY = 4; 95 private static final int MSG_CONFIG_MOSAIC_PREVIEW = 5; 96 private static final int MSG_RESET_TO_PREVIEW = 6; 97 98 private static final int SCREEN_DELAY = 2 * 60 * 1000; 99 100 private static final String TAG = "CAM PanoModule"; 101 private static final int PREVIEW_STOPPED = 0; 102 private static final int PREVIEW_ACTIVE = 1; 103 private static final int CAPTURE_STATE_VIEWFINDER = 0; 104 private static final int CAPTURE_STATE_MOSAIC = 1; 105 // The unit of speed is degrees per frame. 106 private static final float PANNING_SPEED_THRESHOLD = 2.5f; 107 108 private ContentResolver mContentResolver; 109 110 private GLRootView mGLRootView; 111 private ViewGroup mPanoLayout; 112 private LinearLayout mCaptureLayout; 113 private View mReviewLayout; 114 private ImageView mReview; 115 private View mCaptureIndicator; 116 private PanoProgressBar mPanoProgressBar; 117 private PanoProgressBar mSavingProgressBar; 118 private Matrix mProgressDirectionMatrix = new Matrix(); 119 private float[] mProgressAngle = new float[2]; 120 private LayoutNotifyView mPreviewArea; 121 private View mLeftIndicator; 122 private View mRightIndicator; 123 private MosaicPreviewRenderer mMosaicPreviewRenderer; 124 private Object mRendererLock = new Object(); 125 private TextView mTooFastPrompt; 126 private ShutterButton mShutterButton; 127 private Object mWaitObject = new Object(); 128 129 private String mPreparePreviewString; 130 private String mDialogTitle; 131 private String mDialogOkString; 132 private String mDialogPanoramaFailedString; 133 private String mDialogWaitingPreviousString; 134 135 private int mIndicatorColor; 136 private int mIndicatorColorFast; 137 private int mReviewBackground; 138 139 private boolean mUsingFrontCamera; 140 private int mPreviewWidth; 141 private int mPreviewHeight; 142 private int mCameraState; 143 private int mCaptureState; 144 private PowerManager.WakeLock mPartialWakeLock; 145 private MosaicFrameProcessor mMosaicFrameProcessor; 146 private boolean mMosaicFrameProcessorInitialized; 147 private AsyncTask <Void, Void, Void> mWaitProcessorTask; 148 private long mTimeTaken; 149 private Handler mMainHandler; 150 private SurfaceTexture mCameraTexture; 151 private boolean mThreadRunning; 152 private boolean mCancelComputation; 153 private float mHorizontalViewAngle; 154 private float mVerticalViewAngle; 155 156 // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of 157 // getting a better image quality by the former. 158 private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY; 159 160 private PanoOrientationEventListener mOrientationEventListener; 161 // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise 162 // respectively. 163 private int mDeviceOrientation; 164 private int mDeviceOrientationAtCapture; 165 private int mCameraOrientation; 166 private int mOrientationCompensation; 167 168 private RotateDialogController mRotateDialog; 169 170 private SoundClips.Player mSoundPlayer; 171 172 private Runnable mOnFrameAvailableRunnable; 173 174 private CameraActivity mActivity; 175 private View mRootView; 176 private CameraProxy mCameraDevice; 177 private boolean mPaused; 178 private boolean mIsCreatingRenderer; 179 private boolean mIsConfigPending; 180 181 private class MosaicJpeg { MosaicJpeg(byte[] data, int width, int height)182 public MosaicJpeg(byte[] data, int width, int height) { 183 this.data = data; 184 this.width = width; 185 this.height = height; 186 this.isValid = true; 187 } 188 MosaicJpeg()189 public MosaicJpeg() { 190 this.data = null; 191 this.width = 0; 192 this.height = 0; 193 this.isValid = false; 194 } 195 196 public final byte[] data; 197 public final int width; 198 public final int height; 199 public final boolean isValid; 200 } 201 202 private class PanoOrientationEventListener extends OrientationEventListener { PanoOrientationEventListener(Context context)203 public PanoOrientationEventListener(Context context) { 204 super(context); 205 } 206 207 @Override onOrientationChanged(int orientation)208 public void onOrientationChanged(int orientation) { 209 // We keep the last known orientation. So if the user first orient 210 // the camera then point the camera to floor or sky, we still have 211 // the correct orientation. 212 if (orientation == ORIENTATION_UNKNOWN) return; 213 mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation); 214 // When the screen is unlocked, display rotation may change. Always 215 // calculate the up-to-date orientationCompensation. 216 int orientationCompensation = mDeviceOrientation 217 + Util.getDisplayRotation(mActivity) % 360; 218 if (mOrientationCompensation != orientationCompensation) { 219 mOrientationCompensation = orientationCompensation; 220 mActivity.getGLRoot().requestLayoutContentPane(); 221 } 222 } 223 } 224 225 @Override init(CameraActivity activity, View parent, boolean reuseScreenNail)226 public void init(CameraActivity activity, View parent, boolean reuseScreenNail) { 227 mActivity = activity; 228 mRootView = parent; 229 230 createContentView(); 231 232 mContentResolver = mActivity.getContentResolver(); 233 if (reuseScreenNail) { 234 mActivity.reuseCameraScreenNail(true); 235 } else { 236 mActivity.createCameraScreenNail(true); 237 } 238 239 // This runs in UI thread. 240 mOnFrameAvailableRunnable = new Runnable() { 241 @Override 242 public void run() { 243 // Frames might still be available after the activity is paused. 244 // If we call onFrameAvailable after pausing, the GL thread will crash. 245 if (mPaused) return; 246 247 MosaicPreviewRenderer renderer = null; 248 synchronized (mRendererLock) { 249 try { 250 while (mMosaicPreviewRenderer == null) { 251 mRendererLock.wait(); 252 } 253 renderer = mMosaicPreviewRenderer; 254 } catch (InterruptedException e) { 255 Log.e(TAG, "Unexpected interruption", e); 256 } 257 } 258 if (mGLRootView.getVisibility() != View.VISIBLE) { 259 renderer.showPreviewFrameSync(); 260 mGLRootView.setVisibility(View.VISIBLE); 261 } else { 262 if (mCaptureState == CAPTURE_STATE_VIEWFINDER) { 263 renderer.showPreviewFrame(); 264 } else { 265 renderer.alignFrameSync(); 266 mMosaicFrameProcessor.processFrame(); 267 } 268 } 269 } 270 }; 271 272 PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); 273 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama"); 274 275 mOrientationEventListener = new PanoOrientationEventListener(mActivity); 276 277 mMosaicFrameProcessor = MosaicFrameProcessor.getInstance(); 278 279 Resources appRes = mActivity.getResources(); 280 mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview); 281 mDialogTitle = appRes.getString(R.string.pano_dialog_title); 282 mDialogOkString = appRes.getString(R.string.dialog_ok); 283 mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed); 284 mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous); 285 286 mGLRootView = (GLRootView) mActivity.getGLRoot(); 287 288 mMainHandler = new Handler() { 289 @Override 290 public void handleMessage(Message msg) { 291 switch (msg.what) { 292 case MSG_LOW_RES_FINAL_MOSAIC_READY: 293 onBackgroundThreadFinished(); 294 showFinalMosaic((Bitmap) msg.obj); 295 saveHighResMosaic(); 296 break; 297 case MSG_GENERATE_FINAL_MOSAIC_ERROR: 298 onBackgroundThreadFinished(); 299 if (mPaused) { 300 resetToPreview(); 301 } else { 302 mRotateDialog.showAlertDialog( 303 mDialogTitle, mDialogPanoramaFailedString, 304 mDialogOkString, new Runnable() { 305 @Override 306 public void run() { 307 resetToPreview(); 308 }}, 309 null, null); 310 } 311 clearMosaicFrameProcessorIfNeeded(); 312 break; 313 case MSG_END_DIALOG_RESET_TO_PREVIEW: 314 onBackgroundThreadFinished(); 315 resetToPreview(); 316 clearMosaicFrameProcessorIfNeeded(); 317 break; 318 case MSG_CLEAR_SCREEN_DELAY: 319 mActivity.getWindow().clearFlags(WindowManager.LayoutParams. 320 FLAG_KEEP_SCREEN_ON); 321 break; 322 case MSG_CONFIG_MOSAIC_PREVIEW: 323 configMosaicPreview(msg.arg1, msg.arg2); 324 break; 325 case MSG_RESET_TO_PREVIEW: 326 resetToPreview(); 327 break; 328 } 329 } 330 }; 331 } 332 333 @Override dispatchTouchEvent(MotionEvent m)334 public boolean dispatchTouchEvent(MotionEvent m) { 335 return mActivity.superDispatchTouchEvent(m); 336 } 337 setupCamera()338 private void setupCamera() throws CameraHardwareException, CameraDisabledException { 339 openCamera(); 340 Parameters parameters = mCameraDevice.getParameters(); 341 setupCaptureParams(parameters); 342 configureCamera(parameters); 343 } 344 releaseCamera()345 private void releaseCamera() { 346 if (mCameraDevice != null) { 347 mCameraDevice.setPreviewCallbackWithBuffer(null); 348 CameraHolder.instance().release(); 349 mCameraDevice = null; 350 mCameraState = PREVIEW_STOPPED; 351 } 352 } 353 openCamera()354 private void openCamera() throws CameraHardwareException, CameraDisabledException { 355 int cameraId = CameraHolder.instance().getBackCameraId(); 356 // If there is no back camera, use the first camera. Camera id starts 357 // from 0. Currently if a camera is not back facing, it is front facing. 358 // This is also forward compatible if we have a new facing other than 359 // back or front in the future. 360 if (cameraId == -1) cameraId = 0; 361 mCameraDevice = Util.openCamera(mActivity, cameraId); 362 mCameraOrientation = Util.getCameraOrientation(cameraId); 363 if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true; 364 } 365 findBestPreviewSize(List<Size> supportedSizes, boolean need4To3, boolean needSmaller)366 private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3, 367 boolean needSmaller) { 368 int pixelsDiff = DEFAULT_CAPTURE_PIXELS; 369 boolean hasFound = false; 370 for (Size size : supportedSizes) { 371 int h = size.height; 372 int w = size.width; 373 // we only want 4:3 format. 374 int d = DEFAULT_CAPTURE_PIXELS - h * w; 375 if (needSmaller && d < 0) { // no bigger preview than 960x720. 376 continue; 377 } 378 if (need4To3 && (h * 4 != w * 3)) { 379 continue; 380 } 381 d = Math.abs(d); 382 if (d < pixelsDiff) { 383 mPreviewWidth = w; 384 mPreviewHeight = h; 385 pixelsDiff = d; 386 hasFound = true; 387 } 388 } 389 return hasFound; 390 } 391 setupCaptureParams(Parameters parameters)392 private void setupCaptureParams(Parameters parameters) { 393 List<Size> supportedSizes = parameters.getSupportedPreviewSizes(); 394 if (!findBestPreviewSize(supportedSizes, true, true)) { 395 Log.w(TAG, "No 4:3 ratio preview size supported."); 396 if (!findBestPreviewSize(supportedSizes, false, true)) { 397 Log.w(TAG, "Can't find a supported preview size smaller than 960x720."); 398 findBestPreviewSize(supportedSizes, false, false); 399 } 400 } 401 Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth); 402 parameters.setPreviewSize(mPreviewWidth, mPreviewHeight); 403 404 List<int[]> frameRates = parameters.getSupportedPreviewFpsRange(); 405 int last = frameRates.size() - 1; 406 int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX]; 407 int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX]; 408 parameters.setPreviewFpsRange(minFps, maxFps); 409 Log.v(TAG, "preview fps: " + minFps + ", " + maxFps); 410 411 List<String> supportedFocusModes = parameters.getSupportedFocusModes(); 412 if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) { 413 parameters.setFocusMode(mTargetFocusMode); 414 } else { 415 // Use the default focus mode and log a message 416 Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode + 417 " becuase the mode is not supported."); 418 } 419 420 parameters.set(Util.RECORDING_HINT, Util.FALSE); 421 422 mHorizontalViewAngle = parameters.getHorizontalViewAngle(); 423 mVerticalViewAngle = parameters.getVerticalViewAngle(); 424 } 425 getPreviewBufSize()426 public int getPreviewBufSize() { 427 PixelFormat pixelInfo = new PixelFormat(); 428 PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo); 429 // TODO: remove this extra 32 byte after the driver bug is fixed. 430 return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32; 431 } 432 configureCamera(Parameters parameters)433 private void configureCamera(Parameters parameters) { 434 mCameraDevice.setParameters(parameters); 435 } 436 configMosaicPreview(final int w, final int h)437 private void configMosaicPreview(final int w, final int h) { 438 synchronized (mRendererLock) { 439 if (mIsCreatingRenderer) { 440 mMainHandler.removeMessages(MSG_CONFIG_MOSAIC_PREVIEW); 441 mMainHandler.obtainMessage(MSG_CONFIG_MOSAIC_PREVIEW, w, h).sendToTarget(); 442 mIsConfigPending = true; 443 return; 444 } 445 mIsCreatingRenderer = true; 446 mIsConfigPending = false; 447 } 448 stopCameraPreview(); 449 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 450 screenNail.setSize(w, h); 451 synchronized (mRendererLock) { 452 if (mMosaicPreviewRenderer != null) { 453 mMosaicPreviewRenderer.release(); 454 } 455 mMosaicPreviewRenderer = null; 456 screenNail.releaseSurfaceTexture(); 457 screenNail.acquireSurfaceTexture(); 458 } 459 mActivity.notifyScreenNailChanged(); 460 final boolean isLandscape = (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); 461 new Thread(new Runnable() { 462 @Override 463 public void run() { 464 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 465 SurfaceTexture surfaceTexture = screenNail.getSurfaceTexture(); 466 if (surfaceTexture == null) { 467 synchronized (mRendererLock) { 468 mIsConfigPending = true; // try config again later. 469 mIsCreatingRenderer = false; 470 mRendererLock.notifyAll(); 471 return; 472 } 473 } 474 MosaicPreviewRenderer renderer = new MosaicPreviewRenderer( 475 screenNail.getSurfaceTexture(), w, h, isLandscape); 476 synchronized (mRendererLock) { 477 mMosaicPreviewRenderer = renderer; 478 mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture(); 479 480 if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) { 481 mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW); 482 } 483 mIsCreatingRenderer = false; 484 mRendererLock.notifyAll(); 485 } 486 } 487 }).start(); 488 } 489 490 // Receives the layout change event from the preview area. So we can set 491 // the camera preview screennail to the same size and initialize the mosaic 492 // preview renderer. 493 @Override onLayoutChange(View v, int l, int t, int r, int b)494 public void onLayoutChange(View v, int l, int t, int r, int b) { 495 Log.i(TAG, "layout change: "+(r - l) + "/" +(b - t)); 496 mActivity.onLayoutChange(v, l, t, r, b); 497 configMosaicPreview(r - l, b - t); 498 } 499 500 @Override onFrameAvailable(SurfaceTexture surface)501 public void onFrameAvailable(SurfaceTexture surface) { 502 /* This function may be called by some random thread, 503 * so let's be safe and jump back to ui thread. 504 * No OpenGL calls can be done here. */ 505 mActivity.runOnUiThread(mOnFrameAvailableRunnable); 506 } 507 hideDirectionIndicators()508 private void hideDirectionIndicators() { 509 mLeftIndicator.setVisibility(View.GONE); 510 mRightIndicator.setVisibility(View.GONE); 511 } 512 showDirectionIndicators(int direction)513 private void showDirectionIndicators(int direction) { 514 switch (direction) { 515 case PanoProgressBar.DIRECTION_NONE: 516 mLeftIndicator.setVisibility(View.VISIBLE); 517 mRightIndicator.setVisibility(View.VISIBLE); 518 break; 519 case PanoProgressBar.DIRECTION_LEFT: 520 mLeftIndicator.setVisibility(View.VISIBLE); 521 mRightIndicator.setVisibility(View.GONE); 522 break; 523 case PanoProgressBar.DIRECTION_RIGHT: 524 mLeftIndicator.setVisibility(View.GONE); 525 mRightIndicator.setVisibility(View.VISIBLE); 526 break; 527 } 528 } 529 startCapture()530 public void startCapture() { 531 // Reset values so we can do this again. 532 mCancelComputation = false; 533 mTimeTaken = System.currentTimeMillis(); 534 mActivity.setSwipingEnabled(false); 535 mActivity.hideSwitcher(); 536 mShutterButton.setImageResource(R.drawable.btn_shutter_recording); 537 mCaptureState = CAPTURE_STATE_MOSAIC; 538 mCaptureIndicator.setVisibility(View.VISIBLE); 539 showDirectionIndicators(PanoProgressBar.DIRECTION_NONE); 540 541 mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { 542 @Override 543 public void onProgress(boolean isFinished, float panningRateX, float panningRateY, 544 float progressX, float progressY) { 545 float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle; 546 float accumulatedVerticalAngle = progressY * mVerticalViewAngle; 547 if (isFinished 548 || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE) 549 || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) { 550 stopCapture(false); 551 } else { 552 float panningRateXInDegree = panningRateX * mHorizontalViewAngle; 553 float panningRateYInDegree = panningRateY * mVerticalViewAngle; 554 updateProgress(panningRateXInDegree, panningRateYInDegree, 555 accumulatedHorizontalAngle, accumulatedVerticalAngle); 556 } 557 } 558 }); 559 560 mPanoProgressBar.reset(); 561 // TODO: calculate the indicator width according to different devices to reflect the actual 562 // angle of view of the camera device. 563 mPanoProgressBar.setIndicatorWidth(20); 564 mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE); 565 mPanoProgressBar.setVisibility(View.VISIBLE); 566 mDeviceOrientationAtCapture = mDeviceOrientation; 567 keepScreenOn(); 568 mActivity.getOrientationManager().lockOrientation(); 569 setupProgressDirectionMatrix(); 570 } 571 setupProgressDirectionMatrix()572 void setupProgressDirectionMatrix() { 573 int degrees = Util.getDisplayRotation(mActivity); 574 int cameraId = CameraHolder.instance().getBackCameraId(); 575 int orientation = Util.getDisplayOrientation(degrees, cameraId); 576 mProgressDirectionMatrix.reset(); 577 mProgressDirectionMatrix.postRotate(orientation); 578 } 579 stopCapture(boolean aborted)580 private void stopCapture(boolean aborted) { 581 mCaptureState = CAPTURE_STATE_VIEWFINDER; 582 mCaptureIndicator.setVisibility(View.GONE); 583 hideTooFastIndication(); 584 hideDirectionIndicators(); 585 586 mMosaicFrameProcessor.setProgressListener(null); 587 stopCameraPreview(); 588 589 mCameraTexture.setOnFrameAvailableListener(null); 590 591 if (!aborted && !mThreadRunning) { 592 mRotateDialog.showWaitingDialog(mPreparePreviewString); 593 // Hide shutter button, shutter icon, etc when waiting for 594 // panorama to stitch 595 mActivity.hideUI(); 596 runBackgroundThread(new Thread() { 597 @Override 598 public void run() { 599 MosaicJpeg jpeg = generateFinalMosaic(false); 600 601 if (jpeg != null && jpeg.isValid) { 602 Bitmap bitmap = null; 603 bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length); 604 mMainHandler.sendMessage(mMainHandler.obtainMessage( 605 MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap)); 606 } else { 607 mMainHandler.sendMessage(mMainHandler.obtainMessage( 608 MSG_END_DIALOG_RESET_TO_PREVIEW)); 609 } 610 } 611 }); 612 } 613 keepScreenOnAwhile(); 614 } 615 showTooFastIndication()616 private void showTooFastIndication() { 617 mTooFastPrompt.setVisibility(View.VISIBLE); 618 // The PreviewArea also contains the border for "too fast" indication. 619 mPreviewArea.setVisibility(View.VISIBLE); 620 mPanoProgressBar.setIndicatorColor(mIndicatorColorFast); 621 mLeftIndicator.setEnabled(true); 622 mRightIndicator.setEnabled(true); 623 } 624 hideTooFastIndication()625 private void hideTooFastIndication() { 626 mTooFastPrompt.setVisibility(View.GONE); 627 // We set "INVISIBLE" instead of "GONE" here because we need mPreviewArea to have layout 628 // information so we can know the size and position for mCameraScreenNail. 629 mPreviewArea.setVisibility(View.INVISIBLE); 630 mPanoProgressBar.setIndicatorColor(mIndicatorColor); 631 mLeftIndicator.setEnabled(false); 632 mRightIndicator.setEnabled(false); 633 } 634 updateProgress(float panningRateXInDegree, float panningRateYInDegree, float progressHorizontalAngle, float progressVerticalAngle)635 private void updateProgress(float panningRateXInDegree, float panningRateYInDegree, 636 float progressHorizontalAngle, float progressVerticalAngle) { 637 mGLRootView.requestRender(); 638 639 if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD) 640 || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) { 641 showTooFastIndication(); 642 } else { 643 hideTooFastIndication(); 644 } 645 646 // progressHorizontalAngle and progressVerticalAngle are relative to the 647 // camera. Convert them to UI direction. 648 mProgressAngle[0] = progressHorizontalAngle; 649 mProgressAngle[1] = progressVerticalAngle; 650 mProgressDirectionMatrix.mapPoints(mProgressAngle); 651 652 int angleInMajorDirection = 653 (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1])) 654 ? (int) mProgressAngle[0] 655 : (int) mProgressAngle[1]; 656 mPanoProgressBar.setProgress((angleInMajorDirection)); 657 } 658 setViews(Resources appRes)659 private void setViews(Resources appRes) { 660 mCaptureState = CAPTURE_STATE_VIEWFINDER; 661 mPanoProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar); 662 mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); 663 mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done)); 664 mPanoProgressBar.setIndicatorColor(mIndicatorColor); 665 mPanoProgressBar.setOnDirectionChangeListener( 666 new PanoProgressBar.OnDirectionChangeListener () { 667 @Override 668 public void onDirectionChange(int direction) { 669 if (mCaptureState == CAPTURE_STATE_MOSAIC) { 670 showDirectionIndicators(direction); 671 } 672 } 673 }); 674 675 mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator); 676 mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator); 677 mLeftIndicator.setEnabled(false); 678 mRightIndicator.setEnabled(false); 679 mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview); 680 // This mPreviewArea also shows the border for visual "too fast" indication. 681 mPreviewArea = (LayoutNotifyView) mRootView.findViewById(R.id.pano_preview_area); 682 mPreviewArea.setOnLayoutChangeListener(this); 683 684 mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar); 685 mSavingProgressBar.setIndicatorWidth(0); 686 mSavingProgressBar.setMaxProgress(100); 687 mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); 688 mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication)); 689 690 mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator); 691 692 mReviewLayout = mRootView.findViewById(R.id.pano_review_layout); 693 mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea); 694 mReview.setBackgroundColor(mReviewBackground); 695 View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button); 696 cancelButton.setOnClickListener(new OnClickListener() { 697 @Override 698 public void onClick(View arg0) { 699 if (mPaused || mCameraTexture == null) return; 700 cancelHighResComputation(); 701 } 702 }); 703 704 mShutterButton = mActivity.getShutterButton(); 705 mShutterButton.setImageResource(R.drawable.btn_new_shutter); 706 mShutterButton.setOnShutterButtonListener(this); 707 708 if (mActivity.getResources().getConfiguration().orientation 709 == Configuration.ORIENTATION_PORTRAIT) { 710 Rotatable view = (Rotatable) mRootView.findViewById(R.id.pano_rotate_reviewarea); 711 view.setOrientation(270, false); 712 } 713 } 714 createContentView()715 private void createContentView() { 716 mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView); 717 Resources appRes = mActivity.getResources(); 718 mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app_root); 719 mIndicatorColor = appRes.getColor(R.color.pano_progress_indication); 720 mReviewBackground = appRes.getColor(R.color.review_background); 721 mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast); 722 mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.pano_layout); 723 mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog); 724 setViews(appRes); 725 } 726 727 @Override onShutterButtonClick()728 public void onShutterButtonClick() { 729 // If mCameraTexture == null then GL setup is not finished yet. 730 // No buttons can be pressed. 731 if (mPaused || mThreadRunning || mCameraTexture == null) return; 732 // Since this button will stay on the screen when capturing, we need to check the state 733 // right now. 734 switch (mCaptureState) { 735 case CAPTURE_STATE_VIEWFINDER: 736 if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return; 737 mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING); 738 startCapture(); 739 break; 740 case CAPTURE_STATE_MOSAIC: 741 mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING); 742 stopCapture(false); 743 } 744 } 745 746 @Override onShutterButtonFocus(boolean pressed)747 public void onShutterButtonFocus(boolean pressed) { 748 } 749 reportProgress()750 public void reportProgress() { 751 mSavingProgressBar.reset(); 752 mSavingProgressBar.setRightIncreasing(true); 753 Thread t = new Thread() { 754 @Override 755 public void run() { 756 while (mThreadRunning) { 757 final int progress = mMosaicFrameProcessor.reportProgress( 758 true, mCancelComputation); 759 760 try { 761 synchronized (mWaitObject) { 762 mWaitObject.wait(50); 763 } 764 } catch (InterruptedException e) { 765 throw new RuntimeException("Panorama reportProgress failed", e); 766 } 767 // Update the progress bar 768 mActivity.runOnUiThread(new Runnable() { 769 @Override 770 public void run() { 771 mSavingProgressBar.setProgress(progress); 772 } 773 }); 774 } 775 } 776 }; 777 t.start(); 778 } 779 getCaptureOrientation()780 private int getCaptureOrientation() { 781 // The panorama image returned from the library is oriented based on the 782 // natural orientation of a camera. We need to set an orientation for the image 783 // in its EXIF header, so the image can be displayed correctly. 784 // The orientation is calculated from compensating the 785 // device orientation at capture and the camera orientation respective to 786 // the natural orientation of the device. 787 int orientation; 788 if (mUsingFrontCamera) { 789 // mCameraOrientation is negative with respect to the front facing camera. 790 // See document of android.hardware.Camera.Parameters.setRotation. 791 orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360; 792 } else { 793 orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360; 794 } 795 return orientation; 796 } 797 saveHighResMosaic()798 public void saveHighResMosaic() { 799 runBackgroundThread(new Thread() { 800 @Override 801 public void run() { 802 mPartialWakeLock.acquire(); 803 MosaicJpeg jpeg; 804 try { 805 jpeg = generateFinalMosaic(true); 806 } finally { 807 mPartialWakeLock.release(); 808 } 809 810 if (jpeg == null) { // Cancelled by user. 811 mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW); 812 } else if (!jpeg.isValid) { // Error when generating mosaic. 813 mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR); 814 } else { 815 int orientation = getCaptureOrientation(); 816 Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation); 817 if (uri != null) { 818 mActivity.addSecureAlbumItemIfNeeded(false, uri); 819 Util.broadcastNewPicture(mActivity, uri); 820 } 821 mMainHandler.sendMessage( 822 mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW)); 823 } 824 } 825 }); 826 reportProgress(); 827 } 828 runBackgroundThread(Thread thread)829 private void runBackgroundThread(Thread thread) { 830 mThreadRunning = true; 831 thread.start(); 832 } 833 onBackgroundThreadFinished()834 private void onBackgroundThreadFinished() { 835 mThreadRunning = false; 836 mRotateDialog.dismissDialog(); 837 } 838 cancelHighResComputation()839 private void cancelHighResComputation() { 840 mCancelComputation = true; 841 synchronized (mWaitObject) { 842 mWaitObject.notify(); 843 } 844 } 845 846 // This function will be called upon the first camera frame is available. reset()847 private void reset() { 848 mCaptureState = CAPTURE_STATE_VIEWFINDER; 849 850 mActivity.getOrientationManager().unlockOrientation(); 851 // We should set mGLRootView visible too. However, since there might be no 852 // frame available yet, setting mGLRootView visible should be done right after 853 // the first camera frame is available and therefore it is done by 854 // mOnFirstFrameAvailableRunnable. 855 mActivity.setSwipingEnabled(true); 856 mShutterButton.setImageResource(R.drawable.btn_new_shutter); 857 mReviewLayout.setVisibility(View.GONE); 858 mPanoProgressBar.setVisibility(View.GONE); 859 mGLRootView.setVisibility(View.VISIBLE); 860 // Orientation change will trigger onLayoutChange->configMosaicPreview-> 861 // resetToPreview. Do not show the capture UI in film strip. 862 if (mActivity.mShowCameraAppView) { 863 mCaptureLayout.setVisibility(View.VISIBLE); 864 mActivity.showUI(); 865 } 866 mMosaicFrameProcessor.reset(); 867 } 868 resetToPreview()869 private void resetToPreview() { 870 reset(); 871 if (!mPaused) startCameraPreview(); 872 } 873 874 private static class FlipBitmapDrawable extends BitmapDrawable { 875 FlipBitmapDrawable(Resources res, Bitmap bitmap)876 public FlipBitmapDrawable(Resources res, Bitmap bitmap) { 877 super(res, bitmap); 878 } 879 880 @Override draw(Canvas canvas)881 public void draw(Canvas canvas) { 882 Rect bounds = getBounds(); 883 int cx = bounds.centerX(); 884 int cy = bounds.centerY(); 885 canvas.save(Canvas.MATRIX_SAVE_FLAG); 886 canvas.rotate(180, cx, cy); 887 super.draw(canvas); 888 canvas.restore(); 889 } 890 } 891 showFinalMosaic(Bitmap bitmap)892 private void showFinalMosaic(Bitmap bitmap) { 893 if (bitmap != null) { 894 int orientation = getCaptureOrientation(); 895 if (orientation >= 180) { 896 // We need to flip the drawable to compensate 897 mReview.setImageDrawable(new FlipBitmapDrawable( 898 mActivity.getResources(), bitmap)); 899 } else { 900 mReview.setImageBitmap(bitmap); 901 } 902 } 903 904 mCaptureLayout.setVisibility(View.GONE); 905 mReviewLayout.setVisibility(View.VISIBLE); 906 } 907 savePanorama(byte[] jpegData, int width, int height, int orientation)908 private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) { 909 if (jpegData != null) { 910 String filename = PanoUtil.createName( 911 mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken); 912 String filepath = Storage.generateFilepath(filename); 913 914 ExifOutputStream out = null; 915 InputStream is = null; 916 try { 917 is = new ByteArrayInputStream(jpegData); 918 ExifReader reader = new ExifReader(); 919 ExifData data = reader.read(is); 920 921 // Add Exif tags. 922 data.addGpsDateTimeStampTag(mTimeTaken); 923 data.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, mTimeTaken, TimeZone.getDefault()); 924 data.addTag(ExifTag.TAG_ORIENTATION). 925 setValue(getExifOrientation(orientation)); 926 927 out = new ExifOutputStream(new FileOutputStream(filepath)); 928 out.setExifData(data); 929 out.write(jpegData); 930 } catch (IOException e) { 931 Log.e(TAG, "Cannot set EXIF for " + filepath, e); 932 Storage.writeFile(filepath, jpegData); 933 } catch (ExifInvalidFormatException e) { 934 Log.e(TAG, "Cannot set EXIF for " + filepath, e); 935 Storage.writeFile(filepath, jpegData); 936 } finally { 937 Util.closeSilently(out); 938 Util.closeSilently(is); 939 } 940 941 int jpegLength = (int) (new File(filepath).length()); 942 return Storage.addImage(mContentResolver, filename, mTimeTaken, 943 null, orientation, jpegLength, filepath, width, height); 944 } 945 return null; 946 } 947 getExifOrientation(int orientation)948 private static int getExifOrientation(int orientation) { 949 switch (orientation) { 950 case 0: 951 return ExifTag.Orientation.TOP_LEFT; 952 case 90: 953 return ExifTag.Orientation.RIGHT_TOP; 954 case 180: 955 return ExifTag.Orientation.BOTTOM_LEFT; 956 case 270: 957 return ExifTag.Orientation.RIGHT_BOTTOM; 958 default: 959 throw new AssertionError("invalid: " + orientation); 960 } 961 } 962 clearMosaicFrameProcessorIfNeeded()963 private void clearMosaicFrameProcessorIfNeeded() { 964 if (!mPaused || mThreadRunning) return; 965 // Only clear the processor if it is initialized by this activity 966 // instance. Other activity instances may be using it. 967 if (mMosaicFrameProcessorInitialized) { 968 mMosaicFrameProcessor.clear(); 969 mMosaicFrameProcessorInitialized = false; 970 } 971 } 972 initMosaicFrameProcessorIfNeeded()973 private void initMosaicFrameProcessorIfNeeded() { 974 if (mPaused || mThreadRunning) return; 975 mMosaicFrameProcessor.initialize( 976 mPreviewWidth, mPreviewHeight, getPreviewBufSize()); 977 mMosaicFrameProcessorInitialized = true; 978 } 979 980 @Override onPauseBeforeSuper()981 public void onPauseBeforeSuper() { 982 mPaused = true; 983 } 984 985 @Override onPauseAfterSuper()986 public void onPauseAfterSuper() { 987 mOrientationEventListener.disable(); 988 if (mCameraDevice == null) { 989 // Camera open failed. Nothing should be done here. 990 return; 991 } 992 // Stop the capturing first. 993 if (mCaptureState == CAPTURE_STATE_MOSAIC) { 994 stopCapture(true); 995 reset(); 996 } 997 998 releaseCamera(); 999 synchronized (mRendererLock) { 1000 mCameraTexture = null; 1001 1002 // The preview renderer might not have a chance to be initialized 1003 // before onPause(). 1004 if (mMosaicPreviewRenderer != null) { 1005 mMosaicPreviewRenderer.release(); 1006 mMosaicPreviewRenderer = null; 1007 } 1008 } 1009 1010 clearMosaicFrameProcessorIfNeeded(); 1011 if (mWaitProcessorTask != null) { 1012 mWaitProcessorTask.cancel(true); 1013 mWaitProcessorTask = null; 1014 } 1015 resetScreenOn(); 1016 if (mSoundPlayer != null) { 1017 mSoundPlayer.release(); 1018 mSoundPlayer = null; 1019 } 1020 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; 1021 screenNail.releaseSurfaceTexture(); 1022 System.gc(); 1023 } 1024 1025 @Override onConfigurationChanged(Configuration newConfig)1026 public void onConfigurationChanged(Configuration newConfig) { 1027 1028 Drawable lowResReview = null; 1029 if (mThreadRunning) lowResReview = mReview.getDrawable(); 1030 1031 // Change layout in response to configuration change 1032 mCaptureLayout.setOrientation( 1033 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE 1034 ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); 1035 mCaptureLayout.removeAllViews(); 1036 LayoutInflater inflater = mActivity.getLayoutInflater(); 1037 inflater.inflate(R.layout.preview_frame_pano, mCaptureLayout); 1038 1039 mPanoLayout.removeView(mReviewLayout); 1040 inflater.inflate(R.layout.pano_review, mPanoLayout); 1041 1042 setViews(mActivity.getResources()); 1043 if (mThreadRunning) { 1044 mReview.setImageDrawable(lowResReview); 1045 mCaptureLayout.setVisibility(View.GONE); 1046 mReviewLayout.setVisibility(View.VISIBLE); 1047 } 1048 } 1049 1050 @Override onOrientationChanged(int orientation)1051 public void onOrientationChanged(int orientation) { 1052 } 1053 1054 @Override onResumeBeforeSuper()1055 public void onResumeBeforeSuper() { 1056 mPaused = false; 1057 } 1058 1059 @Override onResumeAfterSuper()1060 public void onResumeAfterSuper() { 1061 mOrientationEventListener.enable(); 1062 1063 mCaptureState = CAPTURE_STATE_VIEWFINDER; 1064 1065 try { 1066 setupCamera(); 1067 } catch (CameraHardwareException e) { 1068 Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); 1069 return; 1070 } catch (CameraDisabledException e) { 1071 Util.showErrorAndFinish(mActivity, R.string.camera_disabled); 1072 return; 1073 } 1074 1075 // Set up sound playback for shutter button 1076 mSoundPlayer = SoundClips.getPlayer(mActivity); 1077 1078 // Check if another panorama instance is using the mosaic frame processor. 1079 mRotateDialog.dismissDialog(); 1080 if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { 1081 mGLRootView.setVisibility(View.GONE); 1082 mRotateDialog.showWaitingDialog(mDialogWaitingPreviousString); 1083 // If stitching is still going on, make sure switcher and shutter button 1084 // are not showing 1085 mActivity.hideUI(); 1086 mWaitProcessorTask = new WaitProcessorTask().execute(); 1087 } else { 1088 mGLRootView.setVisibility(View.VISIBLE); 1089 // Camera must be initialized before MosaicFrameProcessor is 1090 // initialized. The preview size has to be decided by camera device. 1091 initMosaicFrameProcessorIfNeeded(); 1092 int w = mPreviewArea.getWidth(); 1093 int h = mPreviewArea.getHeight(); 1094 if (w != 0 && h != 0) { // The layout has been calculated. 1095 configMosaicPreview(w, h); 1096 } 1097 } 1098 keepScreenOnAwhile(); 1099 1100 // Dismiss open menu if exists. 1101 PopupManager.getInstance(mActivity).notifyShowPopup(null); 1102 mRootView.requestLayout(); 1103 } 1104 1105 /** 1106 * Generate the final mosaic image. 1107 * 1108 * @param highRes flag to indicate whether we want to get a high-res version. 1109 * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation 1110 * process is cancelled; and a MosaicJpeg with its isValid flag set to false if there 1111 * is an error in generating the final mosaic. 1112 */ generateFinalMosaic(boolean highRes)1113 public MosaicJpeg generateFinalMosaic(boolean highRes) { 1114 int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes); 1115 if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) { 1116 return null; 1117 } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) { 1118 return new MosaicJpeg(); 1119 } 1120 1121 byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); 1122 if (imageData == null) { 1123 Log.e(TAG, "getFinalMosaicNV21() returned null."); 1124 return new MosaicJpeg(); 1125 } 1126 1127 int len = imageData.length - 8; 1128 int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) 1129 + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); 1130 int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) 1131 + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); 1132 Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height); 1133 1134 if (width <= 0 || height <= 0) { 1135 // TODO: pop up an error message indicating that the final result is not generated. 1136 Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " + 1137 height); 1138 return new MosaicJpeg(); 1139 } 1140 1141 YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); 1142 ByteArrayOutputStream out = new ByteArrayOutputStream(); 1143 yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); 1144 try { 1145 out.close(); 1146 } catch (Exception e) { 1147 Log.e(TAG, "Exception in storing final mosaic", e); 1148 return new MosaicJpeg(); 1149 } 1150 return new MosaicJpeg(out.toByteArray(), width, height); 1151 } 1152 startCameraPreview()1153 private void startCameraPreview() { 1154 if (mCameraDevice == null) { 1155 // Camera open failed. Return. 1156 return; 1157 } 1158 1159 // This works around a driver issue. startPreview may fail if 1160 // stopPreview/setPreviewTexture/startPreview are called several times 1161 // in a row. mCameraTexture can be null after pressing home during 1162 // mosaic generation and coming back. Preview will be started later in 1163 // onLayoutChange->configMosaicPreview. This also reduces the latency. 1164 synchronized (mRendererLock) { 1165 if (mCameraTexture == null) return; 1166 1167 // If we're previewing already, stop the preview first (this will 1168 // blank the screen). 1169 if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); 1170 1171 // Set the display orientation to 0, so that the underlying mosaic 1172 // library can always get undistorted mPreviewWidth x mPreviewHeight 1173 // image data from SurfaceTexture. 1174 mCameraDevice.setDisplayOrientation(0); 1175 1176 mCameraTexture.setOnFrameAvailableListener(this); 1177 mCameraDevice.setPreviewTextureAsync(mCameraTexture); 1178 } 1179 mCameraDevice.startPreviewAsync(); 1180 mCameraState = PREVIEW_ACTIVE; 1181 } 1182 stopCameraPreview()1183 private void stopCameraPreview() { 1184 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 1185 Log.v(TAG, "stopPreview"); 1186 mCameraDevice.stopPreview(); 1187 } 1188 mCameraState = PREVIEW_STOPPED; 1189 } 1190 1191 @Override onUserInteraction()1192 public void onUserInteraction() { 1193 if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile(); 1194 } 1195 1196 @Override onBackPressed()1197 public boolean onBackPressed() { 1198 // If panorama is generating low res or high res mosaic, ignore back 1199 // key. So the activity will not be destroyed. 1200 if (mThreadRunning) return true; 1201 return false; 1202 } 1203 resetScreenOn()1204 private void resetScreenOn() { 1205 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); 1206 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1207 } 1208 keepScreenOnAwhile()1209 private void keepScreenOnAwhile() { 1210 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); 1211 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1212 mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY); 1213 } 1214 keepScreenOn()1215 private void keepScreenOn() { 1216 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); 1217 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1218 } 1219 1220 private class WaitProcessorTask extends AsyncTask<Void, Void, Void> { 1221 @Override doInBackground(Void... params)1222 protected Void doInBackground(Void... params) { 1223 synchronized (mMosaicFrameProcessor) { 1224 while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { 1225 try { 1226 mMosaicFrameProcessor.wait(); 1227 } catch (Exception e) { 1228 // ignore 1229 } 1230 } 1231 } 1232 return null; 1233 } 1234 1235 @Override onPostExecute(Void result)1236 protected void onPostExecute(Void result) { 1237 mWaitProcessorTask = null; 1238 mRotateDialog.dismissDialog(); 1239 mGLRootView.setVisibility(View.VISIBLE); 1240 initMosaicFrameProcessorIfNeeded(); 1241 int w = mPreviewArea.getWidth(); 1242 int h = mPreviewArea.getHeight(); 1243 if (w != 0 && h != 0) { // The layout has been calculated. 1244 configMosaicPreview(w, h); 1245 } 1246 resetToPreview(); 1247 } 1248 } 1249 1250 @Override onFullScreenChanged(boolean full)1251 public void onFullScreenChanged(boolean full) { 1252 } 1253 1254 1255 @Override onStop()1256 public void onStop() { 1257 } 1258 1259 @Override installIntentFilter()1260 public void installIntentFilter() { 1261 } 1262 1263 @Override onActivityResult(int requestCode, int resultCode, Intent data)1264 public void onActivityResult(int requestCode, int resultCode, Intent data) { 1265 } 1266 1267 1268 @Override onKeyDown(int keyCode, KeyEvent event)1269 public boolean onKeyDown(int keyCode, KeyEvent event) { 1270 return false; 1271 } 1272 1273 @Override onKeyUp(int keyCode, KeyEvent event)1274 public boolean onKeyUp(int keyCode, KeyEvent event) { 1275 return false; 1276 } 1277 1278 @Override onSingleTapUp(View view, int x, int y)1279 public void onSingleTapUp(View view, int x, int y) { 1280 } 1281 1282 @Override onPreviewTextureCopied()1283 public void onPreviewTextureCopied() { 1284 } 1285 1286 @Override onCaptureTextureCopied()1287 public void onCaptureTextureCopied() { 1288 } 1289 1290 @Override updateStorageHintOnResume()1291 public boolean updateStorageHintOnResume() { 1292 return false; 1293 } 1294 1295 @Override updateCameraAppView()1296 public void updateCameraAppView() { 1297 } 1298 1299 @Override collapseCameraControls()1300 public boolean collapseCameraControls() { 1301 return false; 1302 } 1303 1304 @Override needsSwitcher()1305 public boolean needsSwitcher() { 1306 return true; 1307 } 1308 1309 @Override onShowSwitcherPopup()1310 public void onShowSwitcherPopup() { 1311 } 1312 } 1313