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.app; 18 19 import android.content.res.Resources; 20 import android.graphics.Bitmap; 21 import android.graphics.Canvas; 22 import android.graphics.Matrix; 23 import android.graphics.RectF; 24 import android.graphics.SurfaceTexture; 25 import android.hardware.display.DisplayManager; 26 import android.util.CameraPerformanceTracker; 27 import android.view.GestureDetector; 28 import android.view.LayoutInflater; 29 import android.view.MotionEvent; 30 import android.view.TextureView; 31 import android.view.View; 32 import android.view.ViewConfiguration; 33 import android.view.ViewGroup; 34 import android.widget.FrameLayout; 35 import android.widget.ImageButton; 36 37 import com.android.camera.AccessibilityUtil; 38 import com.android.camera.AnimationManager; 39 import com.android.camera.ButtonManager; 40 import com.android.camera.CaptureLayoutHelper; 41 import com.android.camera.ShutterButton; 42 import com.android.camera.TextureViewHelper; 43 import com.android.camera.debug.Log; 44 import com.android.camera.filmstrip.FilmstripContentPanel; 45 import com.android.camera.hardware.HardwareSpec; 46 import com.android.camera.module.ModuleController; 47 import com.android.camera.settings.Keys; 48 import com.android.camera.settings.SettingsManager; 49 import com.android.camera.ui.AbstractTutorialOverlay; 50 import com.android.camera.ui.BottomBar; 51 import com.android.camera.ui.CaptureAnimationOverlay; 52 import com.android.camera.ui.GridLines; 53 import com.android.camera.ui.MainActivityLayout; 54 import com.android.camera.ui.ModeListView; 55 import com.android.camera.ui.ModeTransitionView; 56 import com.android.camera.ui.PreviewOverlay; 57 import com.android.camera.ui.PreviewStatusListener; 58 import com.android.camera.ui.StickyBottomCaptureLayout; 59 import com.android.camera.ui.TouchCoordinate; 60 import com.android.camera.ui.focus.FocusRing; 61 import com.android.camera.util.AndroidServices; 62 import com.android.camera.util.ApiHelper; 63 import com.android.camera.util.CameraUtil; 64 import com.android.camera.util.Gusterpolator; 65 import com.android.camera.util.PhotoSphereHelper; 66 import com.android.camera.widget.Cling; 67 import com.android.camera.widget.FilmstripLayout; 68 import com.android.camera.widget.IndicatorIconController; 69 import com.android.camera.widget.ModeOptionsOverlay; 70 import com.android.camera.widget.RoundedThumbnailView; 71 import com.android.camera2.R; 72 73 /** 74 * CameraAppUI centralizes control of views shared across modules. Whereas module 75 * specific views will be handled in each Module UI. For example, we can now 76 * bring the flash animation and capture animation up from each module to app 77 * level, as these animations are largely the same for all modules. 78 * 79 * This class also serves to disambiguate touch events. It recognizes all the 80 * swipe gestures that happen on the preview by attaching a touch listener to 81 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge 82 * of how swipe from each direction should be handled, it can then redirect these 83 * events to appropriate recipient views. 84 */ 85 public class CameraAppUI implements ModeListView.ModeSwitchListener, 86 TextureView.SurfaceTextureListener, 87 ModeListView.ModeListOpenListener, 88 SettingsManager.OnSettingChangedListener, 89 ShutterButton.OnShutterButtonListener { 90 91 /** 92 * The bottom controls on the filmstrip. 93 */ 94 public static interface BottomPanel { 95 /** Values for the view state of the button. */ 96 public final int VIEWER_NONE = 0; 97 public final int VIEWER_PHOTO_SPHERE = 1; 98 public final int VIEWER_REFOCUS = 2; 99 public final int VIEWER_OTHER = 3; 100 101 /** 102 * Sets a new or replaces an existing listener for bottom control events. 103 */ setListener(Listener listener)104 void setListener(Listener listener); 105 106 /** 107 * Sets cling for external viewer button. 108 */ setClingForViewer(int viewerType, Cling cling)109 void setClingForViewer(int viewerType, Cling cling); 110 111 /** 112 * Clears cling for external viewer button. 113 */ clearClingForViewer(int viewerType)114 void clearClingForViewer(int viewerType); 115 116 /** 117 * Returns a cling for the specified viewer type. 118 */ getClingForViewer(int viewerType)119 Cling getClingForViewer(int viewerType); 120 121 /** 122 * Set if the bottom controls are visible. 123 * @param visible {@code true} if visible. 124 */ setVisible(boolean visible)125 void setVisible(boolean visible); 126 127 /** 128 * @param visible Whether the button is visible. 129 */ setEditButtonVisibility(boolean visible)130 void setEditButtonVisibility(boolean visible); 131 132 /** 133 * @param enabled Whether the button is enabled. 134 */ setEditEnabled(boolean enabled)135 void setEditEnabled(boolean enabled); 136 137 /** 138 * Sets the visibility of the view-photosphere button. 139 * 140 * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE}, 141 * {@link #VIEWER_REFOCUS}. 142 */ setViewerButtonVisibility(int state)143 void setViewerButtonVisibility(int state); 144 145 /** 146 * @param enabled Whether the button is enabled. 147 */ setViewEnabled(boolean enabled)148 void setViewEnabled(boolean enabled); 149 150 /** 151 * @param enabled Whether the button is enabled. 152 */ setTinyPlanetEnabled(boolean enabled)153 void setTinyPlanetEnabled(boolean enabled); 154 155 /** 156 * @param visible Whether the button is visible. 157 */ setDeleteButtonVisibility(boolean visible)158 void setDeleteButtonVisibility(boolean visible); 159 160 /** 161 * @param enabled Whether the button is enabled. 162 */ setDeleteEnabled(boolean enabled)163 void setDeleteEnabled(boolean enabled); 164 165 /** 166 * @param visible Whether the button is visible. 167 */ setShareButtonVisibility(boolean visible)168 void setShareButtonVisibility(boolean visible); 169 170 /** 171 * @param enabled Whether the button is enabled. 172 */ setShareEnabled(boolean enabled)173 void setShareEnabled(boolean enabled); 174 175 /** 176 * Sets the texts for progress UI. 177 * 178 * @param text The text to show. 179 */ setProgressText(CharSequence text)180 void setProgressText(CharSequence text); 181 182 /** 183 * Sets the progress. 184 * 185 * @param progress The progress value. Should be between 0 and 100. 186 */ setProgress(int progress)187 void setProgress(int progress); 188 189 /** 190 * Replaces the progress UI with an error message. 191 */ showProgressError(CharSequence message)192 void showProgressError(CharSequence message); 193 194 /** 195 * Hide the progress error message. 196 */ hideProgressError()197 void hideProgressError(); 198 199 /** 200 * Shows the progress. 201 */ showProgress()202 void showProgress(); 203 204 /** 205 * Hides the progress. 206 */ hideProgress()207 void hideProgress(); 208 209 /** 210 * Shows the controls. 211 */ showControls()212 void showControls(); 213 214 /** 215 * Hides the controls. 216 */ hideControls()217 void hideControls(); 218 219 /** 220 * Classes implementing this interface can listen for events on the bottom 221 * controls. 222 */ 223 public static interface Listener { 224 /** 225 * Called when the user pressed the "view" button to e.g. view a photo 226 * sphere or RGBZ image. 227 */ onExternalViewer()228 public void onExternalViewer(); 229 230 /** 231 * Called when the "edit" button is pressed. 232 */ onEdit()233 public void onEdit(); 234 235 /** 236 * Called when the "tiny planet" button is pressed. 237 */ onTinyPlanet()238 public void onTinyPlanet(); 239 240 /** 241 * Called when the "delete" button is pressed. 242 */ onDelete()243 public void onDelete(); 244 245 /** 246 * Called when the "share" button is pressed. 247 */ onShare()248 public void onShare(); 249 250 /** 251 * Called when the progress error message is clicked. 252 */ onProgressErrorClicked()253 public void onProgressErrorClicked(); 254 } 255 } 256 257 /** 258 * BottomBarUISpec provides a structure for modules 259 * to specify their ideal bottom bar mode options layout. 260 * 261 * Once constructed by a module, this class should be 262 * treated as read only. 263 * 264 * The application then edits this spec according to 265 * hardware limitations and displays the final bottom 266 * bar ui. 267 */ 268 public static class BottomBarUISpec { 269 /** Mode options UI */ 270 271 /** 272 * Set true if the camera option should be enabled. 273 * If not set or false, and multiple cameras are supported, 274 * the camera option will be disabled. 275 * 276 * If multiple cameras are not supported, this preference 277 * is ignored and the camera option will not be visible. 278 */ 279 public boolean enableCamera; 280 281 /** 282 * Set true if the camera option should not be visible, regardless 283 * of hardware limitations. 284 */ 285 public boolean hideCamera; 286 287 /** 288 * Set true if the photo flash option should be enabled. 289 * If not set or false, the photo flash option will be 290 * disabled. 291 * 292 * If the hardware does not support multiple flash values, 293 * this preference is ignored and the flash option will 294 * be disabled. It will not be made invisible in order to 295 * preserve a consistent experience across devices and between 296 * front and back cameras. 297 */ 298 public boolean enableFlash; 299 300 /** 301 * Set true if the video flash option should be enabled. 302 * Same disable rules apply as the photo flash option. 303 */ 304 public boolean enableTorchFlash; 305 306 /** 307 * Set true if the HDR+ flash option should be enabled. 308 * Same disable rules apply as the photo flash option. 309 */ 310 public boolean enableHdrPlusFlash; 311 312 /** 313 * Set true if flash should not be visible, regardless of 314 * hardware limitations. 315 */ 316 public boolean hideFlash; 317 318 /** 319 * Set true if the hdr/hdr+ option should be enabled. 320 * If not set or false, the hdr/hdr+ option will be disabled. 321 * 322 * Hdr or hdr+ will be chosen based on hardware limitations, 323 * with hdr+ prefered. 324 * 325 * If hardware supports neither hdr nor hdr+, then the hdr/hdr+ 326 * will not be visible. 327 */ 328 public boolean enableHdr; 329 330 /** 331 * Set true if hdr/hdr+ should not be visible, regardless of 332 * hardware limitations. 333 */ 334 public boolean hideHdr; 335 336 /** 337 * Set true if grid lines should be visible. Not setting this 338 * causes grid lines to be disabled. This option is agnostic to 339 * the hardware. 340 */ 341 public boolean enableGridLines; 342 343 /** 344 * Set true if grid lines should not be visible. 345 */ 346 public boolean hideGridLines; 347 348 /** 349 * Set true if the panorama orientation option should be visible. 350 * 351 * This option is not constrained by hardware limitations. 352 */ 353 public boolean enablePanoOrientation; 354 355 /** 356 * Set true if manual exposure compensation should be visible. 357 * 358 * This option is not constrained by hardware limitations. 359 * For example, this is false in HDR+ mode. 360 */ 361 public boolean enableExposureCompensation; 362 363 /** 364 * Set true if the device and module support exposure compensation. 365 * Used only to show exposure button in disabled (greyed out) state. 366 */ 367 public boolean isExposureCompensationSupported; 368 369 /** Intent UI */ 370 371 /** 372 * Set true if the intent ui cancel option should be visible. 373 */ 374 public boolean showCancel; 375 /** 376 * Set true if the intent ui done option should be visible. 377 */ 378 public boolean showDone; 379 /** 380 * Set true if the intent ui retake option should be visible. 381 */ 382 public boolean showRetake; 383 /** 384 * Set true if the intent ui review option should be visible. 385 */ 386 public boolean showReview; 387 388 /** Mode options callbacks */ 389 390 /** 391 * A {@link com.android.camera.ButtonManager.ButtonCallback} 392 * that will be executed when the camera option is pressed. This 393 * callback can be null. 394 */ 395 public ButtonManager.ButtonCallback cameraCallback; 396 397 /** 398 * A {@link com.android.camera.ButtonManager.ButtonCallback} 399 * that will be executed when the flash option is pressed. This 400 * callback can be null. 401 */ 402 public ButtonManager.ButtonCallback flashCallback; 403 404 /** 405 * A {@link com.android.camera.ButtonManager.ButtonCallback} 406 * that will be executed when the hdr/hdr+ option is pressed. This 407 * callback can be null. 408 */ 409 public ButtonManager.ButtonCallback hdrCallback; 410 411 /** 412 * A {@link com.android.camera.ButtonManager.ButtonCallback} 413 * that will be executed when the grid lines option is pressed. This 414 * callback can be null. 415 */ 416 public ButtonManager.ButtonCallback gridLinesCallback; 417 418 /** 419 * A {@link com.android.camera.ButtonManager.ButtonCallback} 420 * that will execute when the panorama orientation option is pressed. 421 * This callback can be null. 422 */ 423 public ButtonManager.ButtonCallback panoOrientationCallback; 424 425 /** Intent UI callbacks */ 426 427 /** 428 * A {@link android.view.View.OnClickListener} that will execute 429 * when the cancel option is pressed. This callback can be null. 430 */ 431 public View.OnClickListener cancelCallback; 432 433 /** 434 * A {@link android.view.View.OnClickListener} that will execute 435 * when the done option is pressed. This callback can be null. 436 */ 437 public View.OnClickListener doneCallback; 438 439 /** 440 * A {@link android.view.View.OnClickListener} that will execute 441 * when the retake option is pressed. This callback can be null. 442 */ 443 public View.OnClickListener retakeCallback; 444 445 /** 446 * A {@link android.view.View.OnClickListener} that will execute 447 * when the review option is pressed. This callback can be null. 448 */ 449 public View.OnClickListener reviewCallback; 450 451 /** 452 * A ExposureCompensationSetCallback that will execute 453 * when an expsosure button is pressed. This callback can be null. 454 */ 455 public interface ExposureCompensationSetCallback { setExposure(int value)456 public void setExposure(int value); 457 } 458 public ExposureCompensationSetCallback exposureCompensationSetCallback; 459 460 /** 461 * Exposure compensation parameters. 462 */ 463 public int minExposureCompensation; 464 public int maxExposureCompensation; 465 public float exposureCompensationStep; 466 467 /** 468 * Whether self-timer is enabled. 469 */ 470 public boolean enableSelfTimer = false; 471 472 /** 473 * Whether the option for self-timer should show. If true and 474 * {@link #enableSelfTimer} is false, then the option should be shown 475 * disabled. 476 */ 477 public boolean showSelfTimer = false; 478 } 479 480 481 private final static Log.Tag TAG = new Log.Tag("CameraAppUI"); 482 483 private final AppController mController; 484 private final boolean mIsCaptureIntent; 485 private final AnimationManager mAnimationManager; 486 487 // Swipe states: 488 private final static int IDLE = 0; 489 private final static int SWIPE_UP = 1; 490 private final static int SWIPE_DOWN = 2; 491 private final static int SWIPE_LEFT = 3; 492 private final static int SWIPE_RIGHT = 4; 493 private boolean mSwipeEnabled = true; 494 495 // Shared Surface Texture properities. 496 private SurfaceTexture mSurface; 497 private int mSurfaceWidth; 498 private int mSurfaceHeight; 499 500 // Touch related measures: 501 private final int mSlop; 502 private final static int SWIPE_TIME_OUT_MS = 500; 503 504 // Mode cover states: 505 private final static int COVER_HIDDEN = 0; 506 private final static int COVER_SHOWN = 1; 507 private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2; 508 private final static int COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE = 3; 509 private final static int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 4; 510 511 /** 512 * Preview down-sample rate when taking a screenshot. 513 */ 514 private final static int DOWN_SAMPLE_RATE_FOR_SCREENSHOT = 2; 515 516 // App level views: 517 private final FrameLayout mCameraRootView; 518 private final ModeTransitionView mModeTransitionView; 519 private final MainActivityLayout mAppRootView; 520 private final ModeListView mModeListView; 521 private final FilmstripLayout mFilmstripLayout; 522 private TextureView mTextureView; 523 private FrameLayout mModuleUI; 524 private ShutterButton mShutterButton; 525 private ImageButton mCountdownCancelButton; 526 private BottomBar mBottomBar; 527 private ModeOptionsOverlay mModeOptionsOverlay; 528 private IndicatorIconController mIndicatorIconController; 529 private FocusRing mFocusRing; 530 private FrameLayout mTutorialsPlaceHolderWrapper; 531 private StickyBottomCaptureLayout mStickyBottomCaptureLayout; 532 private TextureViewHelper mTextureViewHelper; 533 private final GestureDetector mGestureDetector; 534 private DisplayManager.DisplayListener mDisplayListener; 535 private int mLastRotation; 536 private int mSwipeState = IDLE; 537 private PreviewOverlay mPreviewOverlay; 538 private GridLines mGridLines; 539 private CaptureAnimationOverlay mCaptureOverlay; 540 private PreviewStatusListener mPreviewStatusListener; 541 private int mModeCoverState = COVER_HIDDEN; 542 private final FilmstripBottomPanel mFilmstripBottomControls; 543 private final FilmstripContentPanel mFilmstripPanel; 544 private Runnable mHideCoverRunnable; 545 private final View.OnLayoutChangeListener mPreviewLayoutChangeListener 546 = new View.OnLayoutChangeListener() { 547 @Override 548 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 549 int oldTop, int oldRight, int oldBottom) { 550 if (mPreviewStatusListener != null) { 551 mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft, 552 oldTop, oldRight, oldBottom); 553 } 554 } 555 }; 556 private View mModeOptionsToggle; 557 private final RoundedThumbnailView mRoundedThumbnailView; 558 private final CaptureLayoutHelper mCaptureLayoutHelper; 559 private final View mAccessibilityAffordances; 560 private AccessibilityUtil mAccessibilityUtil; 561 562 private boolean mDisableAllUserInteractions; 563 /** Whether to prevent capture indicator from being triggered. */ 564 private boolean mSuppressCaptureIndicator; 565 566 /** Supported HDR mode (none, hdr, hdr+). */ 567 private String mHdrSupportMode; 568 569 /** Used to track the last scope used to update the bottom bar UI. */ 570 private String mCurrentCameraScope; 571 private String mCurrentModuleScope; 572 573 /** 574 * Provides current preview frame and the controls/overlay from the module that 575 * are shown on top of the preview. 576 */ 577 public interface CameraModuleScreenShotProvider { 578 /** 579 * Returns the current preview frame down-sampled using the given down-sample 580 * factor. 581 * 582 * @param downSampleFactor the down sample factor for down sampling the 583 * preview frame. (e.g. a down sample factor of 584 * 2 means to scale down the preview frame to 1/2 585 * the width and height.) 586 * @return down-sampled preview frame 587 */ getPreviewFrame(int downSampleFactor)588 public Bitmap getPreviewFrame(int downSampleFactor); 589 590 /** 591 * @return the controls and overlays that are currently showing on top of 592 * the preview drawn into a bitmap with no scaling applied. 593 */ getPreviewOverlayAndControls()594 public Bitmap getPreviewOverlayAndControls(); 595 596 /** 597 * Returns a bitmap containing the current screenshot. 598 * 599 * @param previewDownSampleFactor the downsample factor applied on the 600 * preview frame when taking the screenshot 601 */ getScreenShot(int previewDownSampleFactor)602 public Bitmap getScreenShot(int previewDownSampleFactor); 603 } 604 605 /** 606 * This listener gets called when the size of the window (excluding the system 607 * decor such as status bar and nav bar) has changed. 608 */ 609 public interface NonDecorWindowSizeChangedListener { onNonDecorWindowSizeChanged(int width, int height, int rotation)610 public void onNonDecorWindowSizeChanged(int width, int height, int rotation); 611 } 612 613 private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider = 614 new CameraModuleScreenShotProvider() { 615 @Override 616 public Bitmap getPreviewFrame(int downSampleFactor) { 617 if (mCameraRootView == null || mTextureView == null) { 618 return null; 619 } 620 // Gets the bitmap from the preview TextureView. 621 Bitmap preview = mTextureViewHelper.getPreviewBitmap(downSampleFactor); 622 return preview; 623 } 624 625 @Override 626 public Bitmap getPreviewOverlayAndControls() { 627 Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(), 628 mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); 629 Canvas canvas = new Canvas(overlays); 630 mCameraRootView.draw(canvas); 631 return overlays; 632 } 633 634 @Override 635 public Bitmap getScreenShot(int previewDownSampleFactor) { 636 Bitmap screenshot = Bitmap.createBitmap(mCameraRootView.getWidth(), 637 mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888); 638 Canvas canvas = new Canvas(screenshot); 639 canvas.drawARGB(255, 0, 0, 0); 640 Bitmap preview = mTextureViewHelper.getPreviewBitmap(previewDownSampleFactor); 641 if (preview != null) { 642 canvas.drawBitmap(preview, null, mTextureViewHelper.getPreviewArea(), null); 643 } 644 Bitmap overlay = getPreviewOverlayAndControls(); 645 if (overlay != null) { 646 canvas.drawBitmap(overlay, 0f, 0f, null); 647 } 648 return screenshot; 649 } 650 }; 651 652 private long mCoverHiddenTime = -1; // System time when preview cover was hidden. 653 getCoverHiddenTime()654 public long getCoverHiddenTime() { 655 return mCoverHiddenTime; 656 } 657 658 /** 659 * This resets the preview to have no applied transform matrix. 660 */ clearPreviewTransform()661 public void clearPreviewTransform() { 662 mTextureViewHelper.clearTransform(); 663 } 664 updatePreviewAspectRatio(float aspectRatio)665 public void updatePreviewAspectRatio(float aspectRatio) { 666 mTextureViewHelper.updateAspectRatio(aspectRatio); 667 } 668 669 /** 670 * WAR: Reset the SurfaceTexture's default buffer size to the current view dimensions of 671 * its TextureView. This is necessary to get the expected behavior for the TextureView's 672 * HardwareLayer transform matrix (set by TextureView#setTransform) after configuring the 673 * SurfaceTexture as an output for the Camera2 API (which involves changing the default buffer 674 * size). 675 * 676 * b/17286155 - Tracking a fix for this in HardwareLayer. 677 */ setDefaultBufferSizeToViewDimens()678 public void setDefaultBufferSizeToViewDimens() { 679 if (mSurface == null || mTextureView == null) { 680 Log.w(TAG, "Could not set SurfaceTexture default buffer dimensions, not yet setup"); 681 return; 682 } 683 mSurface.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight()); 684 } 685 686 /** 687 * Updates the preview matrix without altering it. 688 * 689 * @param matrix 690 * @param aspectRatio the desired aspect ratio for the preview. 691 */ updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio)692 public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) { 693 mTextureViewHelper.updateTransformFullScreen(matrix, aspectRatio); 694 } 695 696 /** 697 * @return the rect that will display the preview. 698 */ getFullscreenRect()699 public RectF getFullscreenRect() { 700 return mTextureViewHelper.getFullscreenRect(); 701 } 702 703 /** 704 * This is to support modules that calculate their own transform matrix because 705 * they need to use a transform matrix to rotate the preview. 706 * 707 * @param matrix transform matrix to be set on the TextureView 708 */ updatePreviewTransform(Matrix matrix)709 public void updatePreviewTransform(Matrix matrix) { 710 mTextureViewHelper.updateTransform(matrix); 711 } 712 713 public interface AnimationFinishedListener { onAnimationFinished(boolean success)714 public void onAnimationFinished(boolean success); 715 } 716 717 private class MyTouchListener implements View.OnTouchListener { 718 private boolean mScaleStarted = false; 719 @Override onTouch(View v, MotionEvent event)720 public boolean onTouch(View v, MotionEvent event) { 721 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 722 mScaleStarted = false; 723 } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 724 mScaleStarted = true; 725 } 726 return (!mScaleStarted) && mGestureDetector.onTouchEvent(event); 727 } 728 } 729 730 /** 731 * This gesture listener finds out the direction of the scroll gestures and 732 * sends them to CameraAppUI to do further handling. 733 */ 734 private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { 735 private MotionEvent mDown; 736 737 @Override onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY)738 public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) { 739 if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS 740 || mSwipeState != IDLE 741 || mIsCaptureIntent 742 || !mSwipeEnabled) { 743 return false; 744 } 745 746 int deltaX = (int) (ev.getX() - mDown.getX()); 747 int deltaY = (int) (ev.getY() - mDown.getY()); 748 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { 749 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) { 750 // Calculate the direction of the swipe. 751 if (deltaX >= Math.abs(deltaY)) { 752 // Swipe right. 753 setSwipeState(SWIPE_RIGHT); 754 } else if (deltaX <= -Math.abs(deltaY)) { 755 // Swipe left. 756 setSwipeState(SWIPE_LEFT); 757 } 758 } 759 } 760 return true; 761 } 762 setSwipeState(int swipeState)763 private void setSwipeState(int swipeState) { 764 mSwipeState = swipeState; 765 // Notify new swipe detected. 766 onSwipeDetected(swipeState); 767 } 768 769 @Override onDown(MotionEvent ev)770 public boolean onDown(MotionEvent ev) { 771 mDown = MotionEvent.obtain(ev); 772 mSwipeState = IDLE; 773 return false; 774 } 775 } 776 CameraAppUI(AppController controller, MainActivityLayout appRootView, boolean isCaptureIntent)777 public CameraAppUI(AppController controller, MainActivityLayout appRootView, 778 boolean isCaptureIntent) { 779 mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop(); 780 mController = controller; 781 mIsCaptureIntent = isCaptureIntent; 782 783 mAppRootView = appRootView; 784 mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout); 785 mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root); 786 mModeTransitionView = (ModeTransitionView) 787 mAppRootView.findViewById(R.id.mode_transition_view); 788 mFilmstripBottomControls = new FilmstripBottomPanel(controller, 789 (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_panel)); 790 mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout); 791 mGestureDetector = new GestureDetector(controller.getAndroidContext(), 792 new MyGestureListener()); 793 Resources res = controller.getAndroidContext().getResources(); 794 mCaptureLayoutHelper = new CaptureLayoutHelper( 795 res.getDimensionPixelSize(R.dimen.bottom_bar_height_min), 796 res.getDimensionPixelSize(R.dimen.bottom_bar_height_max), 797 res.getDimensionPixelSize(R.dimen.bottom_bar_height_optimal)); 798 mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout); 799 if (mModeListView != null) { 800 mModeListView.setModeSwitchListener(this); 801 mModeListView.setModeListOpenListener(this); 802 mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider); 803 mModeListView.setCaptureLayoutHelper(mCaptureLayoutHelper); 804 boolean shouldShowSettingsCling = mController.getSettingsManager().getBoolean( 805 SettingsManager.SCOPE_GLOBAL, 806 Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING); 807 mModeListView.setShouldShowSettingsCling(shouldShowSettingsCling); 808 } else { 809 Log.e(TAG, "Cannot find mode list in the view hierarchy"); 810 } 811 mAnimationManager = new AnimationManager(); 812 mRoundedThumbnailView = (RoundedThumbnailView) appRootView.findViewById(R.id.rounded_thumbnail_view); 813 mRoundedThumbnailView.setCallback(new RoundedThumbnailView.Callback() { 814 @Override 815 public void onHitStateFinished() { 816 mFilmstripLayout.showFilmstrip(); 817 } 818 }); 819 820 mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper); 821 initDisplayListener(); 822 mAccessibilityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances); 823 View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button); 824 modeListToggle.setOnClickListener(new View.OnClickListener() { 825 @Override 826 public void onClick(View view) { 827 openModeList(); 828 } 829 }); 830 View filmstripToggle = mAppRootView.findViewById( 831 R.id.accessibility_filmstrip_toggle_button); 832 filmstripToggle.setOnClickListener(new View.OnClickListener() { 833 @Override 834 public void onClick(View view) { 835 showFilmstrip(); 836 } 837 }); 838 839 mSuppressCaptureIndicator = false; 840 } 841 842 843 /** 844 * Freeze what is currently shown on screen until the next preview frame comes 845 * in. 846 */ freezeScreenUntilPreviewReady()847 public void freezeScreenUntilPreviewReady() { 848 Log.v(TAG, "freezeScreenUntilPreviewReady"); 849 mModeTransitionView.setupModeCover(mCameraModuleScreenShotProvider 850 .getScreenShot(DOWN_SAMPLE_RATE_FOR_SCREENSHOT)); 851 mHideCoverRunnable = new Runnable() { 852 @Override 853 public void run() { 854 mModeTransitionView.hideImageCover(); 855 } 856 }; 857 mModeCoverState = COVER_SHOWN; 858 } 859 860 /** 861 * Creates a cling for the specific viewer and links the cling to the corresponding 862 * button for layout position. 863 * 864 * @param viewerType defines which viewer the cling is for. 865 */ setupClingForViewer(int viewerType)866 public void setupClingForViewer(int viewerType) { 867 if (viewerType == BottomPanel.VIEWER_REFOCUS) { 868 FrameLayout filmstripContent = (FrameLayout) mAppRootView 869 .findViewById(R.id.camera_filmstrip_content_layout); 870 if (filmstripContent != null) { 871 // Creates refocus cling. 872 LayoutInflater inflater = AndroidServices.instance().provideLayoutInflater(); 873 Cling refocusCling = (Cling) inflater.inflate(R.layout.cling_widget, null, false); 874 // Sets instruction text in the cling. 875 refocusCling.setText(mController.getAndroidContext().getResources() 876 .getString(R.string.cling_text_for_refocus_editor_button)); 877 878 // Adds cling into view hierarchy. 879 int clingWidth = mController.getAndroidContext() 880 .getResources().getDimensionPixelSize(R.dimen.default_cling_width); 881 filmstripContent.addView(refocusCling, clingWidth, 882 ViewGroup.LayoutParams.WRAP_CONTENT); 883 mFilmstripBottomControls.setClingForViewer(viewerType, refocusCling); 884 } 885 } 886 } 887 888 /** 889 * Clears the listeners for the cling and remove it from the view hierarchy. 890 * 891 * @param viewerType defines which viewer the cling is for. 892 */ clearClingForViewer(int viewerType)893 public void clearClingForViewer(int viewerType) { 894 Cling clingToBeRemoved = mFilmstripBottomControls.getClingForViewer(viewerType); 895 if (clingToBeRemoved == null) { 896 // No cling is created for the specific viewer type. 897 return; 898 } 899 mFilmstripBottomControls.clearClingForViewer(viewerType); 900 clingToBeRemoved.setVisibility(View.GONE); 901 mAppRootView.removeView(clingToBeRemoved); 902 } 903 904 /** 905 * Enable or disable swipe gestures. We want to disable them e.g. while we 906 * record a video. 907 */ setSwipeEnabled(boolean enabled)908 public void setSwipeEnabled(boolean enabled) { 909 mSwipeEnabled = enabled; 910 // TODO: This can be removed once we come up with a new design for handling swipe 911 // on shutter button and mode options. (More details: b/13751653) 912 mAppRootView.setSwipeEnabled(enabled); 913 } 914 onDestroy()915 public void onDestroy() { 916 AndroidServices.instance().provideDisplayManager() 917 .unregisterDisplayListener(mDisplayListener); 918 } 919 920 /** 921 * Initializes the display listener to listen to display changes such as 922 * 180-degree rotation change, which will not have an onConfigurationChanged 923 * callback. 924 */ initDisplayListener()925 private void initDisplayListener() { 926 if (ApiHelper.HAS_DISPLAY_LISTENER) { 927 mLastRotation = CameraUtil.getDisplayRotation(); 928 929 mDisplayListener = new DisplayManager.DisplayListener() { 930 @Override 931 public void onDisplayAdded(int arg0) { 932 // Do nothing. 933 } 934 935 @Override 936 public void onDisplayChanged(int displayId) { 937 int rotation = CameraUtil.getDisplayRotation( 938 ); 939 if ((rotation - mLastRotation + 360) % 360 == 180 940 && mPreviewStatusListener != null) { 941 mPreviewStatusListener.onPreviewFlipped(); 942 mStickyBottomCaptureLayout.requestLayout(); 943 mModeListView.requestLayout(); 944 mTextureView.requestLayout(); 945 } 946 mLastRotation = rotation; 947 } 948 949 @Override 950 public void onDisplayRemoved(int arg0) { 951 // Do nothing. 952 } 953 }; 954 955 AndroidServices.instance().provideDisplayManager() 956 .registerDisplayListener(mDisplayListener, null); 957 } 958 } 959 960 /** 961 * Redirects touch events to appropriate recipient views based on swipe direction. 962 * More specifically, swipe up and swipe down will be handled by the view that handles 963 * mode transition; swipe left will be send to filmstrip; swipe right will be redirected 964 * to mode list in order to bring up mode list. 965 */ onSwipeDetected(int swipeState)966 private void onSwipeDetected(int swipeState) { 967 if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) { 968 // TODO: Polish quick switch after this release. 969 // Quick switch between modes. 970 int currentModuleIndex = mController.getCurrentModuleIndex(); 971 final int moduleToTransitionTo = 972 mController.getQuickSwitchToModuleId(currentModuleIndex); 973 if (currentModuleIndex != moduleToTransitionTo) { 974 mAppRootView.redirectTouchEventsTo(mModeTransitionView); 975 int shadeColorId = R.color.camera_gray_background; 976 int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo, 977 mController.getAndroidContext()); 978 979 AnimationFinishedListener listener = new AnimationFinishedListener() { 980 @Override 981 public void onAnimationFinished(boolean success) { 982 if (success) { 983 mHideCoverRunnable = new Runnable() { 984 @Override 985 public void run() { 986 mModeTransitionView.startPeepHoleAnimation(); 987 } 988 }; 989 mModeCoverState = COVER_SHOWN; 990 // Go to new module when the previous operation is successful. 991 mController.onModeSelected(moduleToTransitionTo); 992 } 993 } 994 }; 995 } 996 } else if (swipeState == SWIPE_LEFT) { 997 // Pass the touch sequence to filmstrip layout. 998 mAppRootView.redirectTouchEventsTo(mFilmstripLayout); 999 } else if (swipeState == SWIPE_RIGHT) { 1000 // Pass the touch to mode switcher 1001 mAppRootView.redirectTouchEventsTo(mModeListView); 1002 } 1003 } 1004 1005 /** 1006 * Gets called when activity resumes in preview. 1007 */ resume()1008 public void resume() { 1009 // Show mode theme cover until preview is ready 1010 showModeCoverUntilPreviewReady(); 1011 1012 // Hide action bar first since we are in full screen mode first, and 1013 // switch the system UI to lights-out mode. 1014 mFilmstripPanel.hide(); 1015 1016 // Show UI that is meant to only be used when spoken feedback is 1017 // enabled. 1018 mAccessibilityAffordances.setVisibility( 1019 (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) ? View.VISIBLE 1020 : View.GONE); 1021 } 1022 1023 /** 1024 * Opens the mode list (e.g. because of the menu button being pressed) and 1025 * adapts the rest of the UI. 1026 */ openModeList()1027 public void openModeList() { 1028 mModeOptionsOverlay.closeModeOptions(); 1029 mModeListView.onMenuPressed(); 1030 } 1031 showAccessibilityZoomUI(float maxZoom)1032 public void showAccessibilityZoomUI(float maxZoom) { 1033 mAccessibilityUtil.showZoomUI(maxZoom); 1034 } 1035 hideAccessibilityZoomUI()1036 public void hideAccessibilityZoomUI() { 1037 mAccessibilityUtil.hideZoomUI(); 1038 } 1039 1040 /** 1041 * A cover view showing the mode theme color and mode icon will be visible on 1042 * top of preview until preview is ready (i.e. camera preview is started and 1043 * the first frame has been received). 1044 */ showModeCoverUntilPreviewReady()1045 private void showModeCoverUntilPreviewReady() { 1046 int modeId = mController.getCurrentModuleIndex(); 1047 int colorId = R.color.camera_gray_background;; 1048 int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext()); 1049 mModeTransitionView.setupModeCover(colorId, iconId); 1050 mHideCoverRunnable = new Runnable() { 1051 @Override 1052 public void run() { 1053 mModeTransitionView.hideModeCover(null); 1054 if (!mDisableAllUserInteractions) { 1055 showShimmyDelayed(); 1056 } 1057 } 1058 }; 1059 mModeCoverState = COVER_SHOWN; 1060 } 1061 showShimmyDelayed()1062 private void showShimmyDelayed() { 1063 if (!mIsCaptureIntent) { 1064 // Show shimmy in SHIMMY_DELAY_MS 1065 mModeListView.showModeSwitcherHint(); 1066 } 1067 } 1068 hideModeCover()1069 private void hideModeCover() { 1070 if (mHideCoverRunnable != null) { 1071 mAppRootView.post(mHideCoverRunnable); 1072 mHideCoverRunnable = null; 1073 } 1074 mModeCoverState = COVER_HIDDEN; 1075 if (mCoverHiddenTime < 0) { 1076 mCoverHiddenTime = System.currentTimeMillis(); 1077 } 1078 } 1079 1080 onPreviewVisiblityChanged(int visibility)1081 public void onPreviewVisiblityChanged(int visibility) { 1082 if (visibility == ModuleController.VISIBILITY_HIDDEN) { 1083 setIndicatorBottomBarWrapperVisible(false); 1084 mAccessibilityAffordances.setVisibility(View.GONE); 1085 } else { 1086 setIndicatorBottomBarWrapperVisible(true); 1087 if (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) { 1088 mAccessibilityAffordances.setVisibility(View.VISIBLE); 1089 } else { 1090 mAccessibilityAffordances.setVisibility(View.GONE); 1091 } 1092 } 1093 } 1094 1095 /** 1096 * Call to stop the preview from being rendered. Sets the entire capture 1097 * root view to invisible which includes the preview plus focus indicator 1098 * and any other auxiliary views for capture modes. 1099 */ pausePreviewRendering()1100 public void pausePreviewRendering() { 1101 mCameraRootView.setVisibility(View.INVISIBLE); 1102 } 1103 1104 /** 1105 * Call to begin rendering the preview and auxiliary views again. 1106 */ resumePreviewRendering()1107 public void resumePreviewRendering() { 1108 mCameraRootView.setVisibility(View.VISIBLE); 1109 } 1110 1111 /** 1112 * Returns the transform associated with the preview view. 1113 * 1114 * @param m the Matrix in which to copy the current transform. 1115 * @return The specified matrix if not null or a new Matrix instance 1116 * otherwise. 1117 */ getPreviewTransform(Matrix m)1118 public Matrix getPreviewTransform(Matrix m) { 1119 return mTextureView.getTransform(m); 1120 } 1121 1122 @Override onOpenFullScreen()1123 public void onOpenFullScreen() { 1124 // Do nothing. 1125 } 1126 1127 @Override onModeListOpenProgress(float progress)1128 public void onModeListOpenProgress(float progress) { 1129 // When the mode list is in transition, ensure the large layers are 1130 // hardware accelerated. 1131 if (progress >= 1.0f || progress <= 0.0f) { 1132 // Convert hardware layers back to default layer types when animation stops 1133 // to prevent accidental artifacting. 1134 if(mModeOptionsToggle.getLayerType() == View.LAYER_TYPE_HARDWARE || 1135 mShutterButton.getLayerType() == View.LAYER_TYPE_HARDWARE) { 1136 Log.v(TAG, "Disabling hardware layer for the Mode Options Toggle Button."); 1137 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_NONE, null); 1138 Log.v(TAG, "Disabling hardware layer for the Shutter Button."); 1139 mShutterButton.setLayerType(View.LAYER_TYPE_NONE, null); 1140 } 1141 } else { 1142 if(mModeOptionsToggle.getLayerType() != View.LAYER_TYPE_HARDWARE || 1143 mShutterButton.getLayerType() != View.LAYER_TYPE_HARDWARE) { 1144 Log.v(TAG, "Enabling hardware layer for the Mode Options Toggle Button."); 1145 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1146 Log.v(TAG, "Enabling hardware layer for the Shutter Button."); 1147 mShutterButton.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1148 } 1149 } 1150 1151 progress = 1 - progress; 1152 float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress); 1153 mModeOptionsToggle.setAlpha(interpolatedProgress); 1154 // Change shutter button alpha linearly based on the mode list open progress: 1155 // set the alpha to disabled alpha when list is fully open, to enabled alpha 1156 // when the list is fully closed. 1157 mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED 1158 + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED); 1159 } 1160 1161 @Override onModeListClosed()1162 public void onModeListClosed() { 1163 // Convert hardware layers back to default layer types when animation stops 1164 // to prevent accidental artifacting. 1165 if(mModeOptionsToggle.getLayerType() == View.LAYER_TYPE_HARDWARE || 1166 mShutterButton.getLayerType() == View.LAYER_TYPE_HARDWARE) { 1167 Log.v(TAG, "Disabling hardware layer for the Mode Options Toggle Button."); 1168 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_NONE, null); 1169 Log.v(TAG, "Disabling hardware layer for the Shutter Button."); 1170 mShutterButton.setLayerType(View.LAYER_TYPE_NONE, null); 1171 } 1172 1173 // Make sure the alpha on mode options ellipse is reset when mode drawer 1174 // is closed. 1175 mModeOptionsToggle.setAlpha(1f); 1176 mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED); 1177 } 1178 1179 /** 1180 * Called when the back key is pressed. 1181 * 1182 * @return Whether the UI responded to the key event. 1183 */ onBackPressed()1184 public boolean onBackPressed() { 1185 if (mFilmstripLayout.getVisibility() == View.VISIBLE) { 1186 return mFilmstripLayout.onBackPressed(); 1187 } else { 1188 return mModeListView.onBackPressed(); 1189 } 1190 } 1191 1192 /** 1193 * Sets a {@link com.android.camera.ui.PreviewStatusListener} that 1194 * listens to SurfaceTexture changes. In addition, listeners are set on 1195 * dependent app ui elements. 1196 * 1197 * @param previewStatusListener the listener that gets notified when SurfaceTexture 1198 * changes 1199 */ setPreviewStatusListener(PreviewStatusListener previewStatusListener)1200 public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) { 1201 mPreviewStatusListener = previewStatusListener; 1202 if (mPreviewStatusListener != null) { 1203 onPreviewListenerChanged(); 1204 } 1205 } 1206 1207 /** 1208 * When the PreviewStatusListener changes, listeners need to be 1209 * set on the following app ui elements: 1210 * {@link com.android.camera.ui.PreviewOverlay}, 1211 * {@link com.android.camera.ui.BottomBar}, 1212 * {@link com.android.camera.ui.IndicatorIconController}. 1213 */ onPreviewListenerChanged()1214 private void onPreviewListenerChanged() { 1215 // Set a listener for recognizing preview gestures. 1216 GestureDetector.OnGestureListener gestureListener 1217 = mPreviewStatusListener.getGestureListener(); 1218 if (gestureListener != null) { 1219 mPreviewOverlay.setGestureListener(gestureListener); 1220 } 1221 View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener(); 1222 if (touchListener != null) { 1223 mPreviewOverlay.setTouchListener(touchListener); 1224 } 1225 1226 mTextureViewHelper.setAutoAdjustTransform( 1227 mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()); 1228 } 1229 1230 /** 1231 * This method should be called in onCameraOpened. It defines CameraAppUI 1232 * specific changes that depend on the camera or camera settings. 1233 */ onChangeCamera()1234 public void onChangeCamera() { 1235 ModuleController moduleController = mController.getCurrentModuleController(); 1236 HardwareSpec hardwareSpec = moduleController.getHardwareSpec(); 1237 1238 /** 1239 * The current UI requires that the flash option visibility in front- 1240 * facing camera be 1241 * * disabled if back facing camera supports flash 1242 * * hidden if back facing camera does not support flash 1243 * We save whether back facing camera supports flash because we cannot 1244 * get this in front facing camera without a camera switch. 1245 * 1246 * If this preference is cleared, we also need to clear the camera 1247 * facing setting so we default to opening the camera in back facing 1248 * camera, and can save this flash support value again. 1249 */ 1250 if (hardwareSpec != null) { 1251 if (!mController.getSettingsManager().isSet(SettingsManager.SCOPE_GLOBAL, 1252 Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA)) { 1253 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 1254 Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA, 1255 hardwareSpec.isFlashSupported()); 1256 } 1257 /** Similar logic applies to the HDR option. */ 1258 if (!mController.getSettingsManager().isSet(SettingsManager.SCOPE_GLOBAL, 1259 Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA)) { 1260 String hdrSupportMode; 1261 if (hardwareSpec.isHdrPlusSupported()) { 1262 hdrSupportMode = getResourceString( 1263 R.string.pref_camera_hdr_supportmode_hdr_plus); 1264 } else if (hardwareSpec.isHdrSupported()) { 1265 hdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_hdr); 1266 } else { 1267 hdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_none); 1268 } 1269 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 1270 Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA, hdrSupportMode); 1271 } 1272 } 1273 1274 applyModuleSpecs(hardwareSpec, moduleController.getBottomBarSpec(), 1275 true /*skipScopeCheck*/); 1276 syncModeOptionIndicators(); 1277 } 1278 1279 /** 1280 * Updates the mode option indicators according to the current settings. 1281 */ syncModeOptionIndicators()1282 public void syncModeOptionIndicators() { 1283 if (mIndicatorIconController != null) { 1284 // Sync the settings state with the indicator state. 1285 mIndicatorIconController.syncIndicators(); 1286 } 1287 } 1288 1289 /** 1290 * Adds a listener to receive callbacks when preview area changes. 1291 */ addPreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1292 public void addPreviewAreaChangedListener( 1293 PreviewStatusListener.PreviewAreaChangedListener listener) { 1294 mTextureViewHelper.addPreviewAreaSizeChangedListener(listener); 1295 } 1296 1297 /** 1298 * Removes a listener that receives callbacks when preview area changes. 1299 */ removePreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1300 public void removePreviewAreaChangedListener( 1301 PreviewStatusListener.PreviewAreaChangedListener listener) { 1302 mTextureViewHelper.removePreviewAreaSizeChangedListener(listener); 1303 } 1304 1305 /** 1306 * This inflates generic_module layout, which contains all the shared views across 1307 * modules. Then each module inflates their own views in the given view group. For 1308 * now, this is called every time switching from a not-yet-refactored module to a 1309 * refactored module. In the future, this should only need to be done once per app 1310 * start. 1311 */ prepareModuleUI()1312 public void prepareModuleUI() { 1313 mController.getSettingsManager().addListener(this); 1314 mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout); 1315 mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content); 1316 mTextureViewHelper = new TextureViewHelper(mTextureView, mCaptureLayoutHelper, 1317 mController.getCameraProvider(), mController); 1318 mTextureViewHelper.setSurfaceTextureListener(this); 1319 mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener); 1320 1321 mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar); 1322 int unpressedColor = mController.getAndroidContext().getResources() 1323 .getColor(R.color.camera_gray_background); 1324 setBottomBarColor(unpressedColor); 1325 updateModeSpecificUIColors(); 1326 1327 mBottomBar.setCaptureLayoutHelper(mCaptureLayoutHelper); 1328 1329 mModeOptionsOverlay 1330 = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay); 1331 1332 // Sets the visibility of the bottom bar and the mode options. 1333 resetBottomControls(mController.getCurrentModuleController(), 1334 mController.getCurrentModuleIndex()); 1335 mModeOptionsOverlay.setCaptureLayoutHelper(mCaptureLayoutHelper); 1336 1337 mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button); 1338 addShutterListener(mController.getCurrentModuleController()); 1339 addShutterListener(mModeOptionsOverlay); 1340 addShutterListener(this); 1341 1342 mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines); 1343 mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines); 1344 1345 mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay); 1346 mPreviewOverlay.setOnTouchListener(new MyTouchListener()); 1347 mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay); 1348 mAccessibilityUtil = new AccessibilityUtil(mPreviewOverlay, mAccessibilityAffordances); 1349 1350 mCaptureOverlay = (CaptureAnimationOverlay) 1351 mCameraRootView.findViewById(R.id.capture_overlay); 1352 mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay); 1353 mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay); 1354 1355 if (mIndicatorIconController == null) { 1356 mIndicatorIconController = 1357 new IndicatorIconController(mController, mAppRootView); 1358 } 1359 1360 mController.getButtonManager().load(mCameraRootView); 1361 mController.getButtonManager().setListener(mIndicatorIconController); 1362 mController.getSettingsManager().addListener(mIndicatorIconController); 1363 1364 mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle); 1365 mFocusRing = (FocusRing) mCameraRootView.findViewById(R.id.focus_ring); 1366 mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView 1367 .findViewById(R.id.tutorials_placeholder_wrapper); 1368 mStickyBottomCaptureLayout = (StickyBottomCaptureLayout) mAppRootView 1369 .findViewById(R.id.sticky_bottom_capture_layout); 1370 mStickyBottomCaptureLayout.setCaptureLayoutHelper(mCaptureLayoutHelper); 1371 mCountdownCancelButton = (ImageButton) mStickyBottomCaptureLayout 1372 .findViewById(R.id.shutter_cancel_button); 1373 1374 mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView); 1375 mTextureViewHelper.addAspectRatioChangedListener( 1376 new PreviewStatusListener.PreviewAspectRatioChangedListener() { 1377 @Override 1378 public void onPreviewAspectRatioChanged(float aspectRatio) { 1379 mModeOptionsOverlay.requestLayout(); 1380 mBottomBar.requestLayout(); 1381 } 1382 } 1383 ); 1384 } 1385 1386 /** 1387 * Called indirectly from each module in their initialization to get a view group 1388 * to inflate the module specific views in. 1389 * 1390 * @return a view group for modules to attach views to 1391 */ getModuleRootView()1392 public FrameLayout getModuleRootView() { 1393 // TODO: Change it to mModuleUI when refactor is done 1394 return mCameraRootView; 1395 } 1396 1397 /** 1398 * Remove all the module specific views. 1399 */ clearModuleUI()1400 public void clearModuleUI() { 1401 if (mModuleUI != null) { 1402 mModuleUI.removeAllViews(); 1403 } 1404 removeShutterListener(mController.getCurrentModuleController()); 1405 mTutorialsPlaceHolderWrapper.removeAllViews(); 1406 mTutorialsPlaceHolderWrapper.setVisibility(View.GONE); 1407 1408 setShutterButtonEnabled(true); 1409 mPreviewStatusListener = null; 1410 mPreviewOverlay.reset(); 1411 1412 Log.v(TAG, "mFocusRing.stopFocusAnimations()"); 1413 mFocusRing.stopFocusAnimations(); 1414 } 1415 1416 /** 1417 * Gets called when preview is ready to start. It sets up one shot preview callback 1418 * in order to receive a callback when the preview frame is available, so that 1419 * the preview cover can be hidden to reveal preview. 1420 * 1421 * An alternative for getting the timing to hide preview cover is through 1422 * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)}, 1423 * which is less accurate but therefore is the fallback for modules that manage 1424 * their own preview callbacks (as setting one preview callback will override 1425 * any other installed preview callbacks), or use camera2 API. 1426 */ onPreviewReadyToStart()1427 public void onPreviewReadyToStart() { 1428 if (mModeCoverState == COVER_SHOWN) { 1429 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME; 1430 mController.setupOneShotPreviewListener(); 1431 } 1432 } 1433 1434 /** 1435 * Gets called when preview is started. 1436 */ onPreviewStarted()1437 public void onPreviewStarted() { 1438 Log.v(TAG, "onPreviewStarted"); 1439 if (mModeCoverState == COVER_SHOWN) { 1440 // This is a work around of the face detection failure in b/20724126. 1441 // In particular, we need to drop the first preview frame in order to 1442 // make face detection work and also need to hide this preview frame to 1443 // avoid potential janks. We do this only for L, Nexus 6 and Haleakala. 1444 if (ApiHelper.isLorLMr1() && ApiHelper.IS_NEXUS_6) { 1445 mModeCoverState = COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE; 1446 } else { 1447 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE; 1448 } 1449 } 1450 enableModeOptions(); 1451 } 1452 1453 /** 1454 * Gets notified when next preview frame comes in. 1455 */ onNewPreviewFrame()1456 public void onNewPreviewFrame() { 1457 Log.v(TAG, "onNewPreviewFrame"); 1458 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME); 1459 hideModeCover(); 1460 } 1461 1462 @Override onShutterButtonClick()1463 public void onShutterButtonClick() { 1464 /* 1465 * Set the mode options toggle unclickable, generally 1466 * throughout the app, whenever the shutter button is clicked. 1467 * 1468 * This could be done in the OnShutterButtonListener of the 1469 * ModeOptionsOverlay, but since it is very important that we 1470 * can clearly see when the toggle becomes clickable again, 1471 * keep all of that logic at this level. 1472 */ 1473 // disableModeOptions(); 1474 } 1475 1476 @Override onShutterCoordinate(TouchCoordinate coord)1477 public void onShutterCoordinate(TouchCoordinate coord) { 1478 // Do nothing. 1479 } 1480 1481 @Override onShutterButtonFocus(boolean pressed)1482 public void onShutterButtonFocus(boolean pressed) { 1483 // noop 1484 } 1485 1486 @Override onShutterButtonLongPressed()1487 public void onShutterButtonLongPressed() { 1488 // noop 1489 } 1490 1491 /** 1492 * Set the mode options toggle clickable. 1493 */ enableModeOptions()1494 public void enableModeOptions() { 1495 /* 1496 * For modules using camera 1 api, this gets called in 1497 * onSurfaceTextureUpdated whenever the preview gets stopped and 1498 * started after each capture. This also takes care of the 1499 * case where the mode options might be unclickable when we 1500 * switch modes 1501 * 1502 * For modules using camera 2 api, they're required to call this 1503 * method when a capture is "completed". Unfortunately this differs 1504 * per module implementation. 1505 */ 1506 if (!mDisableAllUserInteractions) { 1507 mModeOptionsOverlay.setToggleClickable(true); 1508 } 1509 } 1510 1511 /** 1512 * Set the mode options toggle not clickable. 1513 */ disableModeOptions()1514 public void disableModeOptions() { 1515 mModeOptionsOverlay.setToggleClickable(false); 1516 } 1517 setDisableAllUserInteractions(boolean disable)1518 public void setDisableAllUserInteractions(boolean disable) { 1519 if (disable) { 1520 disableModeOptions(); 1521 setShutterButtonEnabled(false); 1522 setSwipeEnabled(false); 1523 mModeListView.hideAnimated(); 1524 } else { 1525 enableModeOptions(); 1526 setShutterButtonEnabled(true); 1527 setSwipeEnabled(true); 1528 } 1529 mDisableAllUserInteractions = disable; 1530 } 1531 1532 @Override onModeButtonPressed(int modeIndex)1533 public void onModeButtonPressed(int modeIndex) { 1534 // TODO: Make CameraActivity listen to ModeListView's events. 1535 int pressedModuleId = mController.getModuleId(modeIndex); 1536 int currentModuleId = mController.getCurrentModuleIndex(); 1537 if (pressedModuleId != currentModuleId) { 1538 hideCaptureIndicator(); 1539 } 1540 } 1541 1542 /** 1543 * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView} 1544 * 1545 * @param modeIndex mode index of the selected mode 1546 */ 1547 @Override onModeSelected(int modeIndex)1548 public void onModeSelected(int modeIndex) { 1549 mHideCoverRunnable = new Runnable() { 1550 @Override 1551 public void run() { 1552 mModeListView.startModeSelectionAnimation(); 1553 } 1554 }; 1555 mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED); 1556 mModeCoverState = COVER_SHOWN; 1557 1558 int lastIndex = mController.getCurrentModuleIndex(); 1559 // Actual mode teardown / new mode initialization happens here 1560 mController.onModeSelected(modeIndex); 1561 int currentIndex = mController.getCurrentModuleIndex(); 1562 1563 if (lastIndex == currentIndex) { 1564 hideModeCover(); 1565 } 1566 1567 updateModeSpecificUIColors(); 1568 } 1569 updateModeSpecificUIColors()1570 private void updateModeSpecificUIColors() { 1571 setBottomBarColorsForModeIndex(mController.getCurrentModuleIndex()); 1572 } 1573 1574 @Override onSettingsSelected()1575 public void onSettingsSelected() { 1576 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 1577 Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, false); 1578 mModeListView.setShouldShowSettingsCling(false); 1579 mController.onSettingsSelected(); 1580 } 1581 1582 @Override getCurrentModeIndex()1583 public int getCurrentModeIndex() { 1584 return mController.getCurrentModuleIndex(); 1585 } 1586 1587 /********************** Capture animation **********************/ 1588 /* TODO: This session is subject to UX changes. In addition to the generic 1589 flash animation and post capture animation, consider designating a parameter 1590 for specifying the type of animation, as well as an animation finished listener 1591 so that modules can have more knowledge of the status of the animation. */ 1592 1593 /** 1594 * Turns on or off the capture indicator suppression. 1595 */ setShouldSuppressCaptureIndicator(boolean suppress)1596 public void setShouldSuppressCaptureIndicator(boolean suppress) { 1597 mSuppressCaptureIndicator = suppress; 1598 } 1599 1600 /** 1601 * Starts the capture indicator pop-out animation. 1602 * 1603 * @param accessibilityString An accessibility String to be announced during the peek animation. 1604 */ startCaptureIndicatorRevealAnimation(String accessibilityString)1605 public void startCaptureIndicatorRevealAnimation(String accessibilityString) { 1606 if (mSuppressCaptureIndicator || mFilmstripLayout.getVisibility() == View.VISIBLE) { 1607 return; 1608 } 1609 mRoundedThumbnailView.startRevealThumbnailAnimation(accessibilityString); 1610 } 1611 1612 /** 1613 * Updates the thumbnail image in the capture indicator. 1614 * 1615 * @param thumbnailBitmap The thumbnail image to be shown. 1616 */ updateCaptureIndicatorThumbnail(Bitmap thumbnailBitmap, int rotation)1617 public void updateCaptureIndicatorThumbnail(Bitmap thumbnailBitmap, int rotation) { 1618 if (mSuppressCaptureIndicator || mFilmstripLayout.getVisibility() == View.VISIBLE) { 1619 return; 1620 } 1621 mRoundedThumbnailView.setThumbnail(thumbnailBitmap, rotation); 1622 } 1623 1624 /** 1625 * Hides the capture indicator. 1626 */ hideCaptureIndicator()1627 public void hideCaptureIndicator() { 1628 mRoundedThumbnailView.hideThumbnail(); 1629 } 1630 1631 /** 1632 * Starts the flash animation. 1633 */ startFlashAnimation(boolean shortFlash)1634 public void startFlashAnimation(boolean shortFlash) { 1635 mCaptureOverlay.startFlashAnimation(shortFlash); 1636 } 1637 1638 /** 1639 * Cancels the pre-capture animation. 1640 */ cancelPreCaptureAnimation()1641 public void cancelPreCaptureAnimation() { 1642 mAnimationManager.cancelAnimations(); 1643 } 1644 1645 /** 1646 * Cancels the post-capture animation. 1647 */ cancelPostCaptureAnimation()1648 public void cancelPostCaptureAnimation() { 1649 mAnimationManager.cancelAnimations(); 1650 } 1651 getFilmstripContentPanel()1652 public FilmstripContentPanel getFilmstripContentPanel() { 1653 return mFilmstripPanel; 1654 } 1655 1656 /** 1657 * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the 1658 * bottom of the filmstrip. 1659 */ getFilmstripBottomControls()1660 public BottomPanel getFilmstripBottomControls() { 1661 return mFilmstripBottomControls; 1662 } 1663 showBottomControls()1664 public void showBottomControls() { 1665 mFilmstripBottomControls.show(); 1666 } 1667 hideBottomControls()1668 public void hideBottomControls() { 1669 mFilmstripBottomControls.hide(); 1670 } 1671 1672 /** 1673 * @param listener The listener for bottom controls. 1674 */ setFilmstripBottomControlsListener(BottomPanel.Listener listener)1675 public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) { 1676 mFilmstripBottomControls.setListener(listener); 1677 } 1678 1679 /***************************SurfaceTexture Api and Listener*********************************/ 1680 1681 /** 1682 * Return the shared surface texture. 1683 */ getSurfaceTexture()1684 public SurfaceTexture getSurfaceTexture() { 1685 return mSurface; 1686 } 1687 1688 /** 1689 * Return the shared {@link android.graphics.SurfaceTexture}'s width. 1690 */ getSurfaceWidth()1691 public int getSurfaceWidth() { 1692 return mSurfaceWidth; 1693 } 1694 1695 /** 1696 * Return the shared {@link android.graphics.SurfaceTexture}'s height. 1697 */ getSurfaceHeight()1698 public int getSurfaceHeight() { 1699 return mSurfaceHeight; 1700 } 1701 1702 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)1703 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 1704 mSurface = surface; 1705 mSurfaceWidth = width; 1706 mSurfaceHeight = height; 1707 Log.v(TAG, "SurfaceTexture is available"); 1708 if (mPreviewStatusListener != null) { 1709 mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height); 1710 } 1711 enableModeOptions(); 1712 } 1713 1714 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)1715 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 1716 mSurface = surface; 1717 mSurfaceWidth = width; 1718 mSurfaceHeight = height; 1719 if (mPreviewStatusListener != null) { 1720 mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height); 1721 } 1722 } 1723 1724 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)1725 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 1726 mSurface = null; 1727 Log.v(TAG, "SurfaceTexture is destroyed"); 1728 if (mPreviewStatusListener != null) { 1729 return mPreviewStatusListener.onSurfaceTextureDestroyed(surface); 1730 } 1731 return false; 1732 } 1733 1734 @Override onSurfaceTextureUpdated(SurfaceTexture surface)1735 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 1736 mSurface = surface; 1737 if (mPreviewStatusListener != null) { 1738 mPreviewStatusListener.onSurfaceTextureUpdated(surface); 1739 } 1740 // Do not show the first preview frame. Due to the bug b/20724126, we need to have 1741 // a WAR to request a preview frame followed by 5-frame ZSL burst before the repeating 1742 // preview and ZSL streams. Need to hide the first preview frame since it is janky. 1743 // We do this only for L, Nexus 6 and Haleakala. 1744 if (mModeCoverState == COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE) { 1745 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE; 1746 } else if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE){ 1747 Log.v(TAG, "hiding cover via onSurfaceTextureUpdated"); 1748 CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME); 1749 hideModeCover(); 1750 } 1751 } 1752 1753 /****************************Grid lines api ******************************/ 1754 1755 /** 1756 * Show a set of evenly spaced lines over the preview. The number 1757 * of lines horizontally and vertically is determined by 1758 * {@link com.android.camera.ui.GridLines}. 1759 */ showGridLines()1760 public void showGridLines() { 1761 if (mGridLines != null) { 1762 mGridLines.setVisibility(View.VISIBLE); 1763 } 1764 } 1765 1766 /** 1767 * Hide the set of evenly spaced grid lines overlaying the preview. 1768 */ hideGridLines()1769 public void hideGridLines() { 1770 if (mGridLines != null) { 1771 mGridLines.setVisibility(View.INVISIBLE); 1772 } 1773 } 1774 1775 /** 1776 * Return a callback which shows or hide the preview grid lines 1777 * depending on whether the grid lines setting is set on. 1778 */ getGridLinesCallback()1779 public ButtonManager.ButtonCallback getGridLinesCallback() { 1780 return new ButtonManager.ButtonCallback() { 1781 @Override 1782 public void onStateChanged(int state) { 1783 if (!mController.isPaused()) { 1784 if (Keys.areGridLinesOn(mController.getSettingsManager())) { 1785 showGridLines(); 1786 } else { 1787 hideGridLines(); 1788 } 1789 } 1790 } 1791 }; 1792 } 1793 1794 /***************************Mode options api *****************************/ 1795 1796 /** 1797 * Set the mode options visible. 1798 */ 1799 public void showModeOptions() { 1800 /* Make mode options clickable. */ 1801 enableModeOptions(); 1802 mModeOptionsOverlay.setVisibility(View.VISIBLE); 1803 } 1804 1805 /** 1806 * Set the mode options invisible. This is necessary for modes 1807 * that don't show a bottom bar for the capture UI. 1808 */ 1809 public void hideModeOptions() { 1810 mModeOptionsOverlay.setVisibility(View.INVISIBLE); 1811 } 1812 1813 /****************************Bottom bar api ******************************/ 1814 1815 /** 1816 * Sets up the bottom bar and mode options with the correct 1817 * shutter button and visibility based on the current module. 1818 */ 1819 public void resetBottomControls(ModuleController module, int moduleIndex) { 1820 if (areBottomControlsUsed(module)) { 1821 setBottomBarShutterIcon(moduleIndex); 1822 mCaptureLayoutHelper.setShowBottomBar(true); 1823 } else { 1824 mCaptureLayoutHelper.setShowBottomBar(false); 1825 } 1826 } 1827 1828 /** 1829 * Show or hide the mode options and bottom bar, based on 1830 * whether the current module is using the bottom bar. Returns 1831 * whether the mode options and bottom bar are used. 1832 */ 1833 private boolean areBottomControlsUsed(ModuleController module) { 1834 if (module.isUsingBottomBar()) { 1835 showBottomBar(); 1836 showModeOptions(); 1837 return true; 1838 } else { 1839 hideBottomBar(); 1840 hideModeOptions(); 1841 return false; 1842 } 1843 } 1844 1845 /** 1846 * Set the bottom bar visible. 1847 */ 1848 public void showBottomBar() { 1849 mBottomBar.setVisibility(View.VISIBLE); 1850 } 1851 1852 /** 1853 * Set the bottom bar invisible. 1854 */ 1855 public void hideBottomBar() { 1856 mBottomBar.setVisibility(View.INVISIBLE); 1857 } 1858 1859 /** 1860 * Sets the color of the bottom bar. 1861 */ 1862 public void setBottomBarColor(int colorId) { 1863 mBottomBar.setBackgroundColor(colorId); 1864 } 1865 1866 /** 1867 * Sets the pressed color of the bottom bar for a camera mode index. 1868 */ 1869 public void setBottomBarColorsForModeIndex(int index) { 1870 mBottomBar.setColorsForModeIndex(index); 1871 } 1872 1873 /** 1874 * Sets the shutter button icon on the bottom bar, based on 1875 * the mode index. 1876 */ 1877 public void setBottomBarShutterIcon(int modeIndex) { 1878 int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex, 1879 mController.getAndroidContext()); 1880 mBottomBar.setShutterButtonIcon(shutterIconId); 1881 } 1882 1883 public void animateBottomBarToVideoStop(int shutterIconId) { 1884 mBottomBar.animateToVideoStop(shutterIconId); 1885 } 1886 1887 public void animateBottomBarToFullSize(int shutterIconId) { 1888 mBottomBar.animateToFullSize(shutterIconId); 1889 } 1890 1891 public void setShutterButtonEnabled(final boolean enabled) { 1892 if (!mDisableAllUserInteractions) { 1893 mBottomBar.post(new Runnable() { 1894 @Override 1895 public void run() { 1896 mBottomBar.setShutterButtonEnabled(enabled); 1897 } 1898 }); 1899 } 1900 } 1901 1902 public void setShutterButtonImportantToA11y(boolean important) { 1903 mBottomBar.setShutterButtonImportantToA11y(important); 1904 } 1905 1906 public boolean isShutterButtonEnabled() { 1907 return mBottomBar.isShutterButtonEnabled(); 1908 } 1909 1910 public void setIndicatorBottomBarWrapperVisible(boolean visible) { 1911 mStickyBottomCaptureLayout.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1912 } 1913 1914 /** 1915 * Set the visibility of the bottom bar. 1916 */ 1917 // TODO: needed for when panorama is managed by the generic module ui. 1918 public void setBottomBarVisible(boolean visible) { 1919 mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1920 } 1921 1922 /** 1923 * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button. 1924 */ 1925 public void addShutterListener(ShutterButton.OnShutterButtonListener listener) { 1926 mShutterButton.addOnShutterButtonListener(listener); 1927 } 1928 1929 /** 1930 * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button. 1931 */ 1932 public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) { 1933 mShutterButton.removeOnShutterButtonListener(listener); 1934 } 1935 1936 /** 1937 * Sets or replaces the "cancel shutter" button listener. 1938 * <p> 1939 * TODO: Make this part of the interface the same way shutter button 1940 * listeners are. 1941 */ 1942 public void setCancelShutterButtonListener(View.OnClickListener listener) { 1943 mCountdownCancelButton.setOnClickListener(listener); 1944 } 1945 1946 /** 1947 * Performs a transition to the capture layout of the bottom bar. 1948 */ 1949 public void transitionToCapture() { 1950 ModuleController moduleController = mController.getCurrentModuleController(); 1951 applyModuleSpecs(moduleController.getHardwareSpec(), 1952 moduleController.getBottomBarSpec()); 1953 mBottomBar.transitionToCapture(); 1954 } 1955 1956 /** 1957 * Displays the Cancel button instead of the capture button. 1958 */ 1959 public void transitionToCancel() { 1960 ModuleController moduleController = mController.getCurrentModuleController(); 1961 applyModuleSpecs(moduleController.getHardwareSpec(), 1962 moduleController.getBottomBarSpec()); 1963 mBottomBar.transitionToCancel(); 1964 } 1965 1966 /** 1967 * Performs a transition to the global intent layout. 1968 */ 1969 public void transitionToIntentCaptureLayout() { 1970 ModuleController moduleController = mController.getCurrentModuleController(); 1971 applyModuleSpecs(moduleController.getHardwareSpec(), 1972 moduleController.getBottomBarSpec()); 1973 mBottomBar.transitionToIntentCaptureLayout(); 1974 showModeOptions(); 1975 } 1976 1977 /** 1978 * Performs a transition to the global intent review layout. 1979 */ 1980 public void transitionToIntentReviewLayout() { 1981 ModuleController moduleController = mController.getCurrentModuleController(); 1982 applyModuleSpecs(moduleController.getHardwareSpec(), 1983 moduleController.getBottomBarSpec()); 1984 mBottomBar.transitionToIntentReviewLayout(); 1985 hideModeOptions(); 1986 1987 // Hide the preview snapshot since the screen is frozen when users tap 1988 // shutter button in capture intent. 1989 hideModeCover(); 1990 } 1991 1992 /** 1993 * @return whether UI is in intent review mode 1994 */ 1995 public boolean isInIntentReview() { 1996 return mBottomBar.isInIntentReview(); 1997 } 1998 1999 @Override 2000 public void onSettingChanged(SettingsManager settingsManager, String key) { 2001 // Update the mode options based on the hardware spec, 2002 // when hdr changes to prevent flash from getting out of sync. 2003 if (key.equals(Keys.KEY_CAMERA_HDR)) { 2004 ModuleController moduleController = mController.getCurrentModuleController(); 2005 applyModuleSpecs(moduleController.getHardwareSpec(), 2006 moduleController.getBottomBarSpec(), 2007 true /*skipScopeCheck*/); 2008 } 2009 } 2010 2011 /** 2012 * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec} 2013 * to the bottom bar mode options based on limitations from a 2014 * {@link com.android.camera.hardware.HardwareSpec}. 2015 * 2016 * Options not supported by the hardware are either hidden 2017 * or disabled, depending on the option. 2018 * 2019 * Otherwise, the option is fully enabled and clickable. 2020 */ 2021 public void applyModuleSpecs(HardwareSpec hardwareSpec, 2022 BottomBarUISpec bottomBarSpec) { 2023 applyModuleSpecs(hardwareSpec, bottomBarSpec, false /*skipScopeCheck*/); 2024 } 2025 2026 private void applyModuleSpecs(final HardwareSpec hardwareSpec, 2027 final BottomBarUISpec bottomBarSpec, boolean skipScopeCheck) { 2028 if (hardwareSpec == null || bottomBarSpec == null) { 2029 return; 2030 } 2031 2032 ButtonManager buttonManager = mController.getButtonManager(); 2033 SettingsManager settingsManager = mController.getSettingsManager(); 2034 2035 buttonManager.setToInitialState(); 2036 2037 if (skipScopeCheck 2038 || !mController.getModuleScope().equals(mCurrentModuleScope) 2039 || !mController.getCameraScope().equals(mCurrentCameraScope)) { 2040 2041 // Scope dependent options, update only if the module or the 2042 // camera scope changed or scope-check skip was requested. 2043 mCurrentModuleScope = mController.getModuleScope(); 2044 mCurrentCameraScope = mController.getCameraScope(); 2045 2046 mHdrSupportMode = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, 2047 Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA); 2048 2049 /** Standard mode options */ 2050 if (mController.getCameraProvider().getNumberOfCameras() > 1 && 2051 hardwareSpec.isFrontCameraSupported()) { 2052 if (bottomBarSpec.enableCamera) { 2053 int hdrButtonId = ButtonManager.BUTTON_HDR; 2054 if (mHdrSupportMode.equals(getResourceString( 2055 R.string.pref_camera_hdr_supportmode_hdr_plus))) { 2056 hdrButtonId = ButtonManager.BUTTON_HDR_PLUS; 2057 } 2058 buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA, 2059 bottomBarSpec.cameraCallback, 2060 getDisableButtonCallback(hdrButtonId)); 2061 } else { 2062 buttonManager.disableButton(ButtonManager.BUTTON_CAMERA); 2063 } 2064 } else { 2065 // Hide camera icon if front camera not available. 2066 buttonManager.hideButton(ButtonManager.BUTTON_CAMERA); 2067 } 2068 2069 boolean flashBackCamera = settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 2070 Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA); 2071 if (bottomBarSpec.hideFlash 2072 || !flashBackCamera) { 2073 // Hide both flash and torch button in flash disable logic 2074 buttonManager.hideButton(ButtonManager.BUTTON_FLASH); 2075 buttonManager.hideButton(ButtonManager.BUTTON_TORCH); 2076 } else { 2077 if (hardwareSpec.isFlashSupported()) { 2078 if (bottomBarSpec.enableFlash) { 2079 buttonManager.initializeButton(ButtonManager.BUTTON_FLASH, 2080 bottomBarSpec.flashCallback); 2081 } else if (bottomBarSpec.enableTorchFlash) { 2082 buttonManager.initializeButton(ButtonManager.BUTTON_TORCH, 2083 bottomBarSpec.flashCallback); 2084 } else if (bottomBarSpec.enableHdrPlusFlash) { 2085 buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS_FLASH, 2086 bottomBarSpec.flashCallback); 2087 } else { 2088 // Disable both flash and torch button in flash disable 2089 // logic. Need to ensure it's visible, it may be hidden 2090 // from previous non-flash mode. 2091 buttonManager.showButton(ButtonManager.BUTTON_FLASH); 2092 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 2093 buttonManager.disableButton(ButtonManager.BUTTON_TORCH); 2094 } 2095 } else { 2096 // Flash not supported but another module does. 2097 // Disable flash button. Need to ensure it's visible, 2098 // it may be hidden from previous non-flash mode. 2099 buttonManager.showButton(ButtonManager.BUTTON_FLASH); 2100 buttonManager.disableButton(ButtonManager.BUTTON_FLASH); 2101 buttonManager.disableButton(ButtonManager.BUTTON_TORCH); 2102 } 2103 } 2104 2105 if (bottomBarSpec.hideHdr || mIsCaptureIntent) { 2106 // Force hide hdr or hdr plus icon. 2107 buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS); 2108 } else { 2109 if (hardwareSpec.isHdrPlusSupported()) { 2110 mHdrSupportMode = getResourceString( 2111 R.string.pref_camera_hdr_supportmode_hdr_plus); 2112 if (bottomBarSpec.enableHdr) { 2113 buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS, 2114 bottomBarSpec.hdrCallback, 2115 getDisableButtonCallback(ButtonManager.BUTTON_CAMERA)); 2116 } else { 2117 buttonManager.disableButton(ButtonManager.BUTTON_HDR_PLUS); 2118 } 2119 } else if (hardwareSpec.isHdrSupported()) { 2120 mHdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_hdr); 2121 if (bottomBarSpec.enableHdr) { 2122 buttonManager.initializeButton(ButtonManager.BUTTON_HDR, 2123 bottomBarSpec.hdrCallback, 2124 getDisableButtonCallback(ButtonManager.BUTTON_CAMERA)); 2125 } else { 2126 buttonManager.disableButton(ButtonManager.BUTTON_HDR); 2127 } 2128 } else { 2129 // Hide hdr plus or hdr icon if neither are supported overall. 2130 if (mHdrSupportMode.isEmpty() || mHdrSupportMode 2131 .equals(getResourceString(R.string.pref_camera_hdr_supportmode_none))) { 2132 buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS); 2133 } else { 2134 // Disable HDR button. Need to ensure it's visible, 2135 // it may be hidden from previous non HDR mode (eg. Video). 2136 int buttonId = ButtonManager.BUTTON_HDR; 2137 if (mHdrSupportMode.equals( 2138 getResourceString(R.string.pref_camera_hdr_supportmode_hdr_plus))) { 2139 buttonId = ButtonManager.BUTTON_HDR_PLUS; 2140 } 2141 buttonManager.showButton(buttonId); 2142 buttonManager.disableButton(buttonId); 2143 } 2144 } 2145 } 2146 2147 } 2148 if (bottomBarSpec.hideGridLines) { 2149 // Force hide grid lines icon. 2150 buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES); 2151 hideGridLines(); 2152 } else { 2153 if (bottomBarSpec.enableGridLines) { 2154 buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES, 2155 bottomBarSpec.gridLinesCallback != null ? 2156 bottomBarSpec.gridLinesCallback : getGridLinesCallback() 2157 ); 2158 } else { 2159 buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES); 2160 hideGridLines(); 2161 } 2162 } 2163 2164 if (bottomBarSpec.enableSelfTimer) { 2165 buttonManager.initializeButton(ButtonManager.BUTTON_COUNTDOWN, null); 2166 } else { 2167 if (bottomBarSpec.showSelfTimer) { 2168 buttonManager.disableButton(ButtonManager.BUTTON_COUNTDOWN); 2169 } else { 2170 buttonManager.hideButton(ButtonManager.BUTTON_COUNTDOWN); 2171 } 2172 } 2173 2174 if (bottomBarSpec.enablePanoOrientation 2175 && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) { 2176 buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback); 2177 } 2178 2179 2180 2181 // If manual exposure is enabled both in SettingsManager and 2182 // BottomBarSpec,then show the exposure button. 2183 // If manual exposure is disabled in the BottomBarSpec (eg. HDR+ 2184 // enabled), but the device/module has the feature, then disable the exposure 2185 // button. 2186 // Otherwise, hide the button. 2187 if (bottomBarSpec.enableExposureCompensation 2188 && !(bottomBarSpec.minExposureCompensation == 0 && bottomBarSpec.maxExposureCompensation == 0) 2189 && mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL, 2190 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) { 2191 buttonManager.initializePushButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION, 2192 new View.OnClickListener() { 2193 @Override 2194 public void onClick(View v) { 2195 mModeOptionsOverlay.showExposureOptions(); 2196 } 2197 }); 2198 buttonManager.setExposureCompensationParameters( 2199 bottomBarSpec.minExposureCompensation, 2200 bottomBarSpec.maxExposureCompensation, 2201 bottomBarSpec.exposureCompensationStep); 2202 2203 buttonManager.setExposureCompensationCallback( 2204 bottomBarSpec.exposureCompensationSetCallback); 2205 buttonManager.updateExposureButtons(); 2206 } else if (mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL, 2207 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED) 2208 && bottomBarSpec.isExposureCompensationSupported) { 2209 buttonManager.disableButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION); 2210 } else { 2211 buttonManager.hideButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION); 2212 buttonManager.setExposureCompensationCallback(null); 2213 } 2214 2215 /** Intent UI */ 2216 if (bottomBarSpec.showCancel) { 2217 buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL, 2218 bottomBarSpec.cancelCallback); 2219 } 2220 if (bottomBarSpec.showDone) { 2221 buttonManager.initializePushButton(ButtonManager.BUTTON_DONE, 2222 bottomBarSpec.doneCallback); 2223 } 2224 if (bottomBarSpec.showRetake) { 2225 buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE, 2226 bottomBarSpec.retakeCallback, 2227 R.drawable.ic_back, 2228 R.string.retake_button_description); 2229 } 2230 if (bottomBarSpec.showReview) { 2231 buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW, 2232 bottomBarSpec.reviewCallback, 2233 R.drawable.ic_play, 2234 R.string.review_button_description); 2235 } 2236 } 2237 2238 /** 2239 * Returns a {@link com.android.camera.ButtonManager.ButtonCallback} that 2240 * will disable the button identified by the parameter. 2241 * 2242 * @param conflictingButton The button id to be disabled. 2243 */ 2244 private ButtonManager.ButtonCallback getDisableButtonCallback(final int conflictingButton) { 2245 return new ButtonManager.ButtonCallback() { 2246 @Override 2247 public void onStateChanged(int state) { 2248 mController.getButtonManager().disableButton(conflictingButton); 2249 } 2250 }; 2251 } 2252 2253 private String getResourceString(int stringId) { 2254 try { 2255 return mController.getAndroidContext().getResources().getString(stringId); 2256 } catch (Resources.NotFoundException e) { 2257 // String not found, returning empty string. 2258 return ""; 2259 } 2260 } 2261 2262 /** 2263 * Shows the given tutorial on the screen. 2264 */ 2265 public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) { 2266 tutorial.show(mTutorialsPlaceHolderWrapper, inflater); 2267 } 2268 2269 /** 2270 * Whether the capture ratio selector dialog must be shown on this device. 2271 * */ 2272 public boolean shouldShowAspectRatioDialog() { 2273 final boolean isAspectRatioPreferenceSet = mController.getSettingsManager().getBoolean( 2274 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO); 2275 final boolean isAspectRatioDevice = 2276 ApiHelper.IS_NEXUS_4 || ApiHelper.IS_NEXUS_5 || ApiHelper.IS_NEXUS_6; 2277 return isAspectRatioDevice && !isAspectRatioPreferenceSet; 2278 } 2279 2280 2281 /***************************Filmstrip api *****************************/ 2282 2283 public void showFilmstrip() { 2284 mModeListView.onBackPressed(); 2285 mFilmstripLayout.showFilmstrip(); 2286 } 2287 2288 public void hideFilmstrip() { 2289 mFilmstripLayout.hideFilmstrip(); 2290 } 2291 2292 public int getFilmstripVisibility() { 2293 return mFilmstripLayout.getVisibility(); 2294 } 2295 } 2296