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