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