| Index: chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
|
| index 5fc766a0659bbd4fcbf7dd7c6334816a405190ae..4426bcfdaa3a46cb27256b8f517fc864ee941c9a 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
|
| @@ -4,6 +4,7 @@
|
|
|
| package org.chromium.chrome.browser.contextualsearch;
|
|
|
| +import android.os.Handler;
|
| import android.text.TextUtils;
|
| import android.view.View;
|
| import android.view.ViewGroup;
|
| @@ -23,6 +24,7 @@ import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChange
|
| import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContentViewDelegate;
|
| import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
|
| import org.chromium.chrome.browser.contextualsearch.ContextualSearchBlacklist.BlacklistReason;
|
| +import org.chromium.chrome.browser.contextualsearch.ContextualSearchInternalStateController.InternalState;
|
| import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionController.SelectionType;
|
| import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler;
|
| import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.OverrideUrlLoadingResult;
|
| @@ -50,7 +52,6 @@ import org.chromium.content_public.browser.WebContents;
|
| import org.chromium.content_public.common.BrowserControlsState;
|
| import org.chromium.content_public.common.ContentUrlConstants;
|
| import org.chromium.net.NetworkChangeNotifier;
|
| -import org.chromium.ui.base.WindowAndroid;
|
|
|
| import java.net.MalformedURLException;
|
| import java.net.URL;
|
| @@ -58,16 +59,17 @@ import java.util.regex.Pattern;
|
|
|
| import javax.annotation.Nullable;
|
|
|
| -
|
| /**
|
| - * Manager for the Contextual Search feature.
|
| - * This class keeps track of the status of Contextual Search and coordinates the control
|
| - * with the layout.
|
| + * Manager for the Contextual Search feature. This class keeps track of the status of Contextual
|
| + * Search and coordinates the control with the layout.
|
| */
|
| public class ContextualSearchManager implements ContextualSearchManagementDelegate,
|
| ContextualSearchTranslateInterface,
|
| ContextualSearchNetworkCommunicator,
|
| ContextualSearchSelectionHandler, SelectionClient {
|
| + // TODO(donnd): provide an inner class that implements some of these interfaces (like the
|
| + // ContextualSearchTranslateInterface) rather than having the manager itself implement the
|
| + // interface because that exposes all the public methods of that interface at the manager level.
|
| private static final String INTENT_URL_PREFIX = "intent:";
|
|
|
| // The animation duration of a URL being promoted to a tab when triggered by an
|
| @@ -83,6 +85,15 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| // When we don't need to send any "home country" code we can just pass the empty string.
|
| private static final String NO_HOME_COUNTRY = "";
|
|
|
| + // How long to wait for a tap near a previous tap before hiding the UI or showing a re-Tap.
|
| + // This setting is not critical: in practice it determines how long to wait after an invalid
|
| + // tap for the page to respond before hiding the UI. Specifically this setting just needs to be
|
| + // long enough for Blink's decisions before calling handleShowUnhandledTapUIIfNeeded (which
|
| + // probably are page-dependent), and short enough that the Bar goes away fairly quickly after a
|
| + // tap on non-text or whitespace: We currently do not get notification in these cases (hence the
|
| + // timer).
|
| + private static final int TAP_NEAR_PREVIOUS_DETECTION_DELAY_MS = 100;
|
| +
|
| private final ObserverList<ContextualSearchObserver> mObservers =
|
| new ObserverList<ContextualSearchObserver>();
|
|
|
| @@ -94,6 +105,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| private ContextualSearchSelectionController mSelectionController;
|
| private ContextualSearchNetworkCommunicator mNetworkCommunicator;
|
| private ContextualSearchPolicy mPolicy;
|
| + private ContextualSearchInternalStateController mInternalStateController;
|
|
|
| @VisibleForTesting
|
| protected ContextualSearchTranslateController mTranslateController;
|
| @@ -117,7 +129,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| private boolean mWereSearchResultsSeen;
|
| private boolean mWereInfoBarsHidden;
|
| private boolean mDidPromoteSearchNavigation;
|
| - private boolean mDidBasePageLoadJustStart;
|
| +
|
| private boolean mWasActivatedByTap;
|
| private boolean mIsInitialized;
|
|
|
| @@ -148,14 +160,10 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| private ContextualSearchRequest mSearchRequest;
|
| private ContextualSearchRequest mLastSearchRequestLoaded;
|
|
|
| - /**
|
| - * Whether the Accessibility Mode is enabled.
|
| - */
|
| + /** Whether the Accessibility Mode is enabled. */
|
| private boolean mIsAccessibilityModeEnabled;
|
|
|
| - /**
|
| - * Tap Experiments and other variable behavior.
|
| - */
|
| + /** Tap Experiments and other variable behavior. */
|
| private ContextualSearchHeuristics mHeuristics;
|
| private QuickAnswersHeuristic mQuickAnswersHeuristic;
|
|
|
| @@ -173,14 +181,12 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
|
|
| /**
|
| * Constructs the manager for the given activity, and will attach views to the given parent.
|
| - * @param activity The {@code ChromeActivity} in use.
|
| - * @param windowAndroid The {@code WindowAndroid} associated with Chrome.
|
| + * @param activity The {@code ChromeActivity} in use.
|
| * @param tabPromotionDelegate The {@link ContextualSearchTabPromotionDelegate} that is
|
| - * responsible for building tabs from contextual search
|
| - * {@link ContentViewCore}s.
|
| + * responsible for building tabs from contextual search {@link ContentViewCore}s.
|
| */
|
| - public ContextualSearchManager(ChromeActivity activity, WindowAndroid windowAndroid,
|
| - ContextualSearchTabPromotionDelegate tabPromotionDelegate) {
|
| + public ContextualSearchManager(
|
| + ChromeActivity activity, ContextualSearchTabPromotionDelegate tabPromotionDelegate) {
|
| mActivity = activity;
|
| mTabPromotionDelegate = tabPromotionDelegate;
|
|
|
| @@ -220,6 +226,9 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| mPolicy = new ContextualSearchPolicy(mSelectionController, mNetworkCommunicator);
|
|
|
| mTranslateController = new ContextualSearchTranslateController(activity, mPolicy, this);
|
| +
|
| + mInternalStateController = new ContextualSearchInternalStateController(
|
| + mPolicy, getContextualSearchInternalStateHandler());
|
| }
|
|
|
| /**
|
| @@ -240,6 +249,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| mWereSearchResultsSeen = false;
|
| mIsInitialized = true;
|
|
|
| + mInternalStateController.reset(StateChangeReason.UNKNOWN);
|
| +
|
| listenForTabModelSelectorNotifications();
|
| }
|
|
|
| @@ -284,6 +295,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| mFindToolbarManager = null;
|
| mFindToolbarObserver = null;
|
| }
|
| + mInternalStateController.enter(InternalState.UNDEFINED);
|
| }
|
|
|
| @Override
|
| @@ -297,51 +309,33 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| return mActivity;
|
| }
|
|
|
| - /**
|
| - * @return Whether the Search Panel is opened. That is, whether it is EXPANDED or MAXIMIZED.
|
| - */
|
| + /** @return Whether the Search Panel is opened. That is, whether it is EXPANDED or MAXIMIZED. */
|
| public boolean isSearchPanelOpened() {
|
| return mSearchPanel.isPanelOpened();
|
| }
|
|
|
| - /**
|
| - * @return The Base Page's {@link ContentViewCore}.
|
| - */
|
| + /** @return The Base Page's {@link ContentViewCore}. */
|
| @Nullable
|
| private WebContents getBaseWebContents() {
|
| return mSelectionController.getBaseWebContents();
|
| }
|
|
|
| - /**
|
| - * Notifies that the base page has started loading a page.
|
| - */
|
| + /** Notifies that the base page has started loading a page. */
|
| public void onBasePageLoadStarted() {
|
| mSelectionController.onBasePageLoadStarted();
|
| }
|
|
|
| - /**
|
| - * Notifies that a Context Menu has been shown.
|
| - */
|
| + /** Notifies that a Context Menu has been shown. */
|
| void onContextMenuShown() {
|
| mSelectionController.onContextMenuShown();
|
| }
|
|
|
| /**
|
| - * Hides the Contextual Search UX.
|
| + * Hides the Contextual Search UX by changing into the IDLE state.
|
| * @param reason The {@link StateChangeReason} for hiding Contextual Search.
|
| */
|
| public void hideContextualSearch(StateChangeReason reason) {
|
| - if (mContext != null) mContext.destroy();
|
| - mContext = null;
|
| - if (mSearchPanel == null) return;
|
| -
|
| - if (mSearchPanel.isShowing()) {
|
| - mSearchPanel.closePanel(reason, false);
|
| - } else {
|
| - if (mSelectionController.getSelectionType() == SelectionType.TAP) {
|
| - mSelectionController.clearSelection();
|
| - }
|
| - }
|
| + mInternalStateController.reset(reason);
|
| }
|
|
|
| @Override
|
| @@ -384,9 +378,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| notifyHideContextualSearch();
|
| }
|
|
|
| - /**
|
| - * Called when the system back button is pressed. Will hide the layout.
|
| - */
|
| + /** Called when the system back button is pressed. Will hide the layout. */
|
| public boolean onBackPressed() {
|
| if (!mIsInitialized || !mSearchPanel.isShowing()) return false;
|
| hideContextualSearch(StateChangeReason.BACK_PRESS);
|
| @@ -395,7 +387,6 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
|
|
| /**
|
| * Shows the Contextual Search UX.
|
| - * Calls back into onGetContextualSearchQueryResponse.
|
| * @param stateChangeReason The reason explaining the change of state.
|
| */
|
| private void showContextualSearch(StateChangeReason stateChangeReason) {
|
| @@ -423,23 +414,15 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| removeLastSearchVisit();
|
| }
|
|
|
| - // TODO(pedrosimonetti): Fix for M47. Replace this with a better delayed load approach.
|
| mSearchPanel.destroyContent();
|
|
|
| String selection = mSelectionController.getSelectedText();
|
| boolean isTap = mSelectionController.getSelectionType() == SelectionType.TAP;
|
| - boolean didRequestSurroundings = false;
|
| if (isTap) {
|
| - // If the user action was not a long-press, immediately start loading content.
|
| + // If the user action was not a long-press, we should not delay before loading content.
|
| mShouldLoadDelayedSearch = false;
|
| }
|
| if (isTap && mPolicy.shouldPreviousTapResolve()) {
|
| - if (mContext != null) mContext.destroy();
|
| - mContext = new ContextualSearchContext(
|
| - selection, mPolicy.getHomeCountry(mActivity), mPolicy.maySendBasePageUrl());
|
| - nativeGatherSurroundingText(
|
| - mNativeContextualSearchManagerPtr, mContext, getBaseWebContents());
|
| - didRequestSurroundings = true;
|
| // Cache the native translate data, so JNI calls won't be made when time-critical.
|
| mTranslateController.cacheNativeTranslateData();
|
| } else {
|
| @@ -459,19 +442,6 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| : "ContextualSearch.ManualRefineMultiWord");
|
| }
|
| }
|
| -
|
| - if (!didRequestSurroundings && getBaseWebContents() != null) {
|
| - // Gather surrounding text for Icing integration, which will make the selection and
|
| - // a shorter version of the surroundings available for Conversational Search.
|
| - // Although the surroundings are extracted, they will not be sent to the server as
|
| - // part of search term resolution, just sent to Icing which keeps them local until
|
| - // the user activates a Voice Search.
|
| - if (mContext != null) mContext.destroy();
|
| - mContext = new ContextualSearchContext();
|
| - nativeGatherSurroundingText(
|
| - mNativeContextualSearchManagerPtr, mContext, getBaseWebContents());
|
| - }
|
| -
|
| mWereSearchResultsSeen = false;
|
|
|
| // Show the Peek Promo only when the Panel wasn't previously visible, provided
|
| @@ -505,14 +475,18 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| @Override
|
| public void startSearchTermResolutionRequest(String selection) {
|
| WebContents baseWebContents = getBaseWebContents();
|
| - if (baseWebContents != null && mContext != null) {
|
| + if (baseWebContents != null && mContext != null && mContext.canResolve()) {
|
| nativeStartSearchTermResolutionRequest(
|
| mNativeContextualSearchManagerPtr, mContext, getBaseWebContents());
|
| + } else {
|
| + // Something went wrong and we couldn't resolve.
|
| + hideContextualSearch(StateChangeReason.UNKNOWN);
|
| }
|
| }
|
|
|
| @Override
|
| - @Nullable public URL getBasePageUrl() {
|
| + @Nullable
|
| + public URL getBasePageUrl() {
|
| WebContents baseWebContents = getBaseWebContents();
|
| if (baseWebContents == null) return null;
|
|
|
| @@ -535,25 +509,21 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| return new ContextualSearchRequest(term, altTerm, mid, isLowPriorityEnabled);
|
| }
|
|
|
| - /**
|
| - * Accessor for the {@code InfoBarContainer} currently attached to the {@code Tab}.
|
| - */
|
| + /** Accessor for the {@code InfoBarContainer} currently attached to the {@code Tab}. */
|
| private InfoBarContainer getInfoBarContainer() {
|
| Tab tab = mActivity.getActivityTab();
|
| return tab == null ? null : tab.getInfoBarContainer();
|
| }
|
|
|
| - /**
|
| - * Listens for notifications that should hide the Contextual Search bar.
|
| - */
|
| + /** Listens for notifications that should hide the Contextual Search bar. */
|
| private void listenForTabModelSelectorNotifications() {
|
| TabModelSelector selector = mActivity.getTabModelSelector();
|
|
|
| mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(selector) {
|
| @Override
|
| public void onPageLoadStarted(Tab tab, String url) {
|
| + // Detects navigation of the base page for crbug.com/428368 (navigation-detection).
|
| hideContextualSearch(StateChangeReason.UNKNOWN);
|
| - mDidBasePageLoadJustStart = true;
|
| }
|
|
|
| @Override
|
| @@ -575,9 +545,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
| }
|
|
|
| - /**
|
| - * Stops listening for notifications that should hide the Contextual Search bar.
|
| - */
|
| + /** Stops listening for notifications that should hide the Contextual Search bar. */
|
| private void stopListeningForHideNotifications() {
|
| if (mTabModelSelectorTabObserver != null) mTabModelSelectorTabObserver.destroy();
|
|
|
| @@ -589,9 +557,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
| }
|
|
|
| - /**
|
| - * Clears our private member referencing the native manager.
|
| - */
|
| + /** Clears our private member referencing the native manager. */
|
| @CalledByNative
|
| public void clearNativeManager() {
|
| assert mNativeContextualSearchManagerPtr != 0;
|
| @@ -610,6 +576,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
|
|
| /**
|
| * Called by native code when the surrounding text and selection range are available.
|
| + * This is done for both Tap and Long-press gestures.
|
| * @param encoding The original encoding used on the base page.
|
| * @param surroundingText The Text surrounding the selection.
|
| * @param startOffset The start offset of the selection.
|
| @@ -618,37 +585,34 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| @CalledByNative
|
| private void onTextSurroundingSelectionAvailable(
|
| final String encoding, final String surroundingText, int startOffset, int endOffset) {
|
| - if (mContext != null && mContext.canResolve() && endOffset <= surroundingText.length()) {
|
| - String afterText = surroundingText.substring(endOffset);
|
| - String selection = mSelectionController.getSelectedText();
|
| - // TODO(donnd): check if panel has been requested to show.
|
| - // We used to call mSearchPanel.isShowing() here, but that's unreliable
|
| - // (crbug.com/669600).
|
| - mSearchPanel.setContextDetails(selection, afterText);
|
| - mNetworkCommunicator.startSearchTermResolutionRequest(selection);
|
| - }
|
| - if (!ContextualSearchFieldTrial.isPageContentNotificationDisabled()) {
|
| - GSAContextDisplaySelection selection = new GSAContextDisplaySelection(
|
| - encoding, surroundingText, startOffset, endOffset);
|
| - notifyShowContextualSearch(selection);
|
| + if (mInternalStateController.isStillWorkingOn(InternalState.GATHERING_SURROUNDINGS)) {
|
| + assert mContext != null;
|
| + // Sometimes Blink returns empty surroundings and 0 offsets so reset in that case.
|
| + // See crbug.com/393100.
|
| + if (surroundingText.length() == 0) {
|
| + mInternalStateController.reset(StateChangeReason.UNKNOWN);
|
| + } else {
|
| + mContext.setSurroundingText(encoding, surroundingText, startOffset, endOffset);
|
| + mInternalStateController.notifyFinishedWorkOn(InternalState.GATHERING_SURROUNDINGS);
|
| + }
|
| }
|
| - mSearchPanel.setWasSelectionPartOfUrl(
|
| - ContextualSearchSelectionController.isSelectionPartOfUrl(
|
| - surroundingText, startOffset, endOffset));
|
| }
|
|
|
| /**
|
| * Called in response to the
|
| * {@link ContextualSearchManager#nativeStartSearchTermResolutionRequest} method.
|
| + * If {@code nativeStartSearchTermResolutionRequest} is called with a previous request sill
|
| + * pending our native delegate is supposed to cancel all previous requests. So this code
|
| + * should only be called with data corresponding to the most recent request.
|
| * @param isNetworkUnavailable Indicates if the network is unavailable, in which case all other
|
| * parameters should be ignored.
|
| - * @param responseCode The HTTP response code. If the code is not OK, the query
|
| - * should be ignored.
|
| + * @param responseCode The HTTP response code. If the code is not OK, the query should be
|
| + * ignored.
|
| * @param searchTerm The term to use in our subsequent search.
|
| * @param displayText The text to display in our UX.
|
| * @param alternateTerm The alternate term to display on the results page.
|
| * @param mid the MID for an entity to use to trigger a Knowledge Panel, or an empty string.
|
| - * A MID is a unique identifier for an entity in the Search Knowledge Graph.
|
| + * A MID is a unique identifier for an entity in the Search Knowledge Graph.
|
| * @param selectionStartAdjust A positive number of characters that the start of the existing
|
| * selection should be expanded by.
|
| * @param selectionEndAdjust A positive number of characters that the end of the existing
|
| @@ -677,6 +641,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| boolean doPreventPreload, int selectionStartAdjust, int selectionEndAdjust,
|
| String contextLanguage, String thumbnailUrl, String caption, String quickActionUri,
|
| int quickActionCategory) {
|
| + if (!mInternalStateController.isStillWorkingOn(InternalState.RESOLVING)) return;
|
| +
|
| // Show an appropriate message for what to search for.
|
| String message;
|
| boolean doLiteralSearch = false;
|
| @@ -749,15 +715,17 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
|
|
| // Adjust the selection unless the user changed it since we initiated the search.
|
| - if (selectionStartAdjust != 0
|
| - || selectionEndAdjust != 0
|
| - && mSelectionController.getSelectionType() == SelectionType.TAP) {
|
| - String originalSelection = mContext == null ? null : mContext.getSelection();
|
| + if ((selectionStartAdjust != 0 || selectionEndAdjust != 0)
|
| + && mSelectionController.getSelectionType() == SelectionType.TAP) {
|
| + String originalSelection = mContext == null ? null : mContext.getInitialSelectedWord();
|
| if (originalSelection != null
|
| && originalSelection.equals(mSelectionController.getSelectedText())) {
|
| mSelectionController.adjustSelection(selectionStartAdjust, selectionEndAdjust);
|
| + mContext.onSelectionAdjusted(selectionStartAdjust, selectionEndAdjust);
|
| }
|
| }
|
| +
|
| + mInternalStateController.notifyFinishedWorkOn(InternalState.RESOLVING);
|
| }
|
|
|
| /**
|
| @@ -769,17 +737,13 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| return mNetworkCommunicator.isOnline();
|
| }
|
|
|
| - /**
|
| - * Handles this {@link ContextualSearchNetworkCommunicator} vector when not under test.
|
| - */
|
| + /** Handles this {@link ContextualSearchNetworkCommunicator} vector when not under test. */
|
| @Override
|
| public boolean isOnline() {
|
| return NetworkChangeNotifier.isOnline();
|
| }
|
|
|
| - /**
|
| - * Loads a Search Request in the Contextual Search's Content View.
|
| - */
|
| + /** Loads a Search Request in the Contextual Search's Content View. */
|
| private void loadSearchUrl() {
|
| mLoadedSearchUrlTimeMs = System.currentTimeMillis();
|
| mLastSearchRequestLoaded = mSearchRequest;
|
| @@ -792,23 +756,12 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| // to coordinate with Chrome-Android folks to come up with a proper fix for this.
|
| // For now, we force the ContentView to be displayed by calling onShow() again
|
| // when a URL is being loaded. See: crbug.com/398206
|
| - if (mSearchPanel.isContentShowing()
|
| - && mSearchPanel.getContentViewCore() != null) {
|
| + if (mSearchPanel.isContentShowing() && mSearchPanel.getContentViewCore() != null) {
|
| mSearchPanel.getContentViewCore().onShow();
|
| }
|
| }
|
|
|
| /**
|
| - * @return Whether a Tap gesture is currently supported.
|
| - */
|
| - private boolean isTapSupported() {
|
| - // Base page just started navigating away, so taps should be ignored.
|
| - if (mDidBasePageLoadJustStart) return false;
|
| -
|
| - return mPolicy.isTapSupported();
|
| - }
|
| -
|
| - /**
|
| * Called to set a caption. The caption may either be included with the search term resolution
|
| * response or set by the page through the CS JavaScript API used to notify CS that there is
|
| * a caption available on the current overlay.
|
| @@ -860,21 +813,42 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| // Observers
|
| // ============================================================================================
|
|
|
| - /**
|
| - * @param observer An observer to notify when the user performs a contextual search.
|
| - */
|
| + /** @param observer An observer to notify when the user performs a contextual search. */
|
| public void addObserver(ContextualSearchObserver observer) {
|
| mObservers.addObserver(observer);
|
| }
|
|
|
| - /**
|
| - * @param observer An observer to no longer notify when the user performs a contextual search.
|
| + /** @param observer An observer to no longer notify when the user performs a contextual search.
|
| */
|
| public void removeObserver(ContextualSearchObserver observer) {
|
| mObservers.removeObserver(observer);
|
| }
|
|
|
| /**
|
| + * Notifies that a new selection has been established and available for Contextual Search.
|
| + * Should be called when the selection changes to notify listeners that care about the selection
|
| + * and surrounding text.
|
| + * Specifically this means we're showing the Contextual Search UX for the given selection.
|
| + * Notifies Icing of the current selection.
|
| + * Also notifies the panel whether the selection was part of a URL.
|
| + */
|
| + private void notifyObserversOfContextSelectionChanged() {
|
| + assert mContext != null;
|
| + String surroundingText = mContext.getSurroundingText();
|
| + assert surroundingText != null;
|
| + int startOffset = mContext.getSelectionStartOffset();
|
| + int endOffset = mContext.getSelectionEndOffset();
|
| + if (!ContextualSearchFieldTrial.isPageContentNotificationDisabled()) {
|
| + GSAContextDisplaySelection selection = new GSAContextDisplaySelection(
|
| + mContext.getEncoding(), surroundingText, startOffset, endOffset);
|
| + notifyShowContextualSearch(selection);
|
| + }
|
| + mSearchPanel.setWasSelectionPartOfUrl(
|
| + ContextualSearchSelectionController.isSelectionPartOfUrl(
|
| + surroundingText, startOffset, endOffset));
|
| + }
|
| +
|
| + /**
|
| * Notifies all Contextual Search observers that a search has occurred.
|
| * @param selectionContext The selection and context that triggered the search.
|
| */
|
| @@ -886,9 +860,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
| }
|
|
|
| - /**
|
| - * Notifies all Contextual Search observers that a search ended and is no longer in effect.
|
| - */
|
| + /** Notifies all Contextual Search observers that a search ended and is no longer in effect. */
|
| private void notifyHideContextualSearch() {
|
| for (ContextualSearchObserver observer : mObservers) {
|
| observer.onHideContextualSearch();
|
| @@ -922,9 +894,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| return new SearchOverlayContentDelegate();
|
| }
|
|
|
| - /**
|
| - * Implementation of OverlayContentDelegate. Made public for testing purposes.
|
| - */
|
| + /** Implementation of OverlayContentDelegate. Made public for testing purposes. */
|
| public class SearchOverlayContentDelegate extends OverlayContentDelegate {
|
| // Note: New navigation or changes to the WebContents are not advised in this class since
|
| // the WebContents is being observed and navigation is already being performed.
|
| @@ -975,8 +945,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| mSelectionController.getSelectedText(), null, null, false);
|
| mDidStartLoadingResolvedSearchRequest = false;
|
| }
|
| - if (mSearchRequest != null && (!mDidStartLoadingResolvedSearchRequest
|
| - || mShouldLoadDelayedSearch)) {
|
| + if (mSearchRequest != null
|
| + && (!mDidStartLoadingResolvedSearchRequest || mShouldLoadDelayedSearch)) {
|
| // mShouldLoadDelayedSearch is used in the long-press case to load content.
|
| // Since content is now created and destroyed for each request, was impossible
|
| // to know if content was already loaded or recently needed to be; this is for
|
| @@ -992,7 +962,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| @Override
|
| public void onContentViewCreated(ContentViewCore contentViewCore) {
|
| // TODO(donnd): Consider moving to OverlayPanelContent.
|
| - // Enable the Contextual Search JavaScript API between our service and the new view.
|
| + // Enable the Contextual Search JavaScript API between our service and the new view.
|
| nativeEnableContextualSearchJsApiForOverlay(
|
| mNativeContextualSearchManagerPtr, contentViewCore.getWebContents());
|
|
|
| @@ -1013,19 +983,21 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
|
|
| @Override
|
| - public boolean shouldInterceptNavigation(ExternalNavigationHandler externalNavHandler,
|
| - NavigationParams navigationParams) {
|
| + public boolean shouldInterceptNavigation(
|
| + ExternalNavigationHandler externalNavHandler, NavigationParams navigationParams) {
|
| mTabRedirectHandler.updateNewUrlLoading(navigationParams.pageTransitionType,
|
| navigationParams.isRedirect,
|
| navigationParams.hasUserGesture || navigationParams.hasUserGestureCarryover,
|
| mActivity.getLastUserInteractionTime(), TabRedirectHandler.INVALID_ENTRY_INDEX);
|
| - ExternalNavigationParams params = new ExternalNavigationParams.Builder(
|
| - navigationParams.url, false, navigationParams.referrer,
|
| - navigationParams.pageTransitionType, navigationParams.isRedirect)
|
| - .setApplicationMustBeInForeground(true)
|
| - .setRedirectHandler(mTabRedirectHandler)
|
| - .setIsMainFrame(navigationParams.isMainFrame)
|
| - .build();
|
| + ExternalNavigationParams params =
|
| + new ExternalNavigationParams
|
| + .Builder(navigationParams.url, false, navigationParams.referrer,
|
| + navigationParams.pageTransitionType,
|
| + navigationParams.isRedirect)
|
| + .setApplicationMustBeInForeground(true)
|
| + .setRedirectHandler(mTabRedirectHandler)
|
| + .setIsMainFrame(navigationParams.isMainFrame)
|
| + .build();
|
| if (externalNavHandler.shouldOverrideUrlLoading(params)
|
| != OverrideUrlLoadingResult.NO_OVERRIDE) {
|
| mSearchPanel.maximizePanelThenPromoteToTab(StateChangeReason.TAB_PROMOTION,
|
| @@ -1051,14 +1023,12 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| mSearchContentViewDelegate = delegate;
|
| }
|
|
|
| - /**
|
| - * Removes the last resolved search URL from the Chrome history.
|
| - */
|
| + /** Removes the last resolved search URL from the Chrome history. */
|
| private void removeLastSearchVisit() {
|
| if (mLastSearchRequestLoaded != null) {
|
| // TODO(pedrosimonetti): Consider having this feature builtin into OverlayPanelContent.
|
| - mSearchPanel.removeLastHistoryEntry(mLastSearchRequestLoaded.getSearchUrl(),
|
| - mLoadedSearchUrlTimeMs);
|
| + mSearchPanel.removeLastHistoryEntry(
|
| + mLastSearchRequestLoaded.getSearchUrl(), mLoadedSearchUrlTimeMs);
|
| }
|
| }
|
|
|
| @@ -1110,22 +1080,17 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
| }
|
|
|
| - /**
|
| - * @return Whether the given HTTP result code represents a failure or not.
|
| - */
|
| + /** @return Whether the given HTTP result code represents a failure or not. */
|
| private boolean isHttpFailureCode(int httpResultCode) {
|
| return httpResultCode <= 0 || httpResultCode >= 400;
|
| }
|
|
|
| - /**
|
| - * @return whether a navigation in the search content view should promote to a separate tab.
|
| - */
|
| + /** @return whether a navigation in the search content view should promote to a separate tab. */
|
| private boolean shouldPromoteSearchNavigation() {
|
| // A navigation can be due to us loading a URL, or a touch in the search content view.
|
| // Require a touch, but no recent loading, in order to promote to a separate tab.
|
| // Note that tapping the opt-in button requires checking for recent loading.
|
| - return mSearchPanel.didTouchContent()
|
| - && !mSearchPanel.isProcessingPendingNavigation();
|
| + return mSearchPanel.didTouchContent() && !mSearchPanel.isProcessingPendingNavigation();
|
| }
|
|
|
| /**
|
| @@ -1204,8 +1169,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| // not yet committed being processed. Otherwise, get the URL from the WebContents.
|
| NavigationEntry entry =
|
| searchContentViewCore.getWebContents().getNavigationController().getPendingEntry();
|
| - String url = entry != null
|
| - ? entry.getUrl() : searchContentViewCore.getWebContents().getUrl();
|
| + String url =
|
| + entry != null ? entry.getUrl() : searchContentViewCore.getWebContents().getUrl();
|
| return url;
|
| }
|
|
|
| @@ -1235,7 +1200,6 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
|
|
| @Override
|
| public void showUnhandledTapUIIfNeeded(final int x, final int y) {
|
| - mDidBasePageLoadJustStart = false;
|
| if (!isOverlayVideoMode()) {
|
| mSelectionController.handleShowUnhandledTapUIIfNeeded(x, y);
|
| }
|
| @@ -1246,6 +1210,11 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| return false;
|
| }
|
|
|
| + // TODO(donnd): add handling of an ACK to selectWordAroundCaret (crbug.com/435778 has details).
|
| +
|
| + /**
|
| + * @return Whether the display is in a full-screen video overlay mode.
|
| + */
|
| private boolean isOverlayVideoMode() {
|
| return mActivity.getFullscreenManager() != null
|
| && mActivity.getFullscreenManager().isOverlayVideoMode();
|
| @@ -1286,6 +1255,13 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| }
|
|
|
| @Override
|
| + public void handleNonSuppressedTap() {
|
| + if (mIsAccessibilityModeEnabled) return;
|
| +
|
| + mInternalStateController.notifyFinishedWorkOn(InternalState.DECIDING_SUPPRESSION);
|
| + }
|
| +
|
| + @Override
|
| public void handleMetricsForWouldSuppressTap(ContextualSearchHeuristics tapHeuristics) {
|
| mHeuristics = tapHeuristics;
|
|
|
| @@ -1303,42 +1279,42 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| public void handleValidTap() {
|
| if (mIsAccessibilityModeEnabled) return;
|
|
|
| - if (isTapSupported()) {
|
| - // Here we are probably starting a new Contextual Search with a Tap gesture (or we'll
|
| - // ignore the tap), therefore we need to clear to properly reflect that a search just
|
| - // started and we don't have the resolved search term yet.
|
| - mSearchRequest = null;
|
| -
|
| - if (mContext != null) mContext.destroy();
|
| - mContext = null;
|
| -
|
| - WebContents baseWebContents = getBaseWebContents();
|
| - if (baseWebContents != null) {
|
| - // Let the policy know that a tap gesture has been received.
|
| - mPolicy.registerTap();
|
| -
|
| - baseWebContents.selectWordAroundCaret();
|
| - }
|
| - }
|
| + mInternalStateController.enter(InternalState.TAP_RECOGNIZED);
|
| }
|
|
|
| + /**
|
| + * Notifies this class that the selection has changed. This may be due to the user moving the
|
| + * selection handles after a long-press, or after a Tap gesture has called selectWordAroundCaret
|
| + * to expand the selection to a whole word.
|
| + */
|
| @Override
|
| public void handleSelection(String selection, boolean selectionValid, SelectionType type,
|
| float x, float y) {
|
| if (mIsAccessibilityModeEnabled) return;
|
|
|
| if (!selection.isEmpty()) {
|
| - StateChangeReason stateChangeReason = type == SelectionType.TAP
|
| - ? StateChangeReason.TEXT_SELECT_TAP : StateChangeReason.TEXT_SELECT_LONG_PRESS;
|
| ContextualSearchUma.logSelectionIsValid(selectionValid);
|
| +
|
| + // Update the context so it knows the selection has changed.
|
| + if (mContext != null) mContext.updateContextFromSelection(selection);
|
| +
|
| if (selectionValid && mSearchPanel != null) {
|
| mSearchPanel.updateBasePageSelectionYPx(y);
|
| if (!mSearchPanel.isShowing()) {
|
| mSearchPanel.getPanelMetrics().onSelectionEstablished(selection);
|
| }
|
| - showContextualSearch(stateChangeReason);
|
| + showSelectionAsSearchInBar(selection);
|
| +
|
| + // TODO(donnd): remove this complication when we get an ACK message from
|
| + // selectWordAroundCaret (see crbug.com/435778).
|
| + if (type == SelectionType.TAP) {
|
| + mInternalStateController.notifyFinishedWorkOn(
|
| + InternalState.START_SHOWING_TAP_UI);
|
| + } else {
|
| + mInternalStateController.enter(InternalState.LONG_PRESS_RECOGNIZED);
|
| + }
|
| } else {
|
| - hideContextualSearch(stateChangeReason);
|
| + hideContextualSearch(StateChangeReason.INVALID_SELECTION);
|
| }
|
| }
|
| }
|
| @@ -1380,6 +1356,141 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| if (mSearchPanel != null) mSearchPanel.getPanelMetrics().setBlacklistReason(reason);
|
| }
|
|
|
| + @Override
|
| + public void handleSelectionCleared() {
|
| + // The selection was just cleared, so we'll want to remove our UX unless it was due to
|
| + // another Tap while the Bar is showing.
|
| + mInternalStateController.enter(InternalState.SELECTION_CLEARED_RECOGNIZED);
|
| + }
|
| +
|
| + /** Shows the given selection as the Search Term in the Bar. */
|
| + private void showSelectionAsSearchInBar(String selection) {
|
| + if (mSearchPanel.isShowing()) mSearchPanel.setSearchTerm(selection);
|
| + }
|
| +
|
| + // ============================================================================================
|
| + // ContextualSearchInternalStateHandler implementation.
|
| + // ============================================================================================
|
| +
|
| + @VisibleForTesting
|
| + ContextualSearchInternalStateHandler getContextualSearchInternalStateHandler() {
|
| + return new ContextualSearchInternalStateHandler() {
|
| + @Override
|
| + public void hideContextualSearchUi(StateChangeReason reason) {
|
| + // Called when the IDLE state has been entered.
|
| + if (mContext != null) mContext.destroy();
|
| + mContext = null;
|
| + if (mSearchPanel == null) return;
|
| +
|
| + if (mSearchPanel.isShowing()) {
|
| + mSearchPanel.closePanel(reason, false);
|
| + } else {
|
| + if (mSelectionController.getSelectionType() == SelectionType.TAP) {
|
| + mSelectionController.clearSelection();
|
| + }
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void gatherSurroundingText() {
|
| + if (mContext != null) mContext.destroy();
|
| + mContext = new ContextualSearchContext() {
|
| + @Override
|
| + void onSelectionChanged() {
|
| + notifyObserversOfContextSelectionChanged();
|
| + }
|
| + };
|
| +
|
| + boolean isTap = mSelectionController.getSelectionType() == SelectionType.TAP;
|
| + if (isTap && mPolicy.shouldPreviousTapResolve()) {
|
| + mContext.setResolveProperties(
|
| + mPolicy.getHomeCountry(mActivity), mPolicy.maySendBasePageUrl());
|
| + }
|
| +
|
| + mInternalStateController.notifyStartingWorkOn(InternalState.GATHERING_SURROUNDINGS);
|
| + nativeGatherSurroundingText(
|
| + mNativeContextualSearchManagerPtr, mContext, getBaseWebContents());
|
| + }
|
| +
|
| + /** Starts the process of deciding if we'll suppress the current Tap gesture or not. */
|
| + @Override
|
| + public void decideSuppression() {
|
| + mInternalStateController.notifyStartingWorkOn(InternalState.DECIDING_SUPPRESSION);
|
| + mSelectionController.handleShouldSuppressTap();
|
| + }
|
| +
|
| + /** Starts showing the Tap UI by selecting a word around the current caret. */
|
| + @Override
|
| + public void startShowingTapUi() {
|
| + WebContents baseWebContents = getBaseWebContents();
|
| + // TODO(donnd): Call isTapSupported earlier so we don't waste time gathering
|
| + // surrounding text and deciding suppression when unsupported, or remove the whole
|
| + // idea of unsupported taps in favor of deciding suppression better.
|
| + // Details in crbug.com/715297.
|
| + if (baseWebContents != null && mPolicy.isTapSupported()) {
|
| + mInternalStateController.notifyStartingWorkOn(
|
| + InternalState.START_SHOWING_TAP_UI);
|
| + baseWebContents.selectWordAroundCaret();
|
| + // Let the policy know that a valid tap gesture has been received.
|
| + mPolicy.registerTap();
|
| + } else {
|
| + mInternalStateController.reset(StateChangeReason.UNKNOWN);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Waits for possible Tap gesture that's near enough to the previous tap to be
|
| + * considered a "re-tap". We've done some work on the previous Tap and we just saw the
|
| + * selection get cleared (probably due to a Tap that may or may not be valid).
|
| + * If it's invalid we'll want to hide the UI. If it's valid we'll want to just update
|
| + * the UI rather than having the Bar hide and re-show.
|
| + */
|
| + @Override
|
| + public void waitForPossibleTapNearPrevious() {
|
| + mInternalStateController.notifyStartingWorkOn(
|
| + InternalState.WAITING_FOR_POSSIBLE_TAP_NEAR_PREVIOUS);
|
| + new Handler().postDelayed(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mInternalStateController.notifyFinishedWorkOn(
|
| + InternalState.WAITING_FOR_POSSIBLE_TAP_NEAR_PREVIOUS);
|
| + }
|
| + }, TAP_NEAR_PREVIOUS_DETECTION_DELAY_MS);
|
| + }
|
| +
|
| + /** Starts a Resolve request to our server for the best Search Term. */
|
| + @Override
|
| + public void resolveSearchTerm() {
|
| + mInternalStateController.notifyStartingWorkOn(InternalState.RESOLVING);
|
| + String selection = mSelectionController.getSelectedText();
|
| + assert !TextUtils.isEmpty(selection);
|
| + mNetworkCommunicator.startSearchTermResolutionRequest(selection);
|
| +
|
| + // Update the UI to show the resolve is in progress.
|
| + assert mContext != null;
|
| + assert mContext.getTextContentFollowingSelection() != null;
|
| + mSearchPanel.setContextDetails(
|
| + selection, mContext.getTextContentFollowingSelection());
|
| + }
|
| +
|
| + @Override
|
| + public void showContextualSearchTapUi() {
|
| + mInternalStateController.notifyStartingWorkOn(InternalState.SHOW_FULL_TAP_UI);
|
| + showContextualSearch(StateChangeReason.TEXT_SELECT_TAP);
|
| + mInternalStateController.notifyFinishedWorkOn(InternalState.SHOW_FULL_TAP_UI);
|
| + }
|
| +
|
| + @Override
|
| + public void showContextualSearchLongpressUi() {
|
| + mInternalStateController.notifyStartingWorkOn(
|
| + InternalState.SHOWING_LONGPRESS_SEARCH);
|
| + showContextualSearch(StateChangeReason.TEXT_SELECT_LONG_PRESS);
|
| + mInternalStateController.notifyFinishedWorkOn(
|
| + InternalState.SHOWING_LONGPRESS_SEARCH);
|
| + }
|
| + };
|
| + }
|
| +
|
| // ============================================================================================
|
| // Test helpers
|
| // ============================================================================================
|
| @@ -1394,54 +1505,58 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
|
| mPolicy.setNetworkCommunicator(mNetworkCommunicator);
|
| }
|
|
|
| - /**
|
| - * @return The ContextualSearchPolicy currently being used.
|
| - */
|
| + /** @return The ContextualSearchPolicy currently being used. */
|
| @VisibleForTesting
|
| ContextualSearchPolicy getContextualSearchPolicy() {
|
| return mPolicy;
|
| }
|
|
|
| - /**
|
| - * @param policy The {@link ContextualSearchPolicy} for testing.
|
| - */
|
| + /** @param policy The {@link ContextualSearchPolicy} for testing. */
|
| @VisibleForTesting
|
| void setContextualSearchPolicy(ContextualSearchPolicy policy) {
|
| mPolicy = policy;
|
| }
|
|
|
| - /**
|
| - * @return The {@link ContextualSearchPanel}, for testing purposes only.
|
| - */
|
| + /** @return The {@link ContextualSearchPanel}, for testing purposes only. */
|
| @VisibleForTesting
|
| ContextualSearchPanel getContextualSearchPanel() {
|
| return mSearchPanel;
|
| }
|
|
|
| - /**
|
| - * @return The selection controller, for testing purposes.
|
| - */
|
| + /** @return The selection controller, for testing purposes. */
|
| @VisibleForTesting
|
| ContextualSearchSelectionController getSelectionController() {
|
| return mSelectionController;
|
| }
|
|
|
| - /**
|
| - * @param controller The {@link ContextualSearchSelectionController}, for testing purposes.
|
| - */
|
| + /** @param controller The {@link ContextualSearchSelectionController}, for testing purposes. */
|
| @VisibleForTesting
|
| void setSelectionController(ContextualSearchSelectionController controller) {
|
| mSelectionController = controller;
|
| }
|
|
|
| - /**
|
| - * @return The current search request, or {@code null} if there is none, for testing.
|
| - */
|
| + /** @return The current search request, or {@code null} if there is none, for testing. */
|
| @VisibleForTesting
|
| ContextualSearchRequest getRequest() {
|
| return mSearchRequest;
|
| }
|
|
|
| + @VisibleForTesting
|
| + ContextualSearchTabPromotionDelegate getTabPromotionDelegate() {
|
| + return mTabPromotionDelegate;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + void setContextualSearchInternalStateController(
|
| + ContextualSearchInternalStateController controller) {
|
| + mInternalStateController = controller;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + protected ContextualSearchInternalStateController getContextualSearchInternalStateController() {
|
| + return mInternalStateController;
|
| + }
|
| +
|
| // ============================================================================================
|
| // Native calls
|
| // ============================================================================================
|
|
|