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 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 { 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 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 { 202 public PanoOrientationEventListener(Context context) { 203 super(context); 204 } 205 206 @Override 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 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 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 353 public boolean isPanoramaActivity() { 354 return true; 355 } 356 357 private void setupCamera() throws CameraHardwareException, CameraDisabledException { 358 openCamera(); 359 Parameters parameters = mCameraDevice.getParameters(); 360 setupCaptureParams(parameters); 361 configureCamera(parameters); 362 } 363 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 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 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 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 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 452 private void configureCamera(Parameters parameters) { 453 mCameraDevice.setParameters(parameters); 454 } 455 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 464 public void onModeChanged(int mode) { 465 if (mode != ModePicker.MODE_PANORAMA) switchToOtherMode(mode); 466 } 467 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 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 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 513 private void hideDirectionIndicators() { 514 mLeftIndicator.setVisibility(View.GONE); 515 mRightIndicator.setVisibility(View.GONE); 516 } 517 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 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 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 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 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 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 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 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 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 754 public void onShutterButtonFocus(boolean pressed) { 755 } 756 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 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 838 private void runBackgroundThread(Thread thread) { 839 mThreadRunning = true; 840 thread.start(); 841 } 842 843 private void onBackgroundThreadFinished() { 844 mThreadRunning = false; 845 mRotateDialog.dismissDialog(); 846 } 847 848 private void cancelHighResComputation() { 849 mCancelComputation = true; 850 synchronized (mWaitObject) { 851 mWaitObject.notify(); 852 } 853 } 854 855 @OnClickAttr 856 public void onCancelButtonClicked(View v) { 857 if (mPaused || mCameraTexture == null) return; 858 cancelHighResComputation(); 859 } 860 861 @OnClickAttr 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. 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 884 private void resetToPreview() { 885 reset(); 886 if (!mPaused) startCameraPreview(); 887 } 888 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 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 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 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 954 private void initMosaicFrameProcessorIfNeeded() { 955 if (mPaused || mThreadRunning) return; 956 mMosaicFrameProcessor.initialize( 957 mPreviewWidth, mPreviewHeight, getPreviewBufSize()); 958 mMosaicFrameProcessorInitialized = true; 959 } 960 961 @Override 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 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 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 */ 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 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 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 1171 public void onUserInteraction() { 1172 super.onUserInteraction(); 1173 if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile(); 1174 } 1175 1176 @Override 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 1185 private void resetScreenOn() { 1186 mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); 1187 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1188 } 1189 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 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 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 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