Commit 4a22f717 authored by donnd's avatar donnd Committed by Commit Bot

[TTS] Add some initial signals for Tap in content.

Adds a few simple signals for a Tap gesture relative to the content
that was tapped.  These signals include word length and tap offset.
Logs UMA for CTR when these signals are present.

Updates the CS Context with analysis of the content where the Tap
gesture occurred.

One signal determines if the user tapped near the edge of a word
or not. The rest look at very short words, or relatively long words.

All signals use the existing CSHeuristics framework to do the checking and logging.

Updates the CSHeuristics framework to make the CSContext available
to heuristics so they can inspect the text tapped before deciding
whether to suppress or not.

Adds Field Trial params to enable actual suppression based on
these new signals (likely to be used only for interactive-testing
and demonstration purposes).

BUG=723194

Review-Url: https://codereview.chromium.org/2906763002
Cr-Commit-Position: refs/heads/master@{#476485}
parent 9aecc2e1
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.contextualsearch; package org.chromium.chrome.browser.contextualsearch;
import android.annotation.SuppressLint;
import android.text.TextUtils; import android.text.TextUtils;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
...@@ -17,7 +18,10 @@ import javax.annotation.Nullable; ...@@ -17,7 +18,10 @@ import javax.annotation.Nullable;
* or changed. * or changed.
*/ */
public abstract class ContextualSearchContext { public abstract class ContextualSearchContext {
static final int INVALID_SELECTION_OFFSET = -1; static final int INVALID_OFFSET = -1;
// Non-visible word-break marker.
private static final int SOFT_HYPHEN_CHAR = '\u00AD';
// Pointer to the native instance of this class. // Pointer to the native instance of this class.
private long mNativePointer; private long mNativePointer;
...@@ -30,8 +34,11 @@ public abstract class ContextualSearchContext { ...@@ -30,8 +34,11 @@ public abstract class ContextualSearchContext {
private String mSurroundingText; private String mSurroundingText;
// The start and end offsets of the selection within the text content. // The start and end offsets of the selection within the text content.
private int mSelectionStartOffset = INVALID_SELECTION_OFFSET; private int mSelectionStartOffset = INVALID_OFFSET;
private int mSelectionEndOffset = INVALID_SELECTION_OFFSET; private int mSelectionEndOffset = INVALID_OFFSET;
// The offset of an initial Tap gesture within the text content.
private int mTapOffset = INVALID_OFFSET;
// The initial word selected by a Tap, or null. // The initial word selected by a Tap, or null.
private String mInitialSelectedWord; private String mInitialSelectedWord;
...@@ -39,6 +46,13 @@ public abstract class ContextualSearchContext { ...@@ -39,6 +46,13 @@ public abstract class ContextualSearchContext {
// The original encoding of the base page. // The original encoding of the base page.
private String mEncoding; private String mEncoding;
// The tapped word, as analyzed internally before selection takes place, or {@code null} if no
// analysis has been done yet.
private String mTappedWord;
// The offset of the tap within the tapped word or {@code INVALID_OFFSET} if not yet analyzed.
private int mTappedWordOffset = INVALID_OFFSET;
/** /**
* Constructs a context that tracks the selection and some amount of page content. * Constructs a context that tracks the selection and some amount of page content.
*/ */
...@@ -85,6 +99,9 @@ public abstract class ContextualSearchContext { ...@@ -85,6 +99,9 @@ public abstract class ContextualSearchContext {
mSurroundingText = surroundingText; mSurroundingText = surroundingText;
mSelectionStartOffset = startOffset; mSelectionStartOffset = startOffset;
mSelectionEndOffset = endOffset; mSelectionEndOffset = endOffset;
if (startOffset == endOffset && !hasAnalyzedTap()) {
analyzeTap(startOffset);
}
// Notify of an initial selection if it's not empty. // Notify of an initial selection if it's not empty.
if (endOffset > startOffset) onSelectionChanged(); if (endOffset > startOffset) onSelectionChanged();
} }
...@@ -99,7 +116,7 @@ public abstract class ContextualSearchContext { ...@@ -99,7 +116,7 @@ public abstract class ContextualSearchContext {
/** /**
* @return The offset into the surrounding text of the start of the selection, or * @return The offset into the surrounding text of the start of the selection, or
* {@link #INVALID_SELECTION_OFFSET} if not yet established. * {@link #INVALID_OFFSET} if not yet established.
*/ */
int getSelectionStartOffset() { int getSelectionStartOffset() {
return mSelectionStartOffset; return mSelectionStartOffset;
...@@ -107,7 +124,7 @@ public abstract class ContextualSearchContext { ...@@ -107,7 +124,7 @@ public abstract class ContextualSearchContext {
/** /**
* @return The offset into the surrounding text of the end of the selection, or * @return The offset into the surrounding text of the end of the selection, or
* {@link #INVALID_SELECTION_OFFSET} if not yet established. * {@link #INVALID_OFFSET} if not yet established.
*/ */
int getSelectionEndOffset() { int getSelectionEndOffset() {
return mSelectionEndOffset; return mSelectionEndOffset;
...@@ -143,9 +160,7 @@ public abstract class ContextualSearchContext { ...@@ -143,9 +160,7 @@ public abstract class ContextualSearchContext {
* @return Whether this context can Resolve the Search Term. * @return Whether this context can Resolve the Search Term.
*/ */
boolean canResolve() { boolean canResolve() {
return mHasSetResolveProperties && mSelectionStartOffset != INVALID_SELECTION_OFFSET return mHasSetResolveProperties && hasValidSelection();
&& mSelectionEndOffset != INVALID_SELECTION_OFFSET
&& mSelectionEndOffset > mSelectionStartOffset;
} }
/** /**
...@@ -180,7 +195,138 @@ public abstract class ContextualSearchContext { ...@@ -180,7 +195,138 @@ public abstract class ContextualSearchContext {
*/ */
abstract void onSelectionChanged(); abstract void onSelectionChanged();
// TODO(donnd): Add a test for this class! // ============================================================================================
// Content Analysis.
// ============================================================================================
/**
* @return Whether this context has valid Surrounding text and initial Tap offset.
*/
private boolean hasValidTappedText() {
return !TextUtils.isEmpty(mSurroundingText) && mTapOffset >= 0
&& mTapOffset <= mSurroundingText.length();
}
/**
* @return Whether this context has a valid selection.
*/
private boolean hasValidSelection() {
if (!hasValidTappedText()) return false;
return mSelectionStartOffset != INVALID_OFFSET && mSelectionEndOffset != INVALID_OFFSET
&& mSelectionStartOffset < mSelectionEndOffset
&& mSelectionEndOffset < mSurroundingText.length();
}
/**
* @return Whether a Tap gesture has occurred and been analyzed.
*/
private boolean hasAnalyzedTap() {
return mTapOffset >= 0;
}
/**
* @return The tapped word, or {@code null} if the tapped word cannot be identified by the
* current limited parsing capability.
* @see #analyzeTap
*/
String getTappedWord() {
return mTappedWord;
}
/**
* @return The offset of the tap within the tapped word, or {@code -1} if the tapped word cannot
* be identified by the current parsing capability.
* @see #analyzeTap
*/
int getTappedWordOffset() {
return mTappedWordOffset;
}
/**
* Finds the tapped word by expanding from the initial Tap offset looking for word-breaks.
* This mimics the Blink word-segmentation invoked by SelectWordAroundCaret and similar
* selection logic, but is only appropriate for limited use. Does not work on ideographic
* languages and possibly many other cases. Should only be used only for ML signal evaluation.
* @param tapOffset The offset of the Tap within the surrounding text.
*/
private void analyzeTap(int tapOffset) {
mTapOffset = tapOffset;
mTappedWord = null;
mTappedWordOffset = INVALID_OFFSET;
assert hasValidTappedText();
int wordStartOffset = findWordStartOffset(mSurroundingText, mTapOffset);
int wordEndOffset = findWordEndOffset(mSurroundingText, mTapOffset);
if (wordStartOffset == INVALID_OFFSET || wordEndOffset == INVALID_OFFSET) return;
mTappedWord = mSurroundingText.substring(wordStartOffset, wordEndOffset);
mTappedWordOffset = mTapOffset - wordStartOffset;
}
/**
* Finds the offset of the start of the word that includes the given initial offset.
* The character at the initial offset is not examined, but the one before it is, and scanning
* continues on to earlier characters until a non-word character is found. The offset just
* before the non-word character is returned. If the initial offset is a space immediately
* following a word then the start offset of that word is returned.
* @param text The text to scan.
* @param initial The initial offset to scan before.
* @return The start of the word that contains the given initial offset, within {@code text}.
*/
private int findWordStartOffset(String text, int initial) {
// Scan before, aborting if we hit any ideographic letter.
for (int offset = initial - 1; offset >= 0; offset--) {
if (isIdeographicAtIndex(text, offset)) return INVALID_OFFSET;
if (isWordBreakAtIndex(text, offset)) {
// The start of the word is after this word break.
return offset + 1;
}
}
return INVALID_OFFSET;
}
/**
* Finds the offset of the end of the word that includes the given initial offset.
* NOTE: this is the index of the character just past the last character of the word,
* so a 3 character word "who" has start index 0 and end index 3.
* The character at the initial offset is examined and each one after that too until a non-word
* character is encountered, and that offset will be returned.
* @param text The text to scan.
* @param initial The initial offset to scan from.
* @return The end of the word that contains the given initial offset, within {@code text}.
*/
private int findWordEndOffset(String text, int initial) {
// Scan after, aborting if we hit any CJKN letter.
for (int offset = initial; offset < text.length(); offset++) {
if (isIdeographicAtIndex(text, offset)) return INVALID_OFFSET;
if (isWordBreakAtIndex(text, offset)) {
// The end of the word is the offset of this word break.
return offset;
}
}
return INVALID_OFFSET;
}
/**
* @return Whether the character at the given index in the text is "Ideographic" (as in CJKV
* languages), which means there may not be reliable word breaks.
*/
@SuppressLint("NewApi")
private boolean isIdeographicAtIndex(String text, int index) {
return Character.isIdeographic(text.charAt(index));
}
/**
* @return Whether the character at the given index is a word-break.
*/
private boolean isWordBreakAtIndex(String text, int index) {
return !Character.isLetterOrDigit(text.charAt(index))
&& text.codePointAt(index) != SOFT_HYPHEN_CHAR;
}
// ============================================================================================ // ============================================================================================
// Native callback support. // Native callback support.
......
...@@ -48,6 +48,10 @@ public class ContextualSearchFieldTrial { ...@@ -48,6 +48,10 @@ public class ContextualSearchFieldTrial {
private static final String SCREEN_TOP_SUPPRESSION_DPS = "screen_top_suppression_dps"; private static final String SCREEN_TOP_SUPPRESSION_DPS = "screen_top_suppression_dps";
private static final String ENABLE_BAR_OVERLAP_COLLECTION = "enable_bar_overlap_collection"; private static final String ENABLE_BAR_OVERLAP_COLLECTION = "enable_bar_overlap_collection";
private static final String BAR_OVERLAP_SUPPRESSION_ENABLED = "enable_bar_overlap_suppression"; private static final String BAR_OVERLAP_SUPPRESSION_ENABLED = "enable_bar_overlap_suppression";
private static final String WORD_EDGE_SUPPRESSION_ENABLED = "enable_word_edge_suppression";
private static final String SHORT_WORD_SUPPRESSION_ENABLED = "enable_short_word_suppression";
private static final String NOT_LONG_WORD_SUPPRESSION_ENABLED =
"enable_not_long_word_suppression";
private static final String MINIMUM_SELECTION_LENGTH = "minimum_selection_length"; private static final String MINIMUM_SELECTION_LENGTH = "minimum_selection_length";
...@@ -67,6 +71,7 @@ public class ContextualSearchFieldTrial { ...@@ -67,6 +71,7 @@ public class ContextualSearchFieldTrial {
"disable_page_content_notification"; "disable_page_content_notification";
// Cached values to avoid repeated and redundant JNI operations. // Cached values to avoid repeated and redundant JNI operations.
// TODO(donnd): consider creating a single Map to cache these static values.
private static Boolean sEnabled; private static Boolean sEnabled;
private static Boolean sDisableSearchTermResolution; private static Boolean sDisableSearchTermResolution;
private static Boolean sIsMandatoryPromoEnabled; private static Boolean sIsMandatoryPromoEnabled;
...@@ -78,6 +83,9 @@ public class ContextualSearchFieldTrial { ...@@ -78,6 +83,9 @@ public class ContextualSearchFieldTrial {
private static Integer sScreenTopSuppressionDps; private static Integer sScreenTopSuppressionDps;
private static Boolean sIsBarOverlapCollectionEnabled; private static Boolean sIsBarOverlapCollectionEnabled;
private static Boolean sIsBarOverlapSuppressionEnabled; private static Boolean sIsBarOverlapSuppressionEnabled;
private static Boolean sIsWordEdgeSuppressionEnabled;
private static Boolean sIsShortWordSuppressionEnabled;
private static Boolean sIsNotLongWordSuppressionEnabled;
private static Integer sMinimumSelectionLength; private static Integer sMinimumSelectionLength;
private static Boolean sIsOnlineDetectionDisabled; private static Boolean sIsOnlineDetectionDisabled;
private static Boolean sIsAmpAsSeparateTabDisabled; private static Boolean sIsAmpAsSeparateTabDisabled;
...@@ -247,6 +255,36 @@ public class ContextualSearchFieldTrial { ...@@ -247,6 +255,36 @@ public class ContextualSearchFieldTrial {
return sIsBarOverlapSuppressionEnabled.booleanValue(); return sIsBarOverlapSuppressionEnabled.booleanValue();
} }
/**
* @return Whether triggering is suppressed by a tap that's near the edge of a word.
*/
static boolean isWordEdgeSuppressionEnabled() {
if (sIsWordEdgeSuppressionEnabled == null) {
sIsWordEdgeSuppressionEnabled = getBooleanParam(WORD_EDGE_SUPPRESSION_ENABLED);
}
return sIsWordEdgeSuppressionEnabled.booleanValue();
}
/**
* @return Whether triggering is suppressed by a tap that's in a short word.
*/
static boolean isShortWordSuppressionEnabled() {
if (sIsShortWordSuppressionEnabled == null) {
sIsShortWordSuppressionEnabled = getBooleanParam(SHORT_WORD_SUPPRESSION_ENABLED);
}
return sIsShortWordSuppressionEnabled.booleanValue();
}
/**
* @return Whether triggering is suppressed by a tap that's not in a long word.
*/
static boolean isNotLongWordSuppressionEnabled() {
if (sIsNotLongWordSuppressionEnabled == null) {
sIsNotLongWordSuppressionEnabled = getBooleanParam(NOT_LONG_WORD_SUPPRESSION_ENABLED);
}
return sIsNotLongWordSuppressionEnabled.booleanValue();
}
/** /**
* @return The minimum valid selection length. * @return The minimum valid selection length.
*/ */
......
...@@ -1475,13 +1475,14 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega ...@@ -1475,13 +1475,14 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
@Override @Override
public void decideSuppression() { public void decideSuppression() {
mInternalStateController.notifyStartingWorkOn(InternalState.DECIDING_SUPPRESSION); mInternalStateController.notifyStartingWorkOn(InternalState.DECIDING_SUPPRESSION);
// Ranker will handle the suppression, but our legacy implementation uses // Ranker will handle the suppression, but our legacy implementation uses
// TapSuppressionHeuristics (run from the ContextualSearchSelectionConroller). // TapSuppressionHeuristics (run from the ContextualSearchSelectionConroller).
// Usage includes tap-far-from-previous suppression. // Usage includes tap-far-from-previous suppression.
mTapSuppressionRankerLogger.setupLoggingForPage(getBasePageUrl()); mTapSuppressionRankerLogger.setupLoggingForPage(getBasePageUrl());
// TODO(donnd): Move handleShouldSuppressTap out of the Selection Controller. // TODO(donnd): Move handleShouldSuppressTap out of the Selection Controller.
mSelectionController.handleShouldSuppressTap(mTapSuppressionRankerLogger); mSelectionController.handleShouldSuppressTap(mContext, mTapSuppressionRankerLogger);
} }
/** Starts showing the Tap UI by selecting a word around the current caret. */ /** Starts showing the Tap UI by selecting a word around the current caret. */
......
...@@ -205,7 +205,7 @@ public class ContextualSearchSelectionController { ...@@ -205,7 +205,7 @@ public class ContextualSearchSelectionController {
if (baseContentView != null) { if (baseContentView != null) {
baseContentView.clearSelection(); baseContentView.clearSelection();
} }
resetAllStates(); resetSelectionStates();
} }
/** /**
...@@ -351,10 +351,12 @@ public class ContextualSearchSelectionController { ...@@ -351,10 +351,12 @@ public class ContextualSearchSelectionController {
* or #handleNonSuppressedTap() after a possible delay. * or #handleNonSuppressedTap() after a possible delay.
* This should be called when the context is fully built (by gathering surrounding text * This should be called when the context is fully built (by gathering surrounding text
* if needed, etc) but before showing any UX. * if needed, etc) but before showing any UX.
* @param contextualSearchContext The {@link ContextualSearchContext} for the Tap gesture.
* @param rankerLogger The {@link ContextualSearchRankerLogger} currently being used to measure * @param rankerLogger The {@link ContextualSearchRankerLogger} currently being used to measure
* or suppress the UI by Ranker. * or suppress the UI by Ranker.
*/ */
void handleShouldSuppressTap(ContextualSearchRankerLogger rankerLogger) { void handleShouldSuppressTap(ContextualSearchContext contextualSearchContext,
ContextualSearchRankerLogger rankerLogger) {
int x = (int) mX; int x = (int) mX;
int y = (int) mY; int y = (int) mY;
...@@ -362,9 +364,8 @@ public class ContextualSearchSelectionController { ...@@ -362,9 +364,8 @@ public class ContextualSearchSelectionController {
ChromePreferenceManager prefs = ChromePreferenceManager.getInstance(); ChromePreferenceManager prefs = ChromePreferenceManager.getInstance();
int adjustedTapsSinceOpen = prefs.getContextualSearchTapCount() int adjustedTapsSinceOpen = prefs.getContextualSearchTapCount()
- prefs.getContextualSearchTapQuickAnswerCount(); - prefs.getContextualSearchTapQuickAnswerCount();
TapSuppressionHeuristics tapHeuristics = TapSuppressionHeuristics tapHeuristics = new TapSuppressionHeuristics(
new TapSuppressionHeuristics(this, mLastTapState, x, y, adjustedTapsSinceOpen); this, mLastTapState, x, y, adjustedTapsSinceOpen, contextualSearchContext);
// TODO(donnd): Move to be called when the panel closes to work with states that change. // TODO(donnd): Move to be called when the panel closes to work with states that change.
tapHeuristics.logConditionState(); tapHeuristics.logConditionState();
......
...@@ -802,6 +802,47 @@ public class ContextualSearchUma { ...@@ -802,6 +802,47 @@ public class ContextualSearchUma {
} }
} }
/**
* Log whether results were seen due to a Tap on a short word.
* @param wasSearchContentViewSeen If the panel was opened.
* @param isTapOnShortWord Whether this tap was on a "short" word.
*/
public static void logTapShortWordSeen(
boolean wasSearchContentViewSeen, boolean isTapOnShortWord) {
if (!isTapOnShortWord) return;
// We just record CTR of short words.
RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapShortWordSeen",
wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY);
}
/**
* Log whether results were seen due to a Tap on a long word.
* @param wasSearchContentViewSeen If the panel was opened.
* @param isTapOnLongWord Whether this tap was on a long word.
*/
public static void logTapLongWordSeen(
boolean wasSearchContentViewSeen, boolean isTapOnLongWord) {
if (!isTapOnLongWord) return;
RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapLongWordSeen",
wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY);
}
/**
* Log whether results were seen due to a Tap that was on the middle of a word.
* @param wasSearchContentViewSeen If the panel was opened.
* @param isTapOnWordMiddle Whether this tap was on the middle of a word.
*/
public static void logTapOnWordMiddleSeen(
boolean wasSearchContentViewSeen, boolean isTapOnWordMiddle) {
if (!isTapOnWordMiddle) return;
// We just record CTR of words tapped in the "middle".
RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapOnWordMiddleSeen",
wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY);
}
/** /**
* Logs whether results were seen and whether any tap suppression heuristics were satisfied. * Logs whether results were seen and whether any tap suppression heuristics were satisfied.
* @param wasSearchContentViewSeen If the panel was opened. * @param wasSearchContentViewSeen If the panel was opened.
......
...@@ -19,22 +19,19 @@ public class TapSuppressionHeuristics extends ContextualSearchHeuristics { ...@@ -19,22 +19,19 @@ public class TapSuppressionHeuristics extends ContextualSearchHeuristics {
* @param tapsSinceOpen the number of Tap gestures since the last open of the panel. * @param tapsSinceOpen the number of Tap gestures since the last open of the panel.
*/ */
TapSuppressionHeuristics(ContextualSearchSelectionController selectionController, TapSuppressionHeuristics(ContextualSearchSelectionController selectionController,
ContextualSearchTapState previousTapState, int x, int y, int tapsSinceOpen) { ContextualSearchTapState previousTapState, int x, int y, int tapsSinceOpen,
ContextualSearchContext contextualSearchContext) {
super(); super();
mCtrSuppression = new CtrSuppression(); mCtrSuppression = new CtrSuppression();
mHeuristics.add(mCtrSuppression); mHeuristics.add(mCtrSuppression);
RecentScrollTapSuppression scrollTapExperiment = mHeuristics.add(new RecentScrollTapSuppression(selectionController));
new RecentScrollTapSuppression(selectionController);
mHeuristics.add(scrollTapExperiment);
TapFarFromPreviousSuppression farFromPreviousHeuristic = TapFarFromPreviousSuppression farFromPreviousHeuristic =
new TapFarFromPreviousSuppression(selectionController, previousTapState, x, y); new TapFarFromPreviousSuppression(selectionController, previousTapState, x, y);
mHeuristics.add(farFromPreviousHeuristic); mHeuristics.add(farFromPreviousHeuristic);
NearTopTapSuppression tapNearTopSuppression = mHeuristics.add(new TapWordLengthSuppression(contextualSearchContext));
new NearTopTapSuppression(selectionController, y); mHeuristics.add(new TapWordEdgeSuppression(contextualSearchContext));
mHeuristics.add(tapNearTopSuppression); mHeuristics.add(new NearTopTapSuppression(selectionController, y));
BarOverlapTapSuppression barOverlapTapSuppression = mHeuristics.add(new BarOverlapTapSuppression(selectionController, y));
new BarOverlapTapSuppression(selectionController, y);
mHeuristics.add(barOverlapTapSuppression);
// General Tap Suppression and Tap Twice. // General Tap Suppression and Tap Twice.
TapSuppression tapSuppression = TapSuppression tapSuppression =
new TapSuppression(selectionController, previousTapState, x, y, tapsSinceOpen); new TapSuppression(selectionController, previousTapState, x, y, tapsSinceOpen);
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextualsearch;
import android.text.TextUtils;
/**
* Implements the policy that a Tap relatively far away from the middle of a word should be
* ignored. When a Tap is close to the middle of the word tapped it's treated normally.
*/
class TapWordEdgeSuppression extends ContextualSearchHeuristic {
private static final int INVALID_OFFSET = ContextualSearchContext.INVALID_OFFSET;
private static final int MIN_WORD_LENGTH = 4;
private static final double MIN_WORD_START_RATIO = 0.25;
private static final double MIN_WORD_END_RATIO = 0.25;
private final boolean mIsSuppressionEnabled;
private final boolean mIsConditionSatisfied;
/**
* Constructs a heuristic to determine if the current Tap is close to the edge of the word.
* @param contextualSearchContext The current {@link ContextualSearchContext} so we can figure
* out what part of the word has been tapped.
*/
TapWordEdgeSuppression(ContextualSearchContext contextualSearchContext) {
mIsSuppressionEnabled = ContextualSearchFieldTrial.isWordEdgeSuppressionEnabled();
mIsConditionSatisfied = isTapNearWordEdge(contextualSearchContext);
}
@Override
protected boolean isConditionSatisfiedAndEnabled() {
return mIsSuppressionEnabled && mIsConditionSatisfied;
}
@Override
protected void logResultsSeen(boolean wasSearchContentViewSeen, boolean wasActivatedByTap) {
if (wasActivatedByTap) {
ContextualSearchUma.logTapOnWordMiddleSeen(
wasSearchContentViewSeen, !mIsConditionSatisfied);
}
}
@Override
protected boolean shouldAggregateLogForTapSuppression() {
return true;
}
@Override
protected boolean isConditionSatisfiedForAggregateLogging() {
return mIsConditionSatisfied;
}
/**
* Whether the tap is near the word edge and not a second-tap (tap following a suppressed tap).
* @param contextualSearchContext The {@link ContextualSearchContext}, used to determine how
* close the tap offset is to the word edges.
* @return Whether the tap is near an edge and not a second tap.
*/
private boolean isTapNearWordEdge(ContextualSearchContext contextualSearchContext) {
// If setup failed, don't suppress.
String tappedWord = contextualSearchContext.getTappedWord();
int tapOffset = contextualSearchContext.getTappedWordOffset();
if (TextUtils.isEmpty(tappedWord) || tapOffset == INVALID_OFFSET) return false;
// If the word is long enough, suppress if the tap was near one end or the other.
boolean isInStartEdge = (double) tapOffset / tappedWord.length() < MIN_WORD_START_RATIO;
boolean isInEndEdge = (double) (tappedWord.length() - tapOffset) / tappedWord.length()
< MIN_WORD_END_RATIO;
if (tappedWord.length() >= MIN_WORD_LENGTH && (isInStartEdge || isInEndEdge)) return true;
return false;
}
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextualsearch;
import android.text.TextUtils;
/**
* Implements signals for Taps on short and long words.
* This signal could be used for suppression when the word is short, so we aggregate-log too.
* We log CTR to UMA for Taps on both short and long words.
*/
class TapWordLengthSuppression extends ContextualSearchHeuristic {
private static final int MAXIMUM_SHORT_WORD_LENGTH = 3;
private static final int MAXIMUM_NOT_LONG_WORD_LENGTH = 9;
private final boolean mIsShortWordSuppressionEnabled;
private final boolean mIsNotLongWordSuppressionEnabled;
private final boolean mIsShortWordConditionSatisfied;
private final boolean mIsNotLongWordConditionSatisfied;
/**
* Constructs a heuristic to categorize the Tap based on word length of the tapped word.
* @param contextualSearchContext The current {@link ContextualSearchContext} so we can inspect
* the word tapped.
*/
TapWordLengthSuppression(ContextualSearchContext contextualSearchContext) {
mIsShortWordSuppressionEnabled = ContextualSearchFieldTrial.isShortWordSuppressionEnabled();
mIsNotLongWordSuppressionEnabled =
ContextualSearchFieldTrial.isNotLongWordSuppressionEnabled();
mIsShortWordConditionSatisfied =
!isTapOnWordLongerThan(MAXIMUM_SHORT_WORD_LENGTH, contextualSearchContext);
mIsNotLongWordConditionSatisfied =
!isTapOnWordLongerThan(MAXIMUM_NOT_LONG_WORD_LENGTH, contextualSearchContext);
}
@Override
protected boolean isConditionSatisfiedAndEnabled() {
return mIsShortWordSuppressionEnabled && mIsShortWordConditionSatisfied
|| mIsNotLongWordSuppressionEnabled && mIsNotLongWordConditionSatisfied;
}
@Override
protected void logResultsSeen(boolean wasSearchContentViewSeen, boolean wasActivatedByTap) {
if (wasActivatedByTap) {
ContextualSearchUma.logTapShortWordSeen(
wasSearchContentViewSeen, mIsShortWordConditionSatisfied);
// Log CTR of long words, since not-long word CTR is probably not useful.
ContextualSearchUma.logTapLongWordSeen(
wasSearchContentViewSeen, !mIsNotLongWordConditionSatisfied);
}
}
@Override
protected boolean shouldAggregateLogForTapSuppression() {
return true;
}
@Override
protected boolean isConditionSatisfiedForAggregateLogging() {
// Short-word suppression is a good candidate for aggregate logging of overall suppression.
return mIsShortWordConditionSatisfied;
}
/**
* @return Whether the tap is on a word longer than the given word length maximum.
*/
private boolean isTapOnWordLongerThan(
int maxWordLength, ContextualSearchContext contextualSearchContext) {
// If setup failed, don't suppress.
String tappedWord = contextualSearchContext.getTappedWord();
return !TextUtils.isEmpty(tappedWord) && tappedWord.length() > maxWordLength;
}
}
...@@ -262,6 +262,8 @@ chrome_java_sources = [ ...@@ -262,6 +262,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/contextualsearch/TapFarFromPreviousSuppression.java", "java/src/org/chromium/chrome/browser/contextualsearch/TapFarFromPreviousSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapSuppression.java", "java/src/org/chromium/chrome/browser/contextualsearch/TapSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java", "java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java",
"java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java", "java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java",
"java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java", "java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
"java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobService.java", "java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobService.java",
......
...@@ -66297,6 +66297,26 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. ...@@ -66297,6 +66297,26 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary> </summary>
</histogram> </histogram>
<histogram name="Search.ContextualSearchTapLongWordSeen"
enum="ContextualSearchResultsSeen">
<owner>donnd@chromium.org</owner>
<owner>twellington@chromium.org</owner>
<summary>
Whether results were seen for a Tap that was on a word considered long.
Recorded when the UX is hidden. Implemented for Android.
</summary>
</histogram>
<histogram name="Search.ContextualSearchTapShortWordSeen"
enum="ContextualSearchResultsSeen">
<owner>donnd@chromium.org</owner>
<owner>twellington@chromium.org</owner>
<summary>
Whether results were seen for a Tap that was on a word considered short.
Recorded when the UX is hidden. Implemented for Android.
</summary>
</histogram>
<histogram name="Search.ContextualSearchTapsSinceOpenDecided" units="taps"> <histogram name="Search.ContextualSearchTapsSinceOpenDecided" units="taps">
<owner>donnd@chromium.org</owner> <owner>donnd@chromium.org</owner>
<owner>twellington@chromium.org</owner> <owner>twellington@chromium.org</owner>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment