Chromium Code Reviews| 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..0912388561627a8bfeed904bb11aad9a4c93d7d0 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,40 +309,41 @@ 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) { |
| + mInternalStateController.reset(reason); |
| + } |
| + |
| + /** |
| + * Does the work of hiding the Contextual Search UX. |
| + * |
| + * @param reason The {@link StateChangeReason} for hiding Contextual Search. |
| + */ |
| + private void handleHideContextualSearch(StateChangeReason reason) { |
| if (mContext != null) mContext.destroy(); |
| mContext = null; |
| if (mSearchPanel == null) return; |
| @@ -384,9 +397,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 +406,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 +433,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 +461,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 +494,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 +528,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 +564,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 +576,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 +595,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 +604,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 +660,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 +734,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 +756,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 +775,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 +832,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 +879,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 +913,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 +964,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 +981,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 +1002,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 +1042,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 +1099,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 +1188,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 +1219,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 +1229,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 +1274,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 +1298,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 +1375,142 @@ 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 selection as the Search Term in the Bar. */ |
| + private void showSelectionAsSearchInBar() { |
|
Theresa
2017/04/26 18:01:15
findbugs pointed out that this method is never cal
Donn Denman
2017/04/26 20:28:17
Removed, thanks for flagging.
|
| + String selection = mSelectionController.getSelectedText(); |
| + if (TextUtils.isEmpty(selection)) return; |
| + |
| + showSelectionAsSearchInBar(selection); |
| + } |
| + |
| + /** 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() { |
| + /** |
| + * Gathers text surrounding the current selection, which may have been created by either |
| + * a Tap or a Long-press gesture. |
| + */ |
| + @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 hideContextualSearchUi(StateChangeReason reason) { |
| + handleHideContextualSearch(reason); |
| + } |
| + |
| + @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 +1525,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 |
| // ============================================================================================ |