1 /* 2 * Copyright (C) 2013 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.app.AlertDialog; 20 import android.app.ProgressDialog; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.Canvas; 27 import android.graphics.Matrix; 28 import android.graphics.Point; 29 import android.graphics.Rect; 30 import android.graphics.SurfaceTexture; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.TextureView; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.widget.FrameLayout; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.TextView; 42 43 import com.android.camera.ui.CameraControls; 44 import com.android.camera.ui.CameraRootView; 45 import com.android.camera.ui.ModuleSwitcher; 46 import com.android.camera.util.CameraUtil; 47 import com.android.camera2.R; 48 49 /** 50 * The UI of {@link WideAnglePanoramaModule}. 51 */ 52 public class WideAnglePanoramaUI implements 53 TextureView.SurfaceTextureListener, 54 ShutterButton.OnShutterButtonListener, 55 CameraRootView.MyDisplayListener, 56 View.OnLayoutChangeListener { 57 58 @SuppressWarnings("unused") 59 private static final String TAG = "CAM_WidePanoramaUI"; 60 61 private CameraActivity mActivity; 62 private WideAnglePanoramaController mController; 63 64 private ViewGroup mRootView; 65 private ModuleSwitcher mSwitcher; 66 private FrameLayout mCaptureLayout; 67 private View mReviewLayout; 68 private ImageView mReview; 69 private View mPreviewBorder; 70 private View mLeftIndicator; 71 private View mRightIndicator; 72 private View mCaptureIndicator; 73 private PanoProgressBar mCaptureProgressBar; 74 private PanoProgressBar mSavingProgressBar; 75 private TextView mTooFastPrompt; 76 private View mPreviewLayout; 77 private ViewGroup mReviewControl; 78 private TextureView mTextureView; 79 private ShutterButton mShutterButton; 80 private CameraControls mCameraControls; 81 82 private Matrix mProgressDirectionMatrix = new Matrix(); 83 private float[] mProgressAngle = new float[2]; 84 85 private DialogHelper mDialogHelper; 86 87 // Color definitions. 88 private int mIndicatorColor; 89 private int mIndicatorColorFast; 90 private int mReviewBackground; 91 private SurfaceTexture mSurfaceTexture; 92 private View mPreviewCover; 93 94 /** Constructor. */ WideAnglePanoramaUI( CameraActivity activity, WideAnglePanoramaController controller, ViewGroup root)95 public WideAnglePanoramaUI( 96 CameraActivity activity, 97 WideAnglePanoramaController controller, 98 ViewGroup root) { 99 mActivity = activity; 100 mController = controller; 101 mRootView = root; 102 103 createContentView(); 104 mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher); 105 mSwitcher.setCurrentIndex(ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX); 106 mSwitcher.setSwitchListener(mActivity); 107 } 108 onStartCapture()109 public void onStartCapture() { 110 hideSwitcher(); 111 mShutterButton.setImageResource(R.drawable.btn_shutter_recording); 112 mCaptureIndicator.setVisibility(View.VISIBLE); 113 showDirectionIndicators(PanoProgressBar.DIRECTION_NONE); 114 } 115 showPreviewUI()116 public void showPreviewUI() { 117 mCaptureLayout.setVisibility(View.VISIBLE); 118 showUI(); 119 } 120 onStopCapture()121 public void onStopCapture() { 122 mCaptureIndicator.setVisibility(View.INVISIBLE); 123 hideTooFastIndication(); 124 hideDirectionIndicators(); 125 } 126 hideSwitcher()127 public void hideSwitcher() { 128 mSwitcher.closePopup(); 129 mSwitcher.setVisibility(View.INVISIBLE); 130 } 131 hideUI()132 public void hideUI() { 133 hideSwitcher(); 134 mCameraControls.setVisibility(View.INVISIBLE); 135 } 136 showUI()137 public void showUI() { 138 showSwitcher(); 139 mCameraControls.setVisibility(View.VISIBLE); 140 } 141 onPreviewFocusChanged(boolean previewFocused)142 public void onPreviewFocusChanged(boolean previewFocused) { 143 if (previewFocused) { 144 showUI(); 145 } else { 146 hideUI(); 147 } 148 } 149 arePreviewControlsVisible()150 public boolean arePreviewControlsVisible() { 151 return (mCameraControls.getVisibility() == View.VISIBLE); 152 } 153 showSwitcher()154 public void showSwitcher() { 155 mSwitcher.setVisibility(View.VISIBLE); 156 } 157 setCaptureProgressOnDirectionChangeListener( PanoProgressBar.OnDirectionChangeListener listener)158 public void setCaptureProgressOnDirectionChangeListener( 159 PanoProgressBar.OnDirectionChangeListener listener) { 160 mCaptureProgressBar.setOnDirectionChangeListener(listener); 161 } 162 resetCaptureProgress()163 public void resetCaptureProgress() { 164 mCaptureProgressBar.reset(); 165 } 166 setMaxCaptureProgress(int max)167 public void setMaxCaptureProgress(int max) { 168 mCaptureProgressBar.setMaxProgress(max); 169 } 170 showCaptureProgress()171 public void showCaptureProgress() { 172 mCaptureProgressBar.setVisibility(View.VISIBLE); 173 } 174 updateCaptureProgress( float panningRateXInDegree, float panningRateYInDegree, float progressHorizontalAngle, float progressVerticalAngle, float maxPanningSpeed)175 public void updateCaptureProgress( 176 float panningRateXInDegree, float panningRateYInDegree, 177 float progressHorizontalAngle, float progressVerticalAngle, 178 float maxPanningSpeed) { 179 180 if ((Math.abs(panningRateXInDegree) > maxPanningSpeed) 181 || (Math.abs(panningRateYInDegree) > maxPanningSpeed)) { 182 showTooFastIndication(); 183 } else { 184 hideTooFastIndication(); 185 } 186 187 // progressHorizontalAngle and progressVerticalAngle are relative to the 188 // camera. Convert them to UI direction. 189 mProgressAngle[0] = progressHorizontalAngle; 190 mProgressAngle[1] = progressVerticalAngle; 191 mProgressDirectionMatrix.mapPoints(mProgressAngle); 192 193 int angleInMajorDirection = 194 (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1])) 195 ? (int) mProgressAngle[0] 196 : (int) mProgressAngle[1]; 197 mCaptureProgressBar.setProgress((angleInMajorDirection)); 198 } 199 setProgressOrientation(int orientation)200 public void setProgressOrientation(int orientation) { 201 mProgressDirectionMatrix.reset(); 202 mProgressDirectionMatrix.postRotate(orientation); 203 } 204 showDirectionIndicators(int direction)205 public void showDirectionIndicators(int direction) { 206 switch (direction) { 207 case PanoProgressBar.DIRECTION_NONE: 208 mLeftIndicator.setVisibility(View.VISIBLE); 209 mRightIndicator.setVisibility(View.VISIBLE); 210 break; 211 case PanoProgressBar.DIRECTION_LEFT: 212 mLeftIndicator.setVisibility(View.VISIBLE); 213 mRightIndicator.setVisibility(View.INVISIBLE); 214 break; 215 case PanoProgressBar.DIRECTION_RIGHT: 216 mLeftIndicator.setVisibility(View.INVISIBLE); 217 mRightIndicator.setVisibility(View.VISIBLE); 218 break; 219 } 220 } 221 getSurfaceTexture()222 public SurfaceTexture getSurfaceTexture() { 223 return mSurfaceTexture; 224 } 225 226 @Override onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2)227 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) { 228 mSurfaceTexture = surfaceTexture; 229 mController.onPreviewUIReady(); 230 } 231 232 @Override onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2)233 public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) { 234 235 } 236 237 @Override onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)238 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 239 mController.onPreviewUIDestroyed(); 240 mSurfaceTexture = null; 241 Log.d(TAG, "surfaceTexture is destroyed"); 242 return true; 243 } 244 245 @Override onSurfaceTextureUpdated(SurfaceTexture surfaceTexture)246 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 247 // Make sure preview cover is hidden if preview data is available. 248 if (mPreviewCover.getVisibility() != View.GONE) { 249 mPreviewCover.setVisibility(View.GONE); 250 } 251 } 252 hideDirectionIndicators()253 private void hideDirectionIndicators() { 254 mLeftIndicator.setVisibility(View.INVISIBLE); 255 mRightIndicator.setVisibility(View.INVISIBLE); 256 } 257 getPreviewAreaSize()258 public Point getPreviewAreaSize() { 259 return new Point( 260 mTextureView.getWidth(), mTextureView.getHeight()); 261 } 262 reset()263 public void reset() { 264 mShutterButton.setImageResource(R.drawable.btn_new_shutter); 265 mReviewLayout.setVisibility(View.GONE); 266 mCaptureProgressBar.setVisibility(View.INVISIBLE); 267 } 268 showFinalMosaic(Bitmap bitmap, int orientation)269 public void showFinalMosaic(Bitmap bitmap, int orientation) { 270 if (bitmap != null && orientation != 0) { 271 Matrix rotateMatrix = new Matrix(); 272 rotateMatrix.setRotate(orientation); 273 bitmap = Bitmap.createBitmap( 274 bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), 275 rotateMatrix, false); 276 } 277 278 mReview.setImageBitmap(bitmap); 279 mCaptureLayout.setVisibility(View.GONE); 280 mReviewLayout.setVisibility(View.VISIBLE); 281 } 282 onConfigurationChanged( Configuration newConfig, boolean threadRunning)283 public void onConfigurationChanged( 284 Configuration newConfig, boolean threadRunning) { 285 Drawable lowResReview = null; 286 if (threadRunning) lowResReview = mReview.getDrawable(); 287 288 // Change layout in response to configuration change 289 LayoutInflater inflater = (LayoutInflater) 290 mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 291 292 mReviewControl.removeAllViews(); 293 inflater.inflate(R.layout.pano_review_control, mReviewControl, true); 294 295 mRootView.bringChildToFront(mCameraControls); 296 setViews(mActivity.getResources()); 297 if (threadRunning) { 298 mReview.setImageDrawable(lowResReview); 299 mCaptureLayout.setVisibility(View.GONE); 300 mReviewLayout.setVisibility(View.VISIBLE); 301 } 302 } 303 resetSavingProgress()304 public void resetSavingProgress() { 305 mSavingProgressBar.reset(); 306 mSavingProgressBar.setRightIncreasing(true); 307 } 308 updateSavingProgress(int progress)309 public void updateSavingProgress(int progress) { 310 mSavingProgressBar.setProgress(progress); 311 } 312 313 @Override onShutterButtonFocus(boolean pressed)314 public void onShutterButtonFocus(boolean pressed) { 315 // Do nothing. 316 } 317 318 @Override onShutterButtonClick()319 public void onShutterButtonClick() { 320 mController.onShutterButtonClick(); 321 } 322 323 @Override onLayoutChange( View v, int l, int t, int r, int b, int oldl, int oldt, int oldr, int oldb)324 public void onLayoutChange( 325 View v, int l, int t, int r, int b, 326 int oldl, int oldt, int oldr, int oldb) { 327 mController.onPreviewUILayoutChange(l, t, r, b); 328 } 329 showAlertDialog( String title, String failedString, String OKString, Runnable runnable)330 public void showAlertDialog( 331 String title, String failedString, 332 String OKString, Runnable runnable) { 333 mDialogHelper.showAlertDialog(title, failedString, OKString, runnable); 334 } 335 showWaitingDialog(String title)336 public void showWaitingDialog(String title) { 337 mDialogHelper.showWaitingDialog(title); 338 } 339 dismissAllDialogs()340 public void dismissAllDialogs() { 341 mDialogHelper.dismissAll(); 342 } 343 createContentView()344 private void createContentView() { 345 LayoutInflater inflator = (LayoutInflater) mActivity 346 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 347 inflator.inflate(R.layout.panorama_module, mRootView, true); 348 349 Resources appRes = mActivity.getResources(); 350 mIndicatorColor = appRes.getColor(R.color.pano_progress_indication); 351 mReviewBackground = appRes.getColor(R.color.review_background); 352 mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast); 353 354 mPreviewCover = mRootView.findViewById(R.id.preview_cover); 355 mPreviewLayout = mRootView.findViewById(R.id.pano_preview_layout); 356 mReviewControl = (ViewGroup) mRootView.findViewById(R.id.pano_review_control); 357 mReviewLayout = mRootView.findViewById(R.id.pano_review_layout); 358 mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea); 359 mCaptureLayout = (FrameLayout) mRootView.findViewById(R.id.panorama_capture_layout); 360 mCaptureProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar); 361 mCaptureProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); 362 mCaptureProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done)); 363 mCaptureProgressBar.setIndicatorColor(mIndicatorColor); 364 mCaptureProgressBar.setIndicatorWidth(20); 365 366 mPreviewBorder = mCaptureLayout.findViewById(R.id.pano_preview_area_border); 367 368 mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator); 369 mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator); 370 mLeftIndicator.setEnabled(false); 371 mRightIndicator.setEnabled(false); 372 mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview); 373 mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator); 374 375 mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button); 376 mShutterButton.setImageResource(R.drawable.btn_new_shutter); 377 mShutterButton.setOnShutterButtonListener(this); 378 // Hide menu and indicators. 379 mRootView.findViewById(R.id.menu).setVisibility(View.GONE); 380 mRootView.findViewById(R.id.on_screen_indicators).setVisibility(View.GONE); 381 mReview.setBackgroundColor(mReviewBackground); 382 383 // TODO: set display change listener properly. 384 ((CameraRootView) mRootView).setDisplayChangeListener(null); 385 mTextureView = (TextureView) mRootView.findViewById(R.id.pano_preview_textureview); 386 mTextureView.setSurfaceTextureListener(this); 387 mTextureView.addOnLayoutChangeListener(this); 388 mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls); 389 390 mDialogHelper = new DialogHelper(); 391 setViews(appRes); 392 } 393 setViews(Resources appRes)394 private void setViews(Resources appRes) { 395 int weight = appRes.getInteger(R.integer.SRI_pano_layout_weight); 396 397 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mPreviewLayout.getLayoutParams(); 398 lp.weight = weight; 399 mPreviewLayout.setLayoutParams(lp); 400 401 lp = (LinearLayout.LayoutParams) mReview.getLayoutParams(); 402 lp.weight = weight; 403 mPreviewLayout.setLayoutParams(lp); 404 405 mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar); 406 mSavingProgressBar.setIndicatorWidth(0); 407 mSavingProgressBar.setMaxProgress(100); 408 mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); 409 mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication)); 410 411 View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button); 412 cancelButton.setOnClickListener(new View.OnClickListener() { 413 @Override 414 public void onClick(View arg0) { 415 mController.cancelHighResStitching(); 416 } 417 }); 418 419 420 } 421 showTooFastIndication()422 private void showTooFastIndication() { 423 mTooFastPrompt.setVisibility(View.VISIBLE); 424 // The PreviewArea also contains the border for "too fast" indication. 425 mPreviewBorder.setVisibility(View.VISIBLE); 426 mCaptureProgressBar.setIndicatorColor(mIndicatorColorFast); 427 mLeftIndicator.setEnabled(true); 428 mRightIndicator.setEnabled(true); 429 } 430 hideTooFastIndication()431 private void hideTooFastIndication() { 432 mTooFastPrompt.setVisibility(View.GONE); 433 mPreviewBorder.setVisibility(View.INVISIBLE); 434 mCaptureProgressBar.setIndicatorColor(mIndicatorColor); 435 mLeftIndicator.setEnabled(false); 436 mRightIndicator.setEnabled(false); 437 } 438 flipPreviewIfNeeded()439 public void flipPreviewIfNeeded() { 440 // Rotation needed to display image correctly clockwise 441 int cameraOrientation = mController.getCameraOrientation(); 442 // Display rotated counter-clockwise 443 int displayRotation = CameraUtil.getDisplayRotation(mActivity); 444 // Rotation needed to display image correctly on current display 445 int rotation = (cameraOrientation - displayRotation + 360) % 360; 446 if (rotation >= 180) { 447 mTextureView.setRotation(180); 448 } else { 449 mTextureView.setRotation(0); 450 } 451 } 452 453 @Override onDisplayChanged()454 public void onDisplayChanged() { 455 mCameraControls.checkLayoutFlip(); 456 flipPreviewIfNeeded(); 457 } 458 initDisplayChangeListener()459 public void initDisplayChangeListener() { 460 ((CameraRootView) mRootView).setDisplayChangeListener(this); 461 } 462 removeDisplayChangeListener()463 public void removeDisplayChangeListener() { 464 ((CameraRootView) mRootView).removeDisplayChangeListener(); 465 } 466 showPreviewCover()467 public void showPreviewCover() { 468 mPreviewCover.setVisibility(View.VISIBLE); 469 } 470 471 private class DialogHelper { 472 private ProgressDialog mProgressDialog; 473 private AlertDialog mAlertDialog; 474 DialogHelper()475 DialogHelper() { 476 mProgressDialog = null; 477 mAlertDialog = null; 478 } 479 dismissAll()480 public void dismissAll() { 481 if (mAlertDialog != null) { 482 mAlertDialog.dismiss(); 483 mAlertDialog = null; 484 } 485 if (mProgressDialog != null) { 486 mProgressDialog.dismiss(); 487 mProgressDialog = null; 488 } 489 } 490 showAlertDialog( CharSequence title, CharSequence message, CharSequence buttonMessage, final Runnable buttonRunnable)491 public void showAlertDialog( 492 CharSequence title, CharSequence message, 493 CharSequence buttonMessage, final Runnable buttonRunnable) { 494 dismissAll(); 495 mAlertDialog = (new AlertDialog.Builder(mActivity)) 496 .setTitle(title) 497 .setMessage(message) 498 .setNeutralButton(buttonMessage, new DialogInterface.OnClickListener() { 499 @Override 500 public void onClick(DialogInterface dialogInterface, int i) { 501 buttonRunnable.run(); 502 } 503 }) 504 .show(); 505 } 506 showWaitingDialog(CharSequence message)507 public void showWaitingDialog(CharSequence message) { 508 dismissAll(); 509 mProgressDialog = ProgressDialog.show(mActivity, null, message, true, false); 510 } 511 } 512 513 private static class FlipBitmapDrawable extends BitmapDrawable { 514 FlipBitmapDrawable(Resources res, Bitmap bitmap)515 public FlipBitmapDrawable(Resources res, Bitmap bitmap) { 516 super(res, bitmap); 517 } 518 519 @Override draw(Canvas canvas)520 public void draw(Canvas canvas) { 521 Rect bounds = getBounds(); 522 int cx = bounds.centerX(); 523 int cy = bounds.centerY(); 524 canvas.save(Canvas.MATRIX_SAVE_FLAG); 525 canvas.rotate(180, cx, cy); 526 super.draw(canvas); 527 canvas.restore(); 528 } 529 } 530 } 531