blob: 44061ca06a1e42f044d8a8ec96c04405a68e27b7 [file] [log] [blame]
// Copyright 2015 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;
import androidx.annotation.IntDef;
import org.chromium.base.CommandLine;
import org.chromium.base.SysUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.components.variations.VariationsAssociatedData;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Provides Field Trial support for the Contextual Search application within Chrome for Android.
*/
public class ContextualSearchFieldTrial {
//==========================================================================================
// Public settings synchronized with src/components/contextual_search/core/browser/public.cc
//==========================================================================================
public static final String LONGPRESS_RESOLVE_PARAM_NAME = "longpress_resolve_variation";
public static final String LONGPRESS_RESOLVE_PRESERVE_TAP = "3";
//==========================================================================================
private static final String FIELD_TRIAL_NAME = "ContextualSearch";
private static final String DISABLED_PARAM = "disabled";
private static final String ENABLED_VALUE = "true";
private static final int MANDATORY_PROMO_DEFAULT_LIMIT = 10;
// Cached values to avoid repeated and redundant JNI operations.
private static Boolean sEnabled;
private static Boolean[] sSwitches = new Boolean[ContextualSearchSwitch.NUM_ENTRIES];
private static Integer[] sSettings = new Integer[ContextualSearchSetting.NUM_ENTRIES];
// SWITCHES
// TODO(donnd): remove all supporting code once short-lived data collection is done.
@IntDef({ContextualSearchSwitch.IS_TRANSLATION_DISABLED,
ContextualSearchSwitch.IS_ONLINE_DETECTION_DISABLED,
ContextualSearchSwitch.IS_SEARCH_TERM_RESOLUTION_DISABLED,
ContextualSearchSwitch.IS_MANDATORY_PROMO_ENABLED,
ContextualSearchSwitch.IS_ENGLISH_TARGET_TRANSLATION_ENABLED,
ContextualSearchSwitch.IS_BAR_OVERLAP_COLLECTION_ENABLED,
ContextualSearchSwitch.IS_BAR_OVERLAP_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_WORD_EDGE_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_SHORT_WORD_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_NOT_LONG_WORD_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_ENGAGEMENT_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_SMALL_TEXT_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_AMP_AS_SEPARATE_TAB_DISABLED,
ContextualSearchSwitch.IS_SEND_HOME_COUNTRY_DISABLED,
ContextualSearchSwitch.IS_PAGE_CONTENT_NOTIFICATION_DISABLED,
ContextualSearchSwitch.IS_UKM_RANKER_LOGGING_DISABLED,
ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED,
ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_SECOND_TAP_ML_OVERRIDE_ENABLED,
ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE_ENABLED})
@Retention(RetentionPolicy.SOURCE)
/**
* Boolean Switch values that are backed by either a Feature or a Variations parameter.
* Values are used for indexing ContextualSearchSwitchNames - should start from 0 and can't
* have gaps.
*/
@interface ContextualSearchSwitch {
/**
* Whether all translate code is disabled (master switch, needed to disable all translate
* code for Contextual Search in case of an emergency).
*/
int IS_TRANSLATION_DISABLED = 0;
/**
* Whether detection of device-online should be disabled (default false).
* (safety switch for disabling online-detection also used to disable detection when
* running tests).
*/
// TODO(donnd): Convert to test-only after launch and we have confidence it's robust.
int IS_ONLINE_DETECTION_DISABLED = 1;
int IS_SEARCH_TERM_RESOLUTION_DISABLED = 2;
int IS_MANDATORY_PROMO_ENABLED = 3;
/**
* Whether English-target translation should be enabled (default is disabled for 'en').
* Enables usage of English as the target language even when it's the primary UI language.
*/
int IS_ENGLISH_TARGET_TRANSLATION_ENABLED = 4;
/** Whether collecting data on Bar overlap is enabled. */
int IS_BAR_OVERLAP_COLLECTION_ENABLED = 5;
/**
* Whether triggering is suppressed by a selection nearly overlapping the normal
* Bar peeking location.
*/
int IS_BAR_OVERLAP_SUPPRESSION_ENABLED = 6;
/** Whether triggering is suppressed by a tap that's near the edge of a word. */
int IS_WORD_EDGE_SUPPRESSION_ENABLED = 7;
/** Whether triggering is suppressed by a tap that's in a short word. */
int IS_SHORT_WORD_SUPPRESSION_ENABLED = 8;
/** Whether triggering is suppressed by a tap that's not in a long word. */
int IS_NOT_LONG_WORD_SUPPRESSION_ENABLED = 9;
/** Whether triggering is suppressed for a tap that's not on an entity. */
int IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED = 10;
/** Whether triggering is suppressed due to lack of engagement with the feature. */
int IS_ENGAGEMENT_SUPPRESSION_ENABLED = 11;
/** Whether triggering is suppressed for a tap that has a short element run-length. */
int IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED = 12;
/** Whether triggering is suppressed for a tap on small-looking text. */
int IS_SMALL_TEXT_SUPPRESSION_ENABLED = 13;
/**
* Whether to disable auto-promotion of clicks in the AMP carousel into a
* separate Tab.
*/
int IS_AMP_AS_SEPARATE_TAB_DISABLED = 14;
/** Whether sending the "home country" to Google is disabled. */
int IS_SEND_HOME_COUNTRY_DISABLED = 15;
/**
* Whether sending the page content notifications to observers (e.g. icing for
* conversational search) is disabled.
*/
int IS_PAGE_CONTENT_NOTIFICATION_DISABLED = 16;
/** Whether logging for Machine Learning is disabled. */
int IS_UKM_RANKER_LOGGING_DISABLED = 17;
/** Whether or not ML-based Tap suppression is enabled. */
int IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED = 18;
/** Whether or not to override an ML-based Tap suppression on a second tap. */
int IS_CONTEXTUAL_SEARCH_SECOND_TAP_ML_OVERRIDE_ENABLED = 19;
/**
* Whether or not to override tap-disable for users that have never opened the
* panel.
*/
int IS_CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE_ENABLED = 20;
int NUM_ENTRIES = 21;
}
@VisibleForTesting
static final String ONLINE_DETECTION_DISABLED = "disable_online_detection";
@VisibleForTesting
static final String TRANSLATION_DISABLED = "disable_translation";
// Indexed by ContextualSearchSwitch
private static final String[] ContextualSearchSwitchNames = {
TRANSLATION_DISABLED, // IS_TRANSLATION_DISABLED
ONLINE_DETECTION_DISABLED, // IS_ONLINE_DETECTION_DISABLED
"disable_search_term_resolution", // DISABLE_SEARCH_TERM_RESOLUTION
"mandatory_promo_enabled", // IS_MANDATORY_PROMO_ENABLED
"enable_english_target_translation", // IS_ENGLISH_TARGET_TRANSLATION_ENABLED
"enable_bar_overlap_collection", // IS_BAR_OVERLAP_COLLECTION_ENABLED
"enable_bar_overlap_suppression", // IS_BAR_OVERLAP_SUPPRESSION_ENABLED
"enable_word_edge_suppression", // IS_WORD_EDGE_SUPPRESSION_ENABLED
"enable_short_word_suppression", // IS_SHORT_WORD_SUPPRESSION_ENABLED
"enable_not_long_word_suppression", // IS_NOT_LONG_WORD_SUPPRESSION_ENABLED
"enable_not_an_entity_suppression", // IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED
"enable_engagement_suppression", // IS_ENGAGEMENT_SUPPRESSION_ENABLED
"enable_short_text_run_suppression", // IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED
"enable_small_text_suppression", // IS_SMALL_TEXT_SUPPRESSION_ENABLED
"disable_amp_as_separate_tab", // IS_AMP_AS_SEPARATE_TAB_DISABLED
"disable_send_home_country", // IS_SEND_HOME_COUNTRY_DISABLED
"disable_page_content_notification", // IS_PAGE_CONTENT_NOTIFICATION_DISABLED
"disable_ukm_ranker_logging", // IS_UKM_RANKER_LOGGING_DISABLED
ChromeFeatureList.CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION, // (related to Chrome Feature)
ChromeFeatureList.CONTEXTUAL_SEARCH_SECOND_TAP, // (related to Chrome Feature)
ChromeFeatureList.CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE // (related to Chrome Feature)
};
@IntDef({ContextualSearchSetting.MANDATORY_PROMO_LIMIT,
ContextualSearchSetting.SCREEN_TOP_SUPPRESSION_DPS,
ContextualSearchSetting.MINIMUM_SELECTION_LENGTH,
ContextualSearchSetting.WAIT_AFTER_TAP_DELAY_MS,
ContextualSearchSetting.TAP_DURATION_THRESHOLD_MS,
ContextualSearchSetting.RECENT_SCROLL_DURATION_MS})
@Retention(RetentionPolicy.SOURCE)
/**
* These are integer Setting values that are backed by a Variation Param.
* Values are used for indexing ContextualSearchSwitchStrings - should start from 0 and can't
* have gaps.
*/
@interface ContextualSearchSetting {
/** The number of times the Promo should be seen before it becomes mandatory. */
int MANDATORY_PROMO_LIMIT = 0;
/**
* A Y value limit that will suppress a Tap near the top of the screen.
* (any Y value less than the limit will suppress the Tap trigger).
*/
int SCREEN_TOP_SUPPRESSION_DPS = 1;
/** The minimum valid selection length. */
int MINIMUM_SELECTION_LENGTH = 2;
/**
* An amount to delay after a Tap gesture is recognized, in case some user gesture
* immediately follows that would prevent the UI from showing.
* The classic example is a scroll, which might be a signal that the previous tap was
* accidental.
*/
int WAIT_AFTER_TAP_DELAY_MS = 3;
/**
* A threshold for the duration of a tap gesture for categorization as brief or
* lengthy (the maximum amount of time in milliseconds for a tap gesture that's still
* considered a very brief duration tap).
*/
int TAP_DURATION_THRESHOLD_MS = 4;
/**
* The duration to use for suppressing Taps after a recent scroll, or {@code 0} if no
* suppression is configured (the period of time after a scroll when tap triggering is
* suppressed).
*/
int RECENT_SCROLL_DURATION_MS = 5;
int NUM_ENTRIES = 6;
}
// Indexed by ContextualSearchSetting
private static final String[] ContextualSearchSettingNames = {
"mandatory_promo_limit", // MANDATORY_PROMO_LIMIT
"screen_top_suppression_dps", // SCREEN_TOP_SUPPRESSION_DPS
"minimum_selection_length", // MINIMUM_SELECTION_LENGTH
"wait_after_tap_delay_ms", // WAIT_AFTER_TAP_DELAY_MS
"tap_duration_threshold_ms", // TAP_DURATION_THRESHOLD_MS
"recent_scroll_duration_ms" // RECENT_SCROLL_DURATION_MS
};
private ContextualSearchFieldTrial() {
assert ContextualSearchSwitchNames.length == ContextualSearchSwitch.NUM_ENTRIES;
assert ContextualSearchSettingNames.length == ContextualSearchSetting.NUM_ENTRIES;
}
/**
* Current Variations parameters associated with the ContextualSearch Field Trial or a
* Chrome Feature to determine if the service is enabled
* (whether Contextual Search is enabled or not).
*/
public static boolean isEnabled() {
if (sEnabled == null) sEnabled = detectEnabled();
return sEnabled.booleanValue();
}
static boolean getSwitch(@ContextualSearchSwitch int value) {
if (sSwitches[value] == null) {
switch (value) {
case ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED:
case ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_SECOND_TAP_ML_OVERRIDE_ENABLED:
case ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE_ENABLED:
sSwitches[value] =
ChromeFeatureList.isEnabled(ContextualSearchSwitchNames[value]);
break;
default:
assert !TextUtils.isEmpty(ContextualSearchSwitchNames[value]);
sSwitches[value] = getBooleanParam(ContextualSearchSwitchNames[value]);
}
}
return sSwitches[value].booleanValue();
}
static int getValue(@ContextualSearchSetting int value) {
if (sSettings[value] == null) {
sSettings[value] = getIntParamValueOrDefault(ContextualSearchSettingNames[value],
value == ContextualSearchSetting.MANDATORY_PROMO_LIMIT
? MANDATORY_PROMO_DEFAULT_LIMIT
: 0);
}
return sSettings[value].intValue();
}
// --------------------------------------------------------------------------------------------
// Helpers.
// --------------------------------------------------------------------------------------------
private static boolean detectEnabled() {
if (SysUtils.isLowEndDevice()) return false;
// Allow this user-flippable flag to disable the feature.
if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_CONTEXTUAL_SEARCH)) {
return false;
}
// Allow this user-flippable flag to enable the feature.
if (CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_CONTEXTUAL_SEARCH)) {
return true;
}
// Allow disabling the feature remotely.
if (getBooleanParam(DISABLED_PARAM)) return false;
return true;
}
/**
* Gets a boolean Finch parameter, assuming the <paramName>="true" format. Also checks for
* a command-line switch with the same name, for easy local testing.
* @param paramName The name of the Finch parameter (or command-line switch) to get a value
* for.
* @return Whether the Finch param is defined with a value "true", if there's a command-line
* flag present with any value.
*/
private static boolean getBooleanParam(String paramName) {
if (CommandLine.getInstance().hasSwitch(paramName)) {
return true;
}
return TextUtils.equals(ENABLED_VALUE,
VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME, paramName));
}
/**
* Returns an integer value for a Finch parameter, or the default value if no parameter
* exists in the current configuration. Also checks for a command-line switch with the same
* name.
* @param paramName The name of the Finch parameter (or command-line switch) to get a value
* for.
* @param defaultValue The default value to return when there's no param or switch.
* @return An integer value -- either the param or the default.
*/
private static int getIntParamValueOrDefault(String paramName, int defaultValue) {
String value = CommandLine.getInstance().getSwitchValue(paramName);
if (TextUtils.isEmpty(value)) {
value = VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME, paramName);
}
if (!TextUtils.isEmpty(value)) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
return defaultValue;
}
}