Index: chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..188e231fccd98b23cd1ad4a1907e3f8765243d1a |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
@@ -0,0 +1,440 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.compositor.bottombar.contextualsearch; |
+ |
+import android.content.Context; |
+import android.os.Handler; |
+ |
+import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; |
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate; |
+ |
+/** |
+ * Controls the Contextual Search Panel. |
+ */ |
+public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
+ implements ContextualSearchPanelDelegate { |
+ |
+ /** |
+ * State of the Contextual Search Panel. |
+ */ |
+ public static enum PanelState { |
+ UNDEFINED, |
+ CLOSED, |
+ PEEKED, |
+ PROMO, |
+ EXPANDED, |
+ MAXIMIZED; |
+ } |
+ |
+ /** |
+ * The reason for a change in the Contextual Search Panel's state. |
+ */ |
+ public static enum StateChangeReason { |
+ UNKNOWN, |
+ RESET, |
+ BACK_PRESS, |
+ TEXT_SELECT_TAP, |
+ TEXT_SELECT_LONG_PRESS, |
+ INVALID_SELECTION, |
+ BASE_PAGE_TAP, |
+ BASE_PAGE_SCROLL, |
+ SEARCH_BAR_TAP, |
+ SERP_NAVIGATION, |
+ TAB_PROMOTION, |
+ CLICK, |
+ SWIPE, |
+ FLING, |
+ OPTIN, |
+ OPTOUT; |
+ } |
+ |
+ /** |
+ * The delay after which the hide progress will be hidden. |
+ */ |
+ private static final long HIDE_PROGRESS_BAR_DELAY = 1000 / 60 * 4; |
+ |
+ /** |
+ * The initial height of the Contextual Search Panel. |
+ */ |
+ private float mInitialPanelHeight; |
+ |
+ /** |
+ * Whether the Panel should be promoted to a new tab after being maximized. |
+ */ |
+ private boolean mShouldPromoteToTabAfterMaximizing; |
+ |
+ /** |
+ * Whether a touch gesture has been detected. |
+ */ |
+ private boolean mHasDetectedTouchGesture; |
+ |
+ /** |
+ * Whether the search content view has been touched. |
+ */ |
+ private boolean mHasSearchContentViewBeenTouched; |
+ |
+ /** |
+ * The {@link ContextualSearchPanelHost} used to communicate with the supported layout. |
+ */ |
+ private ContextualSearchPanelHost mSearchPanelHost; |
+ |
+ /** |
+ * The object for handling global Contextual Search management duties |
+ */ |
+ private ContextualSearchManagementDelegate mManagementDelegate; |
+ |
+ // ============================================================================================ |
+ // Constructor |
+ // ============================================================================================ |
+ |
+ /** |
+ * @param context The current Android {@link Context}. |
+ * @param updateHost The {@link LayoutUpdateHost} used to request updates in the Layout. |
+ */ |
+ public ContextualSearchPanel(Context context, LayoutUpdateHost updateHost) { |
+ super(context, updateHost); |
+ } |
+ |
+ // ============================================================================================ |
+ // Layout Integration |
+ // ============================================================================================ |
+ |
+ /** |
+ * Sets the {@ContextualSearchPanelHost} used to communicate with the supported layout. |
+ * @param host The {@ContextualSearchPanelHost}. |
+ */ |
+ public void setHost(ContextualSearchPanelHost host) { |
+ mSearchPanelHost = host; |
+ } |
+ |
+ // ============================================================================================ |
+ // Contextual Search Manager Integration |
+ // ============================================================================================ |
+ |
+ /** |
+ * Sets the {@code ContextualSearchManagementDelegate} associated with this Layout. |
+ * @param delegate The {@code ContextualSearchManagementDelegate}. |
+ */ |
+ public void setManagementDelegate(ContextualSearchManagementDelegate delegate) { |
+ mManagementDelegate = delegate; |
+ } |
+ |
+ /** |
+ * @return The {@code ContextualSearchManagementDelegate} associated with this Layout. |
+ */ |
+ public ContextualSearchManagementDelegate getManagementDelegate() { |
+ return mManagementDelegate; |
+ } |
+ |
+ /** |
+ * Sets the visibility of the Search Content View. |
+ * @param isVisible True to make it visible. |
+ */ |
+ public void setSearchContentViewVisibility(boolean isVisible) { |
+ if (mManagementDelegate != null) { |
+ mManagementDelegate.setSearchContentViewVisibility(isVisible); |
+ } |
+ } |
+ |
+ // ============================================================================================ |
+ // Generic Event Handling |
+ // ============================================================================================ |
+ |
+ /** |
+ * Handles the beginning of the swipe gesture. |
+ */ |
+ public void handleSwipeStart() { |
+ if (animationIsRunning()) { |
+ cancelAnimation(this, Property.PANEL_HEIGHT); |
+ } |
+ |
+ mHasDetectedTouchGesture = false; |
+ mInitialPanelHeight = getHeight(); |
+ } |
+ |
+ /** |
+ * Handles the movement of the swipe gesture. |
+ * |
+ * @param ty The movement's total displacement in dps. |
+ */ |
+ public void handleSwipeMove(float ty) { |
+ if (ty > 0 && getPanelState() == PanelState.MAXIMIZED) { |
+ // Resets the Search Content View scroll position when swiping the Panel down |
+ // after being maximized. |
+ mManagementDelegate.resetSearchContentViewScroll(); |
+ } |
+ |
+ // Negative ty value means an upward movement so subtracting ty means expanding the panel. |
+ setClampedPanelHeight(mInitialPanelHeight - ty); |
+ requestUpdate(); |
+ } |
+ |
+ /** |
+ * Handles the end of the swipe gesture. |
+ */ |
+ public void handleSwipeEnd() { |
+ // This method will be called after handleFling() and handleClick() |
+ // methods because we also need to track down the onUpOrCancel() |
+ // action from the Layout. Therefore the animation to the nearest |
+ // PanelState should only happen when no other gesture has been |
+ // detected. |
+ if (!mHasDetectedTouchGesture) { |
+ mHasDetectedTouchGesture = true; |
+ animateToNearestState(); |
+ } |
+ } |
+ |
+ /** |
+ * Handles the fling gesture. |
+ * |
+ * @param velocity The velocity of the gesture in dps per second. |
+ */ |
+ public void handleFling(float velocity) { |
+ mHasDetectedTouchGesture = true; |
+ animateToProjectedState(velocity); |
+ } |
+ |
+ /** |
+ * Handles the click gesture. |
+ * |
+ * @param time The timestamp of the gesture. |
+ * @param x The x coordinate of the gesture. |
+ * @param y The y coordinate of the gesture. |
+ */ |
+ public void handleClick(long time, float x, float y) { |
+ mHasDetectedTouchGesture = true; |
+ if (isYCoordinateInsideBasePage(y)) { |
+ closePanel(StateChangeReason.BASE_PAGE_TAP, true); |
+ } else if (isYCoordinateInsideSearchBar(y)) { |
+ // TODO(pedrosimonetti): handle click in the close button here. |
+ if (isPeeking()) { |
+ if (mManagementDelegate.isRunningInCompatibilityMode()) { |
+ mManagementDelegate.openResolvedSearchUrlInNewTab(); |
+ } else { |
+ expandPanel(StateChangeReason.SEARCH_BAR_TAP); |
+ } |
+ } else if (isExpanded()) { |
+ peekPanel(StateChangeReason.SEARCH_BAR_TAP); |
+ } else if (isMaximized()) { |
+ mManagementDelegate.promoteToTab(true); |
+ } |
+ } |
+ } |
+ |
+ // ============================================================================================ |
+ // Gesture Event helpers |
+ // ============================================================================================ |
+ |
+ /** |
+ * @param y The y coordinate in dp. |
+ * @return Whether the given |y| coordinate is inside the Search Bar area. |
+ */ |
+ public boolean isYCoordinateInsideSearchBar(float y) { |
+ return !isYCoordinateInsideBasePage(y) && !isYCoordinateInsideSearchContentView(y); |
+ } |
+ |
+ /** |
+ * @param y The y coordinate in dp. |
+ * @return Whether the given |y| coordinate is inside the Search Content |
+ * View area. |
+ */ |
+ public boolean isYCoordinateInsideSearchContentView(float y) { |
+ return y > getSearchContentViewOffsetY(); |
+ } |
+ |
+ /** |
+ * @return The vertical offset of the Search Content View in dp. |
+ */ |
+ public float getSearchContentViewOffsetY() { |
+ return getOffsetY() + getSearchBarHeight(); |
+ } |
+ |
+ /** |
+ * @param y The y coordinate in dp. |
+ * @return Whether the given |y| coordinate is inside the Base Page area. |
+ */ |
+ private boolean isYCoordinateInsideBasePage(float y) { |
+ return y < getOffsetY(); |
+ } |
+ |
+ /** |
+ * @return Whether the Panel is in its expanded state. |
+ */ |
+ protected boolean isExpanded() { |
+ return doesPanelHeightMatchState(PanelState.EXPANDED); |
+ } |
+ |
+ /** |
+ * Acknowledges that there was a touch in the search content view, though no immediate action |
+ * needs to be taken. |
+ */ |
+ public void onTouchSearchContentViewAck() { |
+ mHasSearchContentViewBeenTouched = true; |
+ } |
+ |
+ // ============================================================================================ |
+ // Animation Handling |
+ // ============================================================================================ |
+ |
+ @Override |
+ protected void onAnimationFinished() { |
+ super.onAnimationFinished(); |
+ |
+ if (shouldHideContextualSearchLayout()) { |
+ if (mSearchPanelHost != null) { |
+ mSearchPanelHost.hideLayout(false); |
+ } |
+ if (getPanelState() == PanelState.CLOSED) { |
+ mManagementDelegate.dismissContextualSearchBar(); |
+ } |
+ } |
+ |
+ if (mShouldPromoteToTabAfterMaximizing && getPanelState() == PanelState.MAXIMIZED) { |
+ mShouldPromoteToTabAfterMaximizing = false; |
+ mManagementDelegate.promoteToTab(false); |
+ } |
+ } |
+ |
+ /** |
+ * Whether the Contextual Search Layout should be hidden. |
+ * |
+ * @return Whether the Contextual Search Layout should be hidden. |
+ */ |
+ private boolean shouldHideContextualSearchLayout() { |
+ final PanelState state = getPanelState(); |
+ |
+ return (state == PanelState.PEEKED || state == PanelState.CLOSED) |
+ && getHeight() == getPanelHeightFromState(state); |
+ } |
+ |
+ // ============================================================================================ |
+ // Panel Delegate |
+ // ============================================================================================ |
+ |
+ @Override |
+ public boolean isShowing() { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ return super.isShowing(); |
+ } |
+ |
+ @Override |
+ public boolean isPeeking() { |
+ return doesPanelHeightMatchState(PanelState.PEEKED); |
+ } |
+ |
+ @Override |
+ public void maximizePanelThenPromoteToTab(StateChangeReason reason) { |
+ mShouldPromoteToTabAfterMaximizing = true; |
+ maximizePanel(reason); |
+ } |
+ |
+ @Override |
+ public void maximizePanelThenPromoteToTab(StateChangeReason reason, long duration) { |
+ mShouldPromoteToTabAfterMaximizing = true; |
+ animatePanelToState(PanelState.MAXIMIZED, reason, duration); |
+ } |
+ |
+ @Override |
+ public void peekPanel(StateChangeReason reason) { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.peekPanel(reason); |
+ |
+ if (getPanelState() == PanelState.CLOSED || getPanelState() == PanelState.PEEKED) { |
+ mHasSearchContentViewBeenTouched = false; |
+ } |
+ } |
+ |
+ @Override |
+ public void closePanel(StateChangeReason reason, boolean animate) { |
+ // If the close action is animated, the Layout will be hidden when |
+ // the animation is finished, so we should only hide the Layout |
+ // here when not animating. |
+ if (!animate && mSearchPanelHost != null) { |
+ mSearchPanelHost.hideLayout(true); |
+ } |
+ mHasSearchContentViewBeenTouched = false; |
+ |
+ super.closePanel(reason, animate); |
+ } |
+ |
+ @Override |
+ public void updateBasePageSelectionYPx(float y) { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.updateBasePageSelectionYPx(y); |
+ } |
+ |
+ @Override |
+ public void setPromoContentHeight(float height) { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.setPromoContentHeight(height); |
+ } |
+ |
+ @Override |
+ public void setShouldHidePromoHeader(boolean shouldHidePromoHeader) { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.setShouldHidePromoHeader(shouldHidePromoHeader); |
+ } |
+ |
+ @Override |
+ public void animateAfterFirstRunSuccess() { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.animateAfterFirstRunSuccess(); |
+ } |
+ |
+ @Override |
+ public void onLoadStarted() { |
+ setProgressBarCompletion(0); |
+ setProgressBarVisible(true); |
+ requestUpdate(); |
+ } |
+ |
+ @Override |
+ public void onLoadStopped() { |
+ // Hides the Progress Bar after a delay to make sure it is rendered for at least |
+ // a few frames, otherwise its completion won't be visually noticeable. |
+ new Handler().postDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ setProgressBarVisible(false); |
+ requestUpdate(); |
+ } |
+ }, HIDE_PROGRESS_BAR_DELAY); |
+ } |
+ |
+ @Override |
+ public void onLoadProgressChanged(int progress) { |
+ setProgressBarCompletion(progress); |
+ requestUpdate(); |
+ } |
+ |
+ @Override |
+ public PanelState getPanelState() { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ return super.getPanelState(); |
+ } |
+ |
+ @Override |
+ public void setDidSearchInvolvePromo() { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.setDidSearchInvolvePromo(); |
+ } |
+ |
+ @Override |
+ public void setWasSearchContentViewSeen() { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.setWasSearchContentViewSeen(); |
+ } |
+ |
+ @Override |
+ public void setIsPromoActive(boolean shown) { |
+ // NOTE(pedrosimonetti): exposing superclass method to the interface. |
+ super.setIsPromoActive(shown); |
+ } |
+ |
+ @Override |
+ public boolean didTouchSearchContentView() { |
+ return mHasSearchContentViewBeenTouched; |
+ } |
+} |