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 |
// ============================================================================================ |