1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.compositor.bottombar; 6 7 import android.app.Activity; 8 import android.content.Context; 9 import android.graphics.RectF; 10 import android.os.Handler; 11 import android.view.View; 12 import android.view.ViewGroup; 13 14 import androidx.annotation.IntDef; 15 import androidx.annotation.VisibleForTesting; 16 17 import org.chromium.base.ActivityState; 18 import org.chromium.base.ApplicationStatus; 19 import org.chromium.base.ApplicationStatus.ActivityStateListener; 20 import org.chromium.chrome.browser.app.ChromeActivity; 21 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.PanelPriority; 22 import org.chromium.chrome.browser.compositor.layouts.Layout; 23 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl; 24 import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver; 25 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler; 26 import org.chromium.chrome.browser.compositor.layouts.eventfilter.GestureHandler; 27 import org.chromium.chrome.browser.compositor.layouts.eventfilter.OverlayPanelEventFilter; 28 import org.chromium.chrome.browser.compositor.layouts.eventfilter.ScrollDirection; 29 import org.chromium.chrome.browser.layouts.EventFilter; 30 import org.chromium.chrome.browser.layouts.SceneOverlay; 31 import org.chromium.chrome.browser.layouts.components.VirtualView; 32 import org.chromium.chrome.browser.layouts.scene_layer.SceneOverlayLayer; 33 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; 34 import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper; 35 import org.chromium.content_public.browser.SelectionPopupController; 36 import org.chromium.content_public.browser.WebContents; 37 import org.chromium.content_public.common.BrowserControlsState; 38 import org.chromium.ui.base.LocalizationUtils; 39 import org.chromium.ui.resources.ResourceManager; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.List; 44 45 /** 46 * Controls the Overlay Panel. 47 */ 48 public class OverlayPanel extends OverlayPanelAnimation implements ActivityStateListener, 49 EdgeSwipeHandler, GestureHandler, OverlayPanelContentFactory, SceneOverlay { 50 51 /** The delay after which the hide progress will be hidden. */ 52 private static final long HIDE_PROGRESS_BAR_DELAY_MS = 1000 / 60 * 4; 53 54 /** State of the Overlay Panel. */ 55 @IntDef({PanelState.UNDEFINED, PanelState.CLOSED, PanelState.PEEKED, PanelState.EXPANDED, 56 PanelState.MAXIMIZED}) 57 @Retention(RetentionPolicy.SOURCE) 58 public @interface PanelState { 59 // Values can't have gaps and should be numerated from 0. 60 // Values CLOSED - MAXIMIZED are sorted and show next states. 61 // TODO(pedrosimonetti): consider removing the UNDEFINED state 62 int UNDEFINED = 0; 63 int CLOSED = 1; 64 int PEEKED = 2; 65 int EXPANDED = 3; 66 int MAXIMIZED = 4; 67 int NUM_ENTRIES = 5; 68 } 69 70 /** 71 * The reason for a change in the Overlay Panel's state. 72 */ 73 @IntDef({StateChangeReason.UNKNOWN, StateChangeReason.RESET, StateChangeReason.BACK_PRESS, 74 StateChangeReason.TEXT_SELECT_TAP, StateChangeReason.TEXT_SELECT_LONG_PRESS, 75 StateChangeReason.INVALID_SELECTION, StateChangeReason.CLEARED_SELECTION, 76 StateChangeReason.BASE_PAGE_TAP, StateChangeReason.BASE_PAGE_SCROLL, 77 StateChangeReason.SEARCH_BAR_TAP, StateChangeReason.SERP_NAVIGATION, 78 StateChangeReason.TAB_PROMOTION, StateChangeReason.CLICK, StateChangeReason.SWIPE, 79 StateChangeReason.FLING, StateChangeReason.OPTIN, StateChangeReason.OPTOUT, 80 StateChangeReason.CLOSE_BUTTON, StateChangeReason.PANEL_SUPPRESS, 81 StateChangeReason.PANEL_UNSUPPRESS, StateChangeReason.TAP_SUPPRESS, 82 StateChangeReason.NAVIGATION}) 83 @Retention(RetentionPolicy.SOURCE) 84 public @interface StateChangeReason { 85 int UNKNOWN = 0; 86 int RESET = 1; 87 int BACK_PRESS = 2; 88 int TEXT_SELECT_TAP = 3; 89 int TEXT_SELECT_LONG_PRESS = 4; 90 int INVALID_SELECTION = 5; 91 int CLEARED_SELECTION = 6; 92 int BASE_PAGE_TAP = 7; 93 int BASE_PAGE_SCROLL = 8; 94 int SEARCH_BAR_TAP = 9; 95 int SERP_NAVIGATION = 10; 96 int TAB_PROMOTION = 11; 97 int CLICK = 12; 98 int SWIPE = 13; 99 int FLING = 14; 100 int OPTIN = 15; 101 int OPTOUT = 16; 102 int CLOSE_BUTTON = 17; 103 int PANEL_SUPPRESS = 18; 104 int PANEL_UNSUPPRESS = 19; 105 int TAP_SUPPRESS = 20; 106 int NAVIGATION = 21; 107 // Always update MAX_VALUE to match the last StateChangeReason in the list. 108 int MAX_VALUE = 21; 109 } 110 111 /** A layout manager for tracking changes in the active layout. */ 112 private final LayoutManagerImpl mLayoutManager; 113 114 /** The observer that reacts to scene-change events. */ 115 private final SceneChangeObserver mSceneChangeObserver; 116 117 /** The activity this panel is in. */ 118 protected ChromeActivity mActivity; 119 120 /** The initial height of the Overlay Panel. */ 121 private float mInitialPanelHeight; 122 123 /** The initial location of a touch on the panel */ 124 private float mInitialPanelTouchY; 125 126 /** Whether a touch gesture has been detected. */ 127 private boolean mHasDetectedTouchGesture; 128 129 /** The EventFilter that this panel uses. */ 130 protected EventFilter mEventFilter; 131 132 /** That factory that creates OverlayPanelContents. */ 133 private OverlayPanelContentFactory mContentFactory; 134 135 /** Container for content the panel will show. */ 136 private OverlayPanelContent mContent; 137 138 /** OverlayPanel manager handle for notifications of opening and closing. */ 139 protected OverlayPanelManager mPanelManager; 140 141 /** If the base page text controls have been cleared. */ 142 private boolean mDidClearTextControls; 143 144 /** If the panel should be ignoring swipe events (for compatibility mode). */ 145 private boolean mIgnoreSwipeEvents; 146 147 /** This is used to make sure there is one show request to one close request. */ 148 private boolean mPanelShown; 149 150 /** 151 * Cache the viewport width and height of the screen to filter SceneOverlay#onSizeChanged 152 * events. 153 */ 154 private float mViewportWidth; 155 private float mViewportHeight; 156 157 // ============================================================================================ 158 // Constructor 159 // ============================================================================================ 160 161 /** 162 * @param context The current Android {@link Context}. 163 * @param layoutManager A {@link LayoutManagerImpl} for observing changes in the active layout. 164 * @param panelManager The {@link OverlayPanelManager} responsible for showing panels. 165 */ OverlayPanel( Context context, LayoutManagerImpl layoutManager, OverlayPanelManager panelManager)166 public OverlayPanel( 167 Context context, LayoutManagerImpl layoutManager, OverlayPanelManager panelManager) { 168 super(context, layoutManager); 169 mLayoutManager = layoutManager; 170 mContentFactory = this; 171 172 mPanelManager = panelManager; 173 mPanelManager.registerPanel(this); 174 mEventFilter = new OverlayPanelEventFilter(mContext, this); 175 176 mSceneChangeObserver = new SceneChangeObserver() { 177 @Override 178 public void onTabSelectionHinted(int tabId) {} 179 180 @Override 181 public void onSceneChange(Layout layout) { 182 closePanel(StateChangeReason.UNKNOWN, false); 183 } 184 }; 185 // mLayoutManager will be null in testing. 186 if (mLayoutManager != null) mLayoutManager.addSceneChangeObserver(mSceneChangeObserver); 187 } 188 189 /** 190 * Destroy the native components associated with this panel's content. 191 */ destroy()192 public void destroy() { 193 closePanel(StateChangeReason.UNKNOWN, false); 194 if (mLayoutManager != null) mLayoutManager.removeSceneChangeObserver(mSceneChangeObserver); 195 ApplicationStatus.unregisterActivityStateListener(this); 196 } 197 198 /** 199 * Destroy the components associated with this panel. This should be overridden by panel 200 * implementations to destroy text views and other elements. 201 */ destroyComponents()202 protected void destroyComponents() { 203 destroyOverlayPanelContent(); 204 } 205 206 @Override onClosed(@tateChangeReason int reason)207 protected void onClosed(@StateChangeReason int reason) { 208 mPanelShown = false; 209 setBasePageTextControlsVisibility(true); 210 destroyComponents(); 211 mPanelManager.notifyPanelClosed(this, reason); 212 } 213 214 // ============================================================================================ 215 // General API 216 // ============================================================================================ 217 218 /** 219 * @return True if the panel is in the PEEKED state. 220 */ isPeeking()221 public boolean isPeeking() { 222 return doesPanelHeightMatchState(PanelState.PEEKED); 223 } 224 225 /** 226 * @return Whether the Panel is in its expanded state. 227 */ isExpanded()228 public boolean isExpanded() { 229 return doesPanelHeightMatchState(PanelState.EXPANDED); 230 } 231 232 @Override closePanel(@tateChangeReason int reason, boolean animate)233 public void closePanel(@StateChangeReason int reason, boolean animate) { 234 // If the panel hasn't peeked, then it shouldn't need to close. 235 if (!mPanelShown) return; 236 237 super.closePanel(reason, animate); 238 } 239 240 /** 241 * Request that this panel be shown. 242 * @param reason The reason the panel is being shown. 243 */ requestPanelShow(@tateChangeReason int reason)244 public void requestPanelShow(@StateChangeReason int reason) { 245 if (mPanelShown) return; 246 247 if (mPanelManager != null) { 248 mPanelManager.requestPanelShow(this, reason); 249 } 250 } 251 252 @Override peekPanel(@tateChangeReason int reason)253 public void peekPanel(@StateChangeReason int reason) { 254 // TODO(mdjones): This is making a protected API public and should be removed. Animation 255 // should only be controlled by the OverlayPanelManager. 256 257 // Since the OverlayPanelManager can show panels without requestPanelShow being called, the 258 // flag for the panel being shown should be set to true here. 259 mPanelShown = true; 260 super.peekPanel(reason); 261 } 262 263 /** 264 * @param url The URL that the panel should load. 265 */ loadUrlInPanel(String url)266 public void loadUrlInPanel(String url) { 267 getOverlayPanelContent().loadUrl(url, true); 268 } 269 270 /** 271 * @param url The URL that the panel should load. 272 * @param shouldLoadImmediately If the URL should be loaded immediately when this method is 273 * called. 274 */ loadUrlInPanel(String url, boolean shouldLoadImmediately)275 public void loadUrlInPanel(String url, boolean shouldLoadImmediately) { 276 getOverlayPanelContent().loadUrl(url, shouldLoadImmediately); 277 } 278 279 /** 280 * @return True if a URL has been loaded in the panel's current WebContents. 281 */ isProcessingPendingNavigation()282 public boolean isProcessingPendingNavigation() { 283 return mContent != null && mContent.isProcessingPendingNavigation(); 284 } 285 286 /** 287 * @param activity The ChromeActivity associated with the panel. 288 */ setChromeActivity(ChromeActivity activity)289 public void setChromeActivity(ChromeActivity activity) { 290 mActivity = activity; 291 if (mActivity != null) { 292 ApplicationStatus.registerStateListenerForActivity(this, mActivity); 293 } 294 295 initializeUiState(); 296 } 297 298 /** 299 * Notify the panel's content that it has been touched. 300 * @param x The X position of the touch in dp. 301 */ notifyBarTouched(float x)302 public void notifyBarTouched(float x) { 303 if (!isCoordinateInsideCloseButton(x)) { 304 getOverlayPanelContent().showContent(); 305 } 306 } 307 308 /** 309 * Acknowledges that there was a touch in the search content view, though no immediate action 310 * needs to be taken. This should be overridden by child classes. 311 * TODO(mdjones): Get a better name for this. 312 */ onTouchSearchContentViewAck()313 public void onTouchSearchContentViewAck() { 314 } 315 316 /** 317 * Get a panel's display priority. This has a default to MEDIUM and should be overridden by 318 * child classes. 319 * @return The panel's display priority. 320 */ getPriority()321 public @PanelPriority int getPriority() { 322 return PanelPriority.MEDIUM; 323 } 324 325 /** 326 * @return True if a panel can be suppressed. This should be overridden by each panel. 327 */ canBeSuppressed()328 public boolean canBeSuppressed() { 329 return false; 330 } 331 332 /** 333 * @return The absolute amount in DP that the browser controls have shifted off screen. 334 */ getBrowserControlsOffsetDp()335 protected float getBrowserControlsOffsetDp() { 336 if (mActivity == null) return 0.0f; 337 return -mActivity.getBrowserControlsManager().getTopControlOffset() * mPxToDp; 338 } 339 340 /** 341 * Set the visibility of the base page text selection controls. This will also attempt to 342 * remove focus from the base page to clear any open controls. 343 * @param visible If the text controls are visible. 344 */ setBasePageTextControlsVisibility(boolean visible)345 protected void setBasePageTextControlsVisibility(boolean visible) { 346 if (mActivity == null || mActivity.getActivityTab() == null) return; 347 348 WebContents baseWebContents = mActivity.getActivityTab().getWebContents(); 349 if (baseWebContents == null) return; 350 351 // If the panel does not have focus or isn't open, return. 352 if (isPanelOpened() && mDidClearTextControls && !visible) return; 353 if (!isPanelOpened() && !mDidClearTextControls && visible) return; 354 355 mDidClearTextControls = !visible; 356 357 SelectionPopupController spc = SelectionPopupController.fromWebContents(baseWebContents); 358 if (!visible) spc.setPreserveSelectionOnNextLossOfFocus(true); 359 updateFocus(baseWebContents, visible); 360 361 spc.updateTextSelectionUI(visible); 362 } 363 364 // Claim or lose focus of the content view of the base WebContents. This keeps 365 // the state of the text selected for overlay in consistent way. updateFocus(WebContents baseWebContents, boolean focusBaseView)366 private static void updateFocus(WebContents baseWebContents, boolean focusBaseView) { 367 View baseContentView = baseWebContents.getViewAndroidDelegate() != null 368 ? baseWebContents.getViewAndroidDelegate().getContainerView() 369 : null; 370 if (baseContentView == null) return; 371 372 if (focusBaseView) { 373 baseContentView.requestFocus(); 374 } else { 375 baseContentView.clearFocus(); 376 } 377 } 378 379 /** 380 * Returns whether the panel has been activated -- asked to show. It may not yet be physically 381 * showing due animation. Use {@link #isShowing} instead to determine if the panel is 382 * physically visible. 383 * @return Whether the panel is showing or about to show. 384 */ isActive()385 public boolean isActive() { 386 return mPanelShown; 387 } 388 389 // ============================================================================================ 390 // ActivityStateListener 391 // ============================================================================================ 392 393 @Override onActivityStateChange(Activity activity, int newState)394 public void onActivityStateChange(Activity activity, int newState) { 395 boolean isMultiWindowMode = MultiWindowUtils.getInstance().isLegacyMultiWindow(mActivity) 396 || MultiWindowUtils.getInstance().isInMultiWindowMode(mActivity); 397 398 // In multi-window mode the activity that was interacted with last is resumed and 399 // all others are paused. We should not close Contextual Search in this case, 400 // because the activity may be visible even though it is paused. 401 if (isMultiWindowMode 402 && (newState == ActivityState.PAUSED || newState == ActivityState.RESUMED)) { 403 return; 404 } 405 406 if (newState == ActivityState.RESUMED 407 || newState == ActivityState.STOPPED 408 || newState == ActivityState.DESTROYED) { 409 closePanel(StateChangeReason.UNKNOWN, false); 410 } 411 } 412 413 // ============================================================================================ 414 // Content 415 // ============================================================================================ 416 417 /** 418 * @return True if the panel's content view is showing. 419 */ isContentShowing()420 public boolean isContentShowing() { 421 return mContent != null && mContent.isContentShowing(); 422 } 423 424 /** 425 * @return The WebContents that this panel currently holds. 426 */ getWebContents()427 public WebContents getWebContents() { 428 return mContent != null ? mContent.getWebContents() : null; 429 } 430 431 /** 432 * @return The container view that this panel currently holds. 433 */ getContainerView()434 public ViewGroup getContainerView() { 435 return mContent != null ? mContent.getContainerView() : null; 436 } 437 438 /** 439 * Call this when a loadUrl request has failed to notify the panel that the WebContents can 440 * be reused. See crbug.com/682953 for details. 441 */ onLoadUrlFailed()442 public void onLoadUrlFailed() { 443 if (mContent != null) mContent.onLoadUrlFailed(); 444 } 445 446 /** 447 * Progress observer progress indicator animation for a panel. 448 */ 449 public class PanelProgressObserver extends OverlayContentProgressObserver { 450 @Override onProgressBarStarted()451 public void onProgressBarStarted() { 452 setProgressBarCompletion(0); 453 setProgressBarVisible(true); 454 requestUpdate(); 455 } 456 457 @Override onProgressBarUpdated(float progress)458 public void onProgressBarUpdated(float progress) { 459 setProgressBarCompletion(progress); 460 requestUpdate(); 461 } 462 463 @Override onProgressBarFinished()464 public void onProgressBarFinished() { 465 // Hides the Progress Bar after a delay to make sure it is rendered for at least 466 // a few frames, otherwise its completion won't be visually noticeable. 467 new Handler().postDelayed(new Runnable() { 468 @Override 469 public void run() { 470 setProgressBarVisible(false); 471 requestUpdate(); 472 } 473 }, HIDE_PROGRESS_BAR_DELAY_MS); 474 } 475 } 476 477 /** 478 * Create a new OverlayPanelContent object. This can be overridden for tests. 479 * @return A new OverlayPanelContent object. 480 */ 481 @Override createNewOverlayPanelContent()482 public OverlayPanelContent createNewOverlayPanelContent() { 483 return new OverlayPanelContent(new OverlayContentDelegate(), 484 new OverlayContentProgressObserver(), mActivity, /* isIncognito= */ false, 485 getBarHeight()); 486 } 487 488 /** 489 * Add any other objects that depend on the OverlayPanelContent having already been created. 490 */ createNewOverlayPanelContentInternal()491 private OverlayPanelContent createNewOverlayPanelContentInternal() { 492 OverlayPanelContent content = mContentFactory.createNewOverlayPanelContent(); 493 content.setContentViewSize( 494 getContentViewWidthPx(), getContentViewHeightPx(), isFullWidthSizePanel()); 495 return content; 496 } 497 498 /** 499 * @return A new OverlayPanelContent if the instance was null or the existing one. 500 */ getOverlayPanelContent()501 protected OverlayPanelContent getOverlayPanelContent() { 502 // Only create the content when necessary 503 if (mContent == null) { 504 mContent = createNewOverlayPanelContentInternal(); 505 } 506 return mContent; 507 } 508 509 /** 510 * Destroy the native components of the OverlayPanelContent. 511 */ destroyOverlayPanelContent()512 protected void destroyOverlayPanelContent() { 513 // It is possible that an OverlayPanelContent was never created for this panel. 514 if (mContent != null) { 515 mContent.destroy(); 516 mContent = null; 517 } 518 } 519 520 /** 521 * Updates the browser controls state for the base tab. As these values are set at the renderer 522 * level, there is potential for this impacting other tabs that might share the same 523 * process. See {@link BrowserControlsState.update(Tab, tab, int current, boolean animate)} 524 * @param current The desired current state for the controls. Pass 525 * {@link BrowserControlsState#BOTH} to preserve the current position. 526 * @param animate Whether the controls should animate to the specified ending condition or 527 * should jump immediately. 528 */ updateBrowserControlsState(int current, boolean animate)529 public void updateBrowserControlsState(int current, boolean animate) { 530 TabBrowserControlsConstraintsHelper.update(mActivity.getActivityTab(), current, animate); 531 } 532 533 /** 534 * Sets the top control state based on the internals of the panel. 535 */ updateBrowserControlsState()536 public void updateBrowserControlsState() { 537 if (mContent == null) return; 538 539 // Consider the ContentView height to be fullscreen, and inform the system that 540 // the Toolbar is always visible (from the Compositor's perspective), even though 541 // the Toolbar and Base Page might be offset outside the screen. This means the 542 // renderer will consider the ContentView height to be the fullscreen height 543 // minus the Toolbar height. 544 // 545 // This is necessary to fix the bugs: crbug.com/510205 and crbug.com/510206 546 mContent.updateBrowserControlsState(isFullWidthSizePanel()); 547 } 548 549 /** 550 * Remove the last entry from history provided it is in a given time frame. 551 * @param historyUrl The URL to remove. 552 * @param urlTimeMs The time that the URL was visited. 553 */ removeLastHistoryEntry(String historyUrl, long urlTimeMs)554 public void removeLastHistoryEntry(String historyUrl, long urlTimeMs) { 555 if (mContent == null) return; 556 // Expose OverlayPanelContent method. 557 mContent.removeLastHistoryEntry(historyUrl, urlTimeMs); 558 } 559 560 /** 561 * @return The vertical scroll position of the content. 562 */ getContentVerticalScroll()563 public float getContentVerticalScroll() { 564 return mContent != null ? mContent.getContentVerticalScroll() : 0.0f; 565 } 566 567 // ============================================================================================ 568 // OverlayPanelBase methods. 569 // ============================================================================================ 570 571 @Override onHeightAnimationFinished()572 protected void onHeightAnimationFinished() { 573 super.onHeightAnimationFinished(); 574 575 if (getPanelState() == PanelState.PEEKED || getPanelState() == PanelState.CLOSED) { 576 setBasePageTextControlsVisibility(true); 577 } else { 578 setBasePageTextControlsVisibility(false); 579 } 580 if (mContent != null) { 581 mContent.setPanelTopOffset((int) ((mViewportHeight - getHeight()) / mPxToDp)); 582 } 583 } 584 585 @Override getControlContainerHeightResource()586 protected int getControlContainerHeightResource() { 587 // TODO(mdjones): Investigate passing this in to the constructor instead. 588 assert mActivity != null; 589 return mActivity.getControlContainerHeightResource(); 590 } 591 592 // ============================================================================================ 593 // Layout Integration 594 // ============================================================================================ 595 596 /** 597 * Updates the Panel so it preserves its state when the orientation changes. 598 */ updatePanelForOrientationChange()599 protected void updatePanelForOrientationChange() { 600 resizePanelToState(getPanelState(), StateChangeReason.UNKNOWN); 601 } 602 603 // ============================================================================================ 604 // Generic Event Handling 605 // ============================================================================================ 606 607 /** 608 * Handles the beginning of the swipe gesture. 609 */ handleSwipeStart()610 public void handleSwipeStart() { 611 cancelHeightAnimation(); 612 613 mHasDetectedTouchGesture = false; 614 mInitialPanelHeight = getHeight(); 615 } 616 617 /** 618 * Handles the movement of the swipe gesture. 619 * 620 * @param ty The movement's total displacement in dps. 621 */ handleSwipeMove(float ty)622 public void handleSwipeMove(float ty) { 623 if (mContent != null && ty > 0 && getPanelState() == PanelState.MAXIMIZED) { 624 // Resets the Content View scroll position when swiping the Panel down 625 // after being maximized. 626 mContent.resetContentViewScroll(); 627 } 628 629 // Negative ty value means an upward movement so subtracting ty means expanding the panel. 630 setClampedPanelHeight(mInitialPanelHeight - ty); 631 requestUpdate(); 632 } 633 634 /** 635 * Handles the end of the swipe gesture. 636 */ handleSwipeEnd()637 public void handleSwipeEnd() { 638 // This method will be called after handleFling() and handleClick() 639 // methods because we also need to track down the onUpOrCancel() 640 // action from the Layout. Therefore the animation to the nearest 641 // PanelState should only happen when no other gesture has been 642 // detected. 643 if (!mHasDetectedTouchGesture) { 644 mHasDetectedTouchGesture = true; 645 animateToNearestState(); 646 } 647 } 648 649 /** 650 * Handles the fling gesture. 651 * 652 * @param velocity The velocity of the gesture in dps per second. 653 */ handleFling(float velocity)654 public void handleFling(float velocity) { 655 mHasDetectedTouchGesture = true; 656 animateToProjectedState(velocity); 657 } 658 659 /** 660 * @param x The x coordinate in dp. 661 * @return Whether the given |x| coordinate is inside the close button. 662 */ isCoordinateInsideCloseButton(float x)663 protected boolean isCoordinateInsideCloseButton(float x) { 664 if (LocalizationUtils.isLayoutRtl()) { 665 return x <= (getCloseIconX() + getCloseIconDimension() + mButtonPaddingDps); 666 } else { 667 return x >= (getCloseIconX() - mButtonPaddingDps); 668 } 669 } 670 671 /** 672 * @param x The x coordinate in dp. 673 * @return Whether the given |x| coordinate is inside the open-in-new-tab button. 674 */ isCoordinateInsideOpenTabButton(float x)675 protected boolean isCoordinateInsideOpenTabButton(float x) { 676 // Calculation is the same for RTL: within the button plus padding. 677 return getOpenTabIconX() - mButtonPaddingDps <= x 678 && x <= getOpenTabIconX() + getOpenTabIconDimension() + mButtonPaddingDps; 679 } 680 681 /** 682 * Handles the click gesture. 683 * 684 * @param x The x coordinate of the gesture. 685 * @param y The y coordinate of the gesture. 686 */ handleClick(float x, float y)687 public void handleClick(float x, float y) { 688 mHasDetectedTouchGesture = true; 689 if (isCoordinateInsideBasePage(x, y)) { 690 closePanel(StateChangeReason.BASE_PAGE_TAP, true); 691 } else if (isCoordinateInsideBar(x, y) && !onInterceptBarClick()) { 692 handleBarClick(x, y); 693 } 694 } 695 696 /** 697 * Handles the click gesture specifically on the bar. 698 * 699 * @param x The x coordinate of the gesture. 700 * @param y The y coordinate of the gesture. 701 */ handleBarClick(float x, float y)702 protected void handleBarClick(float x, float y) { 703 if (isPeeking()) { 704 expandPanel(StateChangeReason.SEARCH_BAR_TAP); 705 } 706 } 707 708 /** 709 * Allows the click on the bar to be intercepted. 710 * @return True if the click on the bar was intercepted by this function. 711 */ onInterceptBarClick()712 protected boolean onInterceptBarClick() { 713 return false; 714 } 715 716 /** 717 * If the panel is intercepting the initial bar swipe event. This should be overridden per 718 * panel. 719 * @return True if the panel intercepted the initial bar swipe. 720 */ onInterceptBarSwipe()721 public boolean onInterceptBarSwipe() { 722 return false; 723 } 724 725 // ============================================================================================ 726 // Gesture Event helpers 727 // ============================================================================================ 728 729 /** 730 * @param x The x coordinate in dp. 731 * @param y The y coordinate in dp. 732 * @return Whether the given coordinate is inside the bar area of the overlay. 733 */ isCoordinateInsideBar(float x, float y)734 public boolean isCoordinateInsideBar(float x, float y) { 735 return isCoordinateInsideOverlayPanel(x, y) 736 && y >= getOffsetY() && y <= (getOffsetY() + getBarContainerHeight()); 737 } 738 739 /** 740 * @param x The x coordinate in dp. 741 * @param y The y coordinate in dp. 742 * @return Whether the given coordinate is inside the Overlay Content View area. 743 */ isCoordinateInsideContent(float x, float y)744 public boolean isCoordinateInsideContent(float x, float y) { 745 return isCoordinateInsideOverlayPanel(x, y) 746 && y > getContentY(); 747 } 748 749 /** 750 * @return The horizontal offset of the Overlay Content View in dp. 751 */ getContentX()752 public float getContentX() { 753 return getOffsetX(); 754 } 755 756 /** 757 * @return The vertical offset of the Overlay Content View in dp. 758 */ getContentY()759 public float getContentY() { 760 return getOffsetY() + getBarContainerHeight(); 761 } 762 763 /** 764 * @param x The x coordinate in dp. 765 * @param y The y coordinate in dp. 766 * @return Whether the given coordinate is inside the Overlay Panel area. 767 */ isCoordinateInsideOverlayPanel(float x, float y)768 public boolean isCoordinateInsideOverlayPanel(float x, float y) { 769 return y >= getOffsetY() && y <= (getOffsetY() + getHeight()) 770 && x >= getOffsetX() && x <= (getOffsetX() + getWidth()); 771 } 772 773 /** 774 * @param x The x coordinate in dp. 775 * @param y The y coordinate in dp. 776 * @return Whether the given coordinate is inside the Base Page area. 777 */ isCoordinateInsideBasePage(float x, float y)778 private boolean isCoordinateInsideBasePage(float x, float y) { 779 return !isCoordinateInsideOverlayPanel(x, y); 780 } 781 782 @VisibleForTesting setOverlayPanelContentFactory(OverlayPanelContentFactory factory)783 public void setOverlayPanelContentFactory(OverlayPanelContentFactory factory) { 784 mContentFactory = factory; 785 } 786 787 // ============================================================================================ 788 // GestureHandler and EdgeSwipeHandler implementation. 789 // ============================================================================================ 790 791 @Override onDown(float x, float y, boolean fromMouse, int buttons)792 public void onDown(float x, float y, boolean fromMouse, int buttons) { 793 mInitialPanelTouchY = y; 794 handleSwipeStart(); 795 } 796 797 @Override drag(float x, float y, float deltaX, float deltaY, float tx, float ty)798 public void drag(float x, float y, float deltaX, float deltaY, float tx, float ty) { 799 handleSwipeMove(y - mInitialPanelTouchY); 800 } 801 802 @Override onUpOrCancel()803 public void onUpOrCancel() { 804 handleSwipeEnd(); 805 } 806 807 @Override fling(float x, float y, float velocityX, float velocityY)808 public void fling(float x, float y, float velocityX, float velocityY) { 809 handleFling(velocityY); 810 } 811 812 @Override click(float x, float y, boolean fromMouse, int buttons)813 public void click(float x, float y, boolean fromMouse, int buttons) { 814 handleClick(x, y); 815 } 816 817 @Override onLongPress(float x, float y)818 public void onLongPress(float x, float y) {} 819 820 @Override onPinch(float x0, float y0, float x1, float y1, boolean firstEvent)821 public void onPinch(float x0, float y0, float x1, float y1, boolean firstEvent) {} 822 823 // EdgeSwipeHandler implementation. 824 825 @Override swipeStarted(@crollDirection int direction, float x, float y)826 public void swipeStarted(@ScrollDirection int direction, float x, float y) { 827 if (onInterceptBarSwipe()) { 828 mIgnoreSwipeEvents = true; 829 return; 830 } 831 handleSwipeStart(); 832 } 833 834 @Override swipeUpdated(float x, float y, float dx, float dy, float tx, float ty)835 public void swipeUpdated(float x, float y, float dx, float dy, float tx, float ty) { 836 if (mIgnoreSwipeEvents) return; 837 handleSwipeMove(ty); 838 } 839 840 @Override swipeFinished()841 public void swipeFinished() { 842 if (mIgnoreSwipeEvents) { 843 mIgnoreSwipeEvents = false; 844 return; 845 } 846 handleSwipeEnd(); 847 } 848 849 @Override swipeFlingOccurred(float x, float y, float tx, float ty, float vx, float vy)850 public void swipeFlingOccurred(float x, float y, float tx, float ty, float vx, float vy) { 851 if (mIgnoreSwipeEvents) return; 852 handleFling(vy); 853 } 854 855 @Override isSwipeEnabled(@crollDirection int direction)856 public boolean isSwipeEnabled(@ScrollDirection int direction) { 857 return direction == ScrollDirection.UP && isShowing(); 858 } 859 860 // Other event handlers. 861 862 /** 863 * The user has performed a down event and has not performed a move or up yet. This event is 864 * commonly used to provide visual feedback to the user to let them know that their action has 865 * been recognized. 866 * See {@link GestureDetector.SimpleOnGestureListener#onShowPress()}. 867 * @param x The x coordinate in dp. 868 * @param y The y coordinate in dp. 869 */ onShowPress(float x, float y)870 public void onShowPress(float x, float y) {} 871 872 // ============================================================================================ 873 // SceneOverlay implementation. 874 // ============================================================================================ 875 876 @Override getUpdatedSceneOverlayTree( RectF viewport, RectF visibleViewport, ResourceManager resourceManager, float yOffset)877 public SceneOverlayLayer getUpdatedSceneOverlayTree( 878 RectF viewport, RectF visibleViewport, ResourceManager resourceManager, float yOffset) { 879 return null; 880 } 881 882 @Override isSceneOverlayTreeShowing()883 public boolean isSceneOverlayTreeShowing() { 884 return isShowing(); 885 } 886 887 @Override getEventFilter()888 public EventFilter getEventFilter() { 889 return mEventFilter; 890 } 891 892 @Override onSizeChanged(float width, float height, float visibleViewportOffsetY, int orientation)893 public void onSizeChanged(float width, float height, float visibleViewportOffsetY, 894 int orientation) { 895 // Filter events that don't change the viewport width or height. 896 if (height != mViewportHeight || width != mViewportWidth) { 897 // We only care if the orientation is changing or we're shifting in/out of multi-window. 898 // In either case the screen's viewport width or height will certainly change. 899 mViewportWidth = width; 900 mViewportHeight = height; 901 902 onLayoutChanged(width, height, visibleViewportOffsetY); 903 resizePanelContentView(); 904 } 905 } 906 907 /** 908 * Resize the panel's ContentView. Apply adjusted bar size to the height. 909 */ resizePanelContentView()910 protected void resizePanelContentView() { 911 if (!isShowing()) return; 912 913 OverlayPanelContent panelContent = getOverlayPanelContent(); 914 915 // Device could have been rotated before panel webcontent creation. Update content size. 916 panelContent.setContentViewSize( 917 getContentViewWidthPx(), getContentViewHeightPx(), isFullWidthSizePanel()); 918 panelContent.resizePanelContentView(); 919 } 920 921 @Override getVirtualViews(List<VirtualView> views)922 public void getVirtualViews(List<VirtualView> views) { 923 // TODO(mdjones): Add views for accessibility. 924 } 925 926 @Override handlesTabCreating()927 public boolean handlesTabCreating() { 928 // If the panel is not opened, do not handle tab creating. 929 if (!isPanelOpened()) return false; 930 // Updates BrowserControls' State so the Toolbar becomes visible. 931 // TODO(pedrosimonetti): The transition when promoting to a new tab is only smooth 932 // if the SearchContentView's vertical scroll position is zero. Otherwise the 933 // ContentView will appear to jump in the screen. Coordinate with @dtrainor to solve 934 // this problem. 935 updateBrowserControlsState(BrowserControlsState.BOTH, false); 936 return true; 937 } 938 939 @Override shouldHideAndroidBrowserControls()940 public boolean shouldHideAndroidBrowserControls() { 941 return isPanelOpened(); 942 } 943 944 @Override updateOverlay(long time, long dt)945 public boolean updateOverlay(long time, long dt) { 946 if (isPanelOpened()) setBasePageTextControlsVisibility(false); 947 return true; 948 } 949 950 @Override onBackPressed()951 public boolean onBackPressed() { 952 if (!isShowing()) return false; 953 closePanel(StateChangeReason.BACK_PRESS, true); 954 return true; 955 } 956 } 957