Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4635)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java

Issue 2703473002: [TTS] Extract tapped text before showing UI. (Closed)
Patch Set: Minor update. Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
// ============================================================================================

Powered by Google App Engine
This is Rietveld 408576698