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