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 20f75a0474b4999314d4d57019c5aa7f086d0132..3ea63c36fc1a7998f012776f3bf6a3b8e56e9465 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; |
@@ -24,6 +25,7 @@ import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContentViewD |
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel; |
import org.chromium.chrome.browser.contextualsearch.ContextualSearchBlacklist.BlacklistReason; |
import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionController.SelectionType; |
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchStateController.State; |
import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler; |
import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.OverrideUrlLoadingResult; |
import org.chromium.chrome.browser.externalnav.ExternalNavigationParams; |
@@ -53,6 +55,7 @@ import org.chromium.ui.base.WindowAndroid; |
import java.net.MalformedURLException; |
import java.net.URL; |
+import java.util.concurrent.TimeUnit; |
import java.util.regex.Pattern; |
import javax.annotation.Nullable; |
@@ -63,10 +66,10 @@ import javax.annotation.Nullable; |
* 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 { |
+public class ContextualSearchManager |
+ implements ContextualSearchManagementDelegate, ContextualSearchTranslateInterface, |
+ ContextualSearchNetworkCommunicator, ContextualSearchSelectionHandler, |
+ ContextualSearchStateControlled, SelectionClient { |
Theresa
2017/03/28 15:52:55
There are a lot of interfaces listed here. I think
Donn Denman
2017/03/29 18:56:51
Hopefully this is clear now that the State is an I
|
private static final String INTENT_URL_PREFIX = "intent:"; |
// The animation duration of a URL being promoted to a tab when triggered by an |
@@ -82,6 +85,9 @@ 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 = ""; |
+ // The default navigation-detection-delay in milliseconds. |
+ private static final int TAP_NAVIGATION_DETECTION_DELAY = 20; |
+ |
private final ObserverList<ContextualSearchObserver> mObservers = |
new ObserverList<ContextualSearchObserver>(); |
@@ -89,6 +95,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
private final ContextualSearchTabPromotionDelegate mTabPromotionDelegate; |
private final ViewTreeObserver.OnGlobalFocusChangeListener mOnFocusChangeListener; |
private final TabModelObserver mTabModelObserver; |
+ private final ContextualSearchStateController mController; |
private ContextualSearchSelectionController mSelectionController; |
private ContextualSearchNetworkCommunicator mNetworkCommunicator; |
@@ -116,6 +123,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
private boolean mWereSearchResultsSeen; |
private boolean mWereInfoBarsHidden; |
private boolean mDidPromoteSearchNavigation; |
+ |
+ // Detects navigation of the base page for crbug.com/428368. |
private boolean mDidBasePageLoadJustStart; |
private boolean mWasActivatedByTap; |
private boolean mIsInitialized; |
@@ -219,6 +228,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
mPolicy = new ContextualSearchPolicy(mSelectionController, mNetworkCommunicator); |
mTranslateController = new ContextualSearchTranslateController(activity, mPolicy, this); |
+ |
+ mController = new ContextualSearchStateController(mSelectionController, mPolicy, this); |
} |
/** |
@@ -239,6 +250,8 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
mWereSearchResultsSeen = false; |
mIsInitialized = true; |
+ mController.reset(StateChangeReason.UNKNOWN); |
+ |
listenForTabModelSelectorNotifications(); |
} |
@@ -283,6 +296,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
mFindToolbarManager = null; |
mFindToolbarObserver = null; |
} |
+ mController.enter(State.UNDEFINED); |
} |
@Override |
@@ -326,10 +340,18 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
} |
/** |
- * 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) { |
+ mController.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; |
@@ -422,23 +444,18 @@ 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 didResolve = false; |
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'll immediately start 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; |
+ // establish(State.RESOLVING); |
+ didResolve = true; |
// Cache the native translate data, so JNI calls won't be made when time-critical. |
mTranslateController.cacheNativeTranslateData(); |
} else { |
@@ -457,20 +474,12 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
RecordUserAction.record(isSingleWord ? "ContextualSearch.ManualRefineSingleWord" |
: "ContextualSearch.ManualRefineMultiWord"); |
} |
+ /* |
+ if (isTap) { |
+ establish(State.SHOWING_TAP); |
+ } |
+ */ |
} |
- |
- 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,6 +514,16 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
assert mSelectionController.getSelectionType() != SelectionType.UNDETERMINED; |
mWasActivatedByTap = mSelectionController.getSelectionType() == SelectionType.TAP; |
+ |
+ // Update the Bar Context to show the text and after-text. |
+ if (mSearchRequest == null && mContext != null) { |
+ assert mContext.getTextContentFollowingSelection() != null; |
+ assert !TextUtils.isEmpty(selection); |
+ String afterText = mContext.getTextContentFollowingSelection(); |
+ mSearchPanel.setContextDetails(selection, afterText); |
+ } |
+ |
+ notifyContextualSearchSelection(); |
} |
@Override |
@@ -619,11 +638,10 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
*/ |
@CalledByNative |
private void onSurroundingTextAvailable(final String afterText) { |
- 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); |
+ // The panel is not yet showing, so save the afterText so we can show it when we show the |
+ // panel. |
+ assert mContext != null; |
+ mContext.setTextContentFollowingSelection(afterText); |
} |
/** |
@@ -633,12 +651,13 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
@CalledByNative |
private void onIcingSelectionAvailable( |
final String encoding, final String surroundingText, int startOffset, int endOffset) { |
- GSAContextDisplaySelection selection = |
- new GSAContextDisplaySelection(encoding, surroundingText, startOffset, endOffset); |
- mSearchPanel.setWasSelectionPartOfUrl( |
- ContextualSearchSelectionController.isSelectionPartOfUrl( |
- surroundingText, startOffset, endOffset)); |
- notifyShowContextualSearch(selection); |
+ // TODO(donnd): Rename this method! |
+ System.out.println("ctxs onIcingSelectionAvailable"); |
+ if (!mController.isStillWorkingOn(State.GATHERING_SURROUNDINGS)) return; |
+ |
+ assert mContext != null; |
+ mContext.setSurroundingText(encoding, surroundingText, startOffset, endOffset); |
+ mController.notifyFinishedWorkOn(State.GATHERING_SURROUNDINGS); |
} |
/** |
@@ -669,10 +688,13 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
final String mid, boolean doPreventPreload, int selectionStartAdjust, |
int selectionEndAdjust, final String contextLanguage, final String thumbnailUrl, |
final String caption, final String quickActionUri, final int quickActionCategory) { |
+ if (!mController.isStillWorkingOn(State.RESOLVING)) return; |
+ |
mNetworkCommunicator.handleSearchTermResolutionResponse(isNetworkUnavailable, responseCode, |
searchTerm, displayText, alternateTerm, mid, doPreventPreload, selectionStartAdjust, |
selectionEndAdjust, contextLanguage, thumbnailUrl, caption, quickActionUri, |
quickActionCategory); |
+ mController.notifyFinishedWorkOn(State.RESOLVING); |
} |
@Override |
@@ -754,7 +776,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
// Adjust the selection unless the user changed it since we initiated the search. |
if (selectionStartAdjust != 0 || selectionEndAdjust != 0) { |
- String originalSelection = mContext == null ? null : mContext.getSelection(); |
+ String originalSelection = mContext == null ? null : mContext.getSelectedWord(); |
if (originalSelection != null |
&& originalSelection.equals(mSelectionController.getSelectedText())) { |
mSelectionController.adjustSelection(selectionStartAdjust, selectionEndAdjust); |
@@ -877,6 +899,25 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
} |
/** |
+ * Notifies that a new selection has been established that's available for Contextual Search. |
+ * Specifically this means we're showing the Contextual Search UX for the given selection. |
+ * Notifies Icing of the current selection. |
+ * Notifies the panel whether the selection was part of a URL. |
+ */ |
+ private void notifyContextualSearchSelection() { |
+ String surroundingText = mContext.getSurroundingText(); |
+ int startOffset = mContext.getSelectionStartOffset(); |
+ int endOffset = mContext.getSelectionEndOffset(); |
+ 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. |
*/ |
@@ -1242,6 +1283,21 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
} |
} |
+ // DLD @Override |
+ public void selectWordAroundCaretAck(boolean didSelect, int startAdjust, int endAdjust) { |
+ if (isOverlayVideoMode() || !mController.isStillWorkingOn(State.SELECTING_WORD)) return; |
+ if (didSelect) { |
+ // DLD needed???? |
+ // mSelectionController.selectWordAroundCaretAck(didSelect, startAdjust, |
+ // endAdjust); |
+ |
+ System.out.println("ctxs selectWordAroundCaretAck setting start/end adjust"); |
+ assert mContext != null; |
+ mContext.setSelectionAdjust(startAdjust, endAdjust); |
+ } |
+ mController.notifyFinishedWorkOn(State.SELECTING_WORD); |
+ } |
+ |
private boolean isOverlayVideoMode() { |
return mActivity.getFullscreenManager() != null |
&& mActivity.getFullscreenManager().isOverlayVideoMode(); |
@@ -1278,7 +1334,14 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
public void handleSuppressedTap() { |
if (mIsAccessibilityModeEnabled) return; |
- hideContextualSearch(StateChangeReason.BASE_PAGE_TAP); |
+ hideContextualSearch(StateChangeReason.UNKNOWN); |
+ } |
+ |
+ @Override |
+ public void handleNonSuppressedTap() { |
+ if (mIsAccessibilityModeEnabled) return; |
+ |
+ mController.notifyFinishedWorkOn(State.DECIDING_SUPPRESSION); |
} |
@Override |
@@ -1295,46 +1358,67 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
mSearchPanel.getPanelMetrics().setRankerLogExperiments(mHeuristics); |
} |
+ /* |
+ Control flow between this class and the ContextualSearchSelectionController has changed in early |
+ 2017 and can be a bit complicated. |
+ Here's a summary of the Tap control flow: |
+ * The user taps or does a long-press, which establishes an initial selection (or caret for tap). |
+ * We gather page text surrounding the selection. Resolving taps need more than the other kinds. |
+ * We determine if UX should be shown or not (which may be based on page content). |
+ * If it was a tap: |
+ * We select the word. |
+ * Once the word is selected we show our UX and send a resolve request to the server. |
+ * When the search term returns, or if we didn't resolve, we prefetch the SERP in the overlay. |
+ * |
+ * This flow is implemented in these methods: |
+ * handleShowUnhandledTapUIIfNeeded -> handleValidTap (or handleInvalidTap) |
+ * handleValidTap --> nativeGatherSurroundingText OR handleSuppressedTap if tap not allowed. |
+ * nativeGatherSurroundingText --> onSurroundingTextAvailable --> handleShouldSuppressTap. |
+ * handleShouldSuppressTap --> handleSuppressedTap OR handleNonSuppressedTap. |
+ * handleNonSuppressedTap --> selectWordAroundCaret. |
+ * selectWordAroundCaret --> handleSelection --> showContextualSearch. |
+ */ |
+ |
@Override |
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(); |
- } |
- } |
+ System.out.println("ctxs handleValidTap : " + mSelectionController.getSelectionType()); |
+ mController.enter(State.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) { |
+ float x, float y, long selectionStartTimeNanoseconds) { |
+ System.out.println("ctxs handleSelection " + selection); |
if (mIsAccessibilityModeEnabled) return; |
if (!selection.isEmpty()) { |
- StateChangeReason stateChangeReason = type == SelectionType.TAP |
- ? StateChangeReason.TEXT_SELECT_TAP : StateChangeReason.TEXT_SELECT_LONG_PRESS; |
ContextualSearchUma.logSelectionIsValid(selectionValid); |
+ if (mContext != null) { |
+ mContext.updateContextFromSelection(selection, selectionStartTimeNanoseconds); |
+ } |
if (selectionValid && mSearchPanel != null) { |
mSearchPanel.updateBasePageSelectionYPx(y); |
if (!mSearchPanel.isShowing()) { |
mSearchPanel.getPanelMetrics().onSelectionEstablished(selection); |
} |
- showContextualSearch(stateChangeReason); |
+ |
+ if (type == SelectionType.TAP) { |
+ System.out.println("ctxs handleSelection Tap, selecting word!"); |
+ mController.notifyFinishedWorkOn(State.SELECTING_WORD); |
+ } else { |
+ System.out.println("ctxs handleSelection longpress!"); |
+ mController.enter(State.LONG_PRESS_RECOGNIZED); |
+ } |
} else { |
- hideContextualSearch(stateChangeReason); |
+ System.out.println("ctxs handleSelection HIDING!"); |
+ hideContextualSearch(StateChangeReason.INVALID_SELECTION); |
} |
} |
} |
@@ -1377,6 +1461,98 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega |
} |
// ============================================================================================ |
+ // ContextualSearchStateControlled implementation. |
+ // ============================================================================================ |
+ |
+ /** |
+ * 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(); |
+ boolean isTap = mSelectionController.getSelectionType() == SelectionType.TAP; |
+ if (isTap && mPolicy.shouldPreviousTapResolve()) { |
+ mContext = new ContextualSearchContext( |
+ mPolicy.getHomeCountry(mActivity), mPolicy.maySendBasePageUrl()); |
+ } else { |
+ mContext = new ContextualSearchContext(); |
+ } |
+ |
+ System.out.println("ctxs gatherSurroundingText"); |
+ mController.notifyStartingWorkOn(State.GATHERING_SURROUNDINGS); |
+ nativeGatherSurroundingText( |
+ mNativeContextualSearchManagerPtr, mContext, getBaseWebContents()); |
+ } |
+ |
+ /** |
+ * Starts the process of deciding if we'll suppress the current Tap gesture or not. |
+ */ |
+ @Override |
+ public void decideTapSuppression() { |
+ mController.notifyStartingWorkOn(State.DECIDING_SUPPRESSION); |
+ mSelectionController.handleShouldSuppressTap(); |
+ } |
+ |
+ /** |
+ * Starts the process of selecting a word around the current caret. |
+ */ |
+ @Override |
+ public void selectWordAroundCaret() { |
+ WebContents baseWebContents = getBaseWebContents(); |
+ if (baseWebContents != null) { |
+ mController.notifyStartingWorkOn(State.SELECTING_WORD); |
+ baseWebContents.selectWordAroundCaret(); |
+ } else { |
+ mController.reset(StateChangeReason.UNKNOWN); |
+ } |
+ } |
+ |
+ /** |
+ * Waits for possible navigation. |
+ * Many web pages have non-link elements that actually do navigation, so we pause before |
+ * advancing to the next processing state in order to detect the navigation before showing our |
+ * UX. |
+ * See crbug.com/428368. |
+ */ |
+ @Override |
+ public void waitForPossibleNavigation() { |
+ mController.notifyStartingWorkOn(State.WAITING_FOR_POSSIBLE_NAVIGATION); |
+ long elapsedTimeNanoseconds = |
+ System.nanoTime() - mContext.getSelectionStartTimeNanoseconds(); |
+ long elapsedTimeMilliseconds = TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanoseconds); |
+ long delay = Math.max(0, TAP_NAVIGATION_DETECTION_DELAY - elapsedTimeMilliseconds); |
+ new Handler().postDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ // If we detected a navigation, we'll be in the IDLE state again, but that's OK. |
+ mController.notifyFinishedWorkOn(State.WAITING_FOR_POSSIBLE_NAVIGATION); |
+ } |
+ }, delay); |
+ } |
+ |
+ /** |
+ * Starts a Resolve request to our server for the best Search Term. |
+ */ |
+ @Override |
+ public void startSearchTermResolutionRequest() { |
+ mController.notifyStartingWorkOn(State.RESOLVING); |
+ String selection = mSelectionController.getSelectedText(); |
+ assert !TextUtils.isEmpty(selection); |
+ mNetworkCommunicator.startSearchTermResolutionRequest(selection); |
+ } |
+ |
+ @Override |
+ public void hideContextualSearchUx(StateChangeReason reason) { |
+ handleHideContextualSearch(reason); |
+ } |
+ |
+ @Override |
+ public void showContextualSearchUx(StateChangeReason reason) { |
+ showContextualSearch(reason); |
+ } |
+ |
+ // ============================================================================================ |
// Test helpers |
// ============================================================================================ |