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