| 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;
|
| + }
|
| +}
|
|
|