1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.contextualsearch; 6 7 import android.text.TextUtils; 8 9 import androidx.annotation.IntDef; 10 import androidx.annotation.VisibleForTesting; 11 12 import org.chromium.base.CommandLine; 13 import org.chromium.base.SysUtils; 14 import org.chromium.chrome.browser.flags.ChromeFeatureList; 15 import org.chromium.chrome.browser.flags.ChromeSwitches; 16 import org.chromium.components.variations.VariationsAssociatedData; 17 18 import java.lang.annotation.Retention; 19 import java.lang.annotation.RetentionPolicy; 20 21 /** 22 * Provides Field Trial support for the Contextual Search application within Chrome for Android. 23 */ 24 public class ContextualSearchFieldTrial { 25 private static final String FIELD_TRIAL_NAME = "ContextualSearch"; 26 private static final String DISABLED_PARAM = "disabled"; 27 private static final String ENABLED_VALUE = "true"; 28 29 //========================================================================================== 30 // Related Searches FieldTrial and parameter names. 31 //========================================================================================== 32 // Params used elsewhere but gathered here since they may be present in FieldTrial configs. 33 static final String RELATED_SEARCHES_NEEDS_URL_PARAM_NAME = "needs_url"; 34 static final String RELATED_SEARCHES_NEEDS_CONTENT_PARAM_NAME = "needs_content"; 35 // A comma-separated list of lower-case ISO 639 language codes. 36 static final String RELATED_SEARCHES_LANGUAGE_ALLOWLIST_PARAM_NAME = "language_allowlist"; 37 private static final String RELATED_SEARCHES_CONFIG_STAMP_PARAM_NAME = "stamp"; 38 39 // Deprecated. 40 private static final int MANDATORY_PROMO_DEFAULT_LIMIT = 10; 41 42 // Cached values to avoid repeated and redundant JNI operations. 43 private static Boolean sEnabled; 44 private static Boolean[] sSwitches = new Boolean[ContextualSearchSwitch.NUM_ENTRIES]; 45 private static Integer[] sSettings = new Integer[ContextualSearchSetting.NUM_ENTRIES]; 46 47 // SWITCHES 48 // TODO(donnd): remove all supporting code once short-lived data collection is done. 49 @IntDef({ContextualSearchSwitch.IS_TRANSLATION_DISABLED, 50 ContextualSearchSwitch.IS_ONLINE_DETECTION_DISABLED, 51 ContextualSearchSwitch.IS_SEARCH_TERM_RESOLUTION_DISABLED, 52 ContextualSearchSwitch.IS_MANDATORY_PROMO_ENABLED, 53 ContextualSearchSwitch.IS_ENGLISH_TARGET_TRANSLATION_ENABLED, 54 ContextualSearchSwitch.IS_BAR_OVERLAP_COLLECTION_ENABLED, 55 ContextualSearchSwitch.IS_BAR_OVERLAP_SUPPRESSION_ENABLED, 56 ContextualSearchSwitch.IS_WORD_EDGE_SUPPRESSION_ENABLED, 57 ContextualSearchSwitch.IS_SHORT_WORD_SUPPRESSION_ENABLED, 58 ContextualSearchSwitch.IS_NOT_LONG_WORD_SUPPRESSION_ENABLED, 59 ContextualSearchSwitch.IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED, 60 ContextualSearchSwitch.IS_ENGAGEMENT_SUPPRESSION_ENABLED, 61 ContextualSearchSwitch.IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED, 62 ContextualSearchSwitch.IS_SMALL_TEXT_SUPPRESSION_ENABLED, 63 ContextualSearchSwitch.IS_AMP_AS_SEPARATE_TAB_DISABLED, 64 ContextualSearchSwitch.IS_SEND_HOME_COUNTRY_DISABLED, 65 ContextualSearchSwitch.IS_PAGE_CONTENT_NOTIFICATION_DISABLED, 66 ContextualSearchSwitch.IS_UKM_RANKER_LOGGING_DISABLED, 67 ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED, 68 ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_SECOND_TAP_ML_OVERRIDE_ENABLED, 69 ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE_ENABLED, 70 ContextualSearchSwitch.IS_SEND_BASE_PAGE_URL_DISABLED}) 71 @Retention(RetentionPolicy.SOURCE) 72 /** 73 * Boolean Switch values that are backed by either a Feature or a Variations parameter. 74 * Values are used for indexing ContextualSearchSwitchNames - should start from 0 and can't 75 * have gaps. 76 */ 77 @interface ContextualSearchSwitch { 78 /** 79 * @deprecated 80 * Whether all translate code is disabled (master switch, needed to disable all translate 81 * code for Contextual Search in case of an emergency). 82 */ 83 int IS_TRANSLATION_DISABLED = 0; 84 /** 85 * Whether detection of device-online should be disabled (default false). 86 * (safety switch for disabling online-detection also used to disable detection when 87 * running tests). 88 */ 89 // TODO(donnd): Convert to test-only after launch and we have confidence it's robust. 90 int IS_ONLINE_DETECTION_DISABLED = 1; 91 92 int IS_SEARCH_TERM_RESOLUTION_DISABLED = 2; 93 int IS_MANDATORY_PROMO_ENABLED = 3; 94 95 /** 96 * Whether English-target translation should be enabled (default is disabled for 'en'). 97 * Enables usage of English as the target language even when it's the primary UI language. 98 */ 99 int IS_ENGLISH_TARGET_TRANSLATION_ENABLED = 4; 100 /** Whether collecting data on Bar overlap is enabled. */ 101 int IS_BAR_OVERLAP_COLLECTION_ENABLED = 5; 102 /** 103 * Whether triggering is suppressed by a selection nearly overlapping the normal 104 * Bar peeking location. 105 */ 106 int IS_BAR_OVERLAP_SUPPRESSION_ENABLED = 6; 107 /** Whether triggering is suppressed by a tap that's near the edge of a word. */ 108 int IS_WORD_EDGE_SUPPRESSION_ENABLED = 7; 109 /** Whether triggering is suppressed by a tap that's in a short word. */ 110 int IS_SHORT_WORD_SUPPRESSION_ENABLED = 8; 111 /** Whether triggering is suppressed by a tap that's not in a long word. */ 112 int IS_NOT_LONG_WORD_SUPPRESSION_ENABLED = 9; 113 /** Whether triggering is suppressed for a tap that's not on an entity. */ 114 int IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED = 10; 115 /** Whether triggering is suppressed due to lack of engagement with the feature. */ 116 int IS_ENGAGEMENT_SUPPRESSION_ENABLED = 11; 117 /** Whether triggering is suppressed for a tap that has a short element run-length. */ 118 int IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED = 12; 119 /** Whether triggering is suppressed for a tap on small-looking text. */ 120 int IS_SMALL_TEXT_SUPPRESSION_ENABLED = 13; 121 /** 122 * Whether to disable auto-promotion of clicks in the AMP carousel into a 123 * separate Tab. 124 */ 125 int IS_AMP_AS_SEPARATE_TAB_DISABLED = 14; 126 /** Whether sending the "home country" to Google is disabled. */ 127 int IS_SEND_HOME_COUNTRY_DISABLED = 15; 128 /** 129 * Whether sending the page content notifications to observers (e.g. icing for 130 * conversational search) is disabled. 131 */ 132 int IS_PAGE_CONTENT_NOTIFICATION_DISABLED = 16; 133 /** Whether logging for Machine Learning is disabled. */ 134 int IS_UKM_RANKER_LOGGING_DISABLED = 17; 135 /** Whether or not ML-based Tap suppression is enabled. */ 136 int IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED = 18; 137 /** Whether or not to override an ML-based Tap suppression on a second tap. */ 138 int IS_CONTEXTUAL_SEARCH_SECOND_TAP_ML_OVERRIDE_ENABLED = 19; 139 /** 140 * Whether or not to override tap-disable for users that have never opened the 141 * panel. 142 */ 143 int IS_CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE_ENABLED = 20; 144 /** Whether sending the URL of the page viewed by the user is disabled. */ 145 int IS_SEND_BASE_PAGE_URL_DISABLED = 21; 146 147 int NUM_ENTRIES = 22; 148 } 149 150 @VisibleForTesting 151 static final String ONLINE_DETECTION_DISABLED = "disable_online_detection"; 152 @VisibleForTesting 153 static final String TRANSLATION_DISABLED = "disable_translation"; 154 155 // Indexed by ContextualSearchSwitch 156 private static final String[] ContextualSearchSwitchNames = { 157 TRANSLATION_DISABLED, // IS_TRANSLATION_DISABLED 158 ONLINE_DETECTION_DISABLED, // IS_ONLINE_DETECTION_DISABLED 159 "disable_search_term_resolution", // DISABLE_SEARCH_TERM_RESOLUTION 160 "mandatory_promo_enabled", // IS_MANDATORY_PROMO_ENABLED 161 "enable_english_target_translation", // IS_ENGLISH_TARGET_TRANSLATION_ENABLED 162 "enable_bar_overlap_collection", // IS_BAR_OVERLAP_COLLECTION_ENABLED 163 "enable_bar_overlap_suppression", // IS_BAR_OVERLAP_SUPPRESSION_ENABLED 164 "enable_word_edge_suppression", // IS_WORD_EDGE_SUPPRESSION_ENABLED 165 "enable_short_word_suppression", // IS_SHORT_WORD_SUPPRESSION_ENABLED 166 "enable_not_long_word_suppression", // IS_NOT_LONG_WORD_SUPPRESSION_ENABLED 167 "enable_not_an_entity_suppression", // IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED 168 "enable_engagement_suppression", // IS_ENGAGEMENT_SUPPRESSION_ENABLED 169 "enable_short_text_run_suppression", // IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED 170 "enable_small_text_suppression", // IS_SMALL_TEXT_SUPPRESSION_ENABLED 171 "disable_amp_as_separate_tab", // IS_AMP_AS_SEPARATE_TAB_DISABLED 172 "disable_send_home_country", // IS_SEND_HOME_COUNTRY_DISABLED 173 "disable_page_content_notification", // IS_PAGE_CONTENT_NOTIFICATION_DISABLED 174 "disable_ukm_ranker_logging", // IS_UKM_RANKER_LOGGING_DISABLED 175 ChromeFeatureList.CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION, // (related to Chrome Feature) 176 ChromeFeatureList.CONTEXTUAL_SEARCH_SECOND_TAP, // (related to Chrome Feature) 177 ChromeFeatureList.CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE, // (related to Chrome Feature) 178 "disable_send_url" // IS_SEND_BASE_PAGE_URL_DISABLED 179 }; 180 181 @IntDef({ContextualSearchSetting.MANDATORY_PROMO_LIMIT, 182 ContextualSearchSetting.SCREEN_TOP_SUPPRESSION_DPS, 183 ContextualSearchSetting.MINIMUM_SELECTION_LENGTH, 184 ContextualSearchSetting.WAIT_AFTER_TAP_DELAY_MS, 185 ContextualSearchSetting.TAP_DURATION_THRESHOLD_MS, 186 ContextualSearchSetting.RECENT_SCROLL_DURATION_MS}) 187 @Retention(RetentionPolicy.SOURCE) 188 /** 189 * These are integer Setting values that are backed by a Variation Param. 190 * Values are used for indexing ContextualSearchSwitchStrings - should start from 0 and can't 191 * have gaps. 192 */ 193 @interface ContextualSearchSetting { 194 /** The number of times the Promo should be seen before it becomes mandatory. */ 195 int MANDATORY_PROMO_LIMIT = 0; 196 /** 197 * A Y value limit that will suppress a Tap near the top of the screen. 198 * (any Y value less than the limit will suppress the Tap trigger). 199 */ 200 int SCREEN_TOP_SUPPRESSION_DPS = 1; 201 /** The minimum valid selection length. */ 202 int MINIMUM_SELECTION_LENGTH = 2; 203 /** 204 * An amount to delay after a Tap gesture is recognized, in case some user gesture 205 * immediately follows that would prevent the UI from showing. 206 * The classic example is a scroll, which might be a signal that the previous tap was 207 * accidental. 208 */ 209 int WAIT_AFTER_TAP_DELAY_MS = 3; 210 /** 211 * A threshold for the duration of a tap gesture for categorization as brief or 212 * lengthy (the maximum amount of time in milliseconds for a tap gesture that's still 213 * considered a very brief duration tap). 214 */ 215 int TAP_DURATION_THRESHOLD_MS = 4; 216 /** 217 * The duration to use for suppressing Taps after a recent scroll, or {@code 0} if no 218 * suppression is configured (the period of time after a scroll when tap triggering is 219 * suppressed). 220 */ 221 int RECENT_SCROLL_DURATION_MS = 5; 222 223 int NUM_ENTRIES = 6; 224 } 225 226 // Indexed by ContextualSearchSetting 227 private static final String[] ContextualSearchSettingNames = { 228 "mandatory_promo_limit", // MANDATORY_PROMO_LIMIT 229 "screen_top_suppression_dps", // SCREEN_TOP_SUPPRESSION_DPS 230 "minimum_selection_length", // MINIMUM_SELECTION_LENGTH 231 "wait_after_tap_delay_ms", // WAIT_AFTER_TAP_DELAY_MS 232 "tap_duration_threshold_ms", // TAP_DURATION_THRESHOLD_MS 233 "recent_scroll_duration_ms" // RECENT_SCROLL_DURATION_MS 234 }; 235 ContextualSearchFieldTrial()236 private ContextualSearchFieldTrial() { 237 assert ContextualSearchSwitchNames.length == ContextualSearchSwitch.NUM_ENTRIES; 238 assert ContextualSearchSettingNames.length == ContextualSearchSetting.NUM_ENTRIES; 239 } 240 241 /** 242 * Current Variations parameters associated with the ContextualSearch Field Trial or a 243 * Chrome Feature to determine if the service is enabled 244 * (whether Contextual Search is enabled or not). 245 */ isEnabled()246 public static boolean isEnabled() { 247 if (sEnabled == null) sEnabled = detectEnabled(); 248 return sEnabled.booleanValue(); 249 } 250 getSwitch(@ontextualSearchSwitch int value)251 static boolean getSwitch(@ContextualSearchSwitch int value) { 252 if (sSwitches[value] == null) { 253 switch (value) { 254 case ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED: 255 case ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_SECOND_TAP_ML_OVERRIDE_ENABLED: 256 case ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_TAP_DISABLE_OVERRIDE_ENABLED: 257 sSwitches[value] = 258 ChromeFeatureList.isEnabled(ContextualSearchSwitchNames[value]); 259 break; 260 default: 261 assert !TextUtils.isEmpty(ContextualSearchSwitchNames[value]); 262 sSwitches[value] = getBooleanParam(ContextualSearchSwitchNames[value]); 263 } 264 } 265 return sSwitches[value].booleanValue(); 266 } 267 getValue(@ontextualSearchSetting int value)268 static int getValue(@ContextualSearchSetting int value) { 269 if (sSettings[value] == null) { 270 sSettings[value] = getIntParamValueOrDefault(ContextualSearchSettingNames[value], 271 value == ContextualSearchSetting.MANDATORY_PROMO_LIMIT 272 ? MANDATORY_PROMO_DEFAULT_LIMIT 273 : 0); 274 } 275 return sSettings[value].intValue(); 276 } 277 278 /** 279 * Gets the "stamp" parameter from the RelatedSearches FieldTrial feature. 280 * @return The stamp parameter from the feature. If no stamp param is present then an empty 281 * string is returned. 282 */ getRelatedSearchesExperiementConfigurationStamp()283 static String getRelatedSearchesExperiementConfigurationStamp() { 284 return getRelatedSearchesParam(RELATED_SEARCHES_CONFIG_STAMP_PARAM_NAME); 285 } 286 287 /** 288 * Gets the given parameter from the RelatedSearches FieldTrial feature. 289 * @param paramName The name of the parameter to get. 290 * @return The value of the parameter from the feature. If no param is present then an empty 291 * string is returned. 292 */ getRelatedSearchesParam(String paramName)293 static String getRelatedSearchesParam(String paramName) { 294 return ChromeFeatureList.getFieldTrialParamByFeature( 295 ChromeFeatureList.RELATED_SEARCHES, paramName); 296 } 297 298 /** 299 * Determines whether the specified parameter is present and enabled in the RelatedSearches 300 * Feature. 301 * @param relatedSearchesParamName The name of the param to get from the Feature. 302 * @return Whether the given parameter is enabled or not (has a value of "true"). 303 */ isRelatedSearchesParamEnabled(String relatedSearchesParamName)304 static boolean isRelatedSearchesParamEnabled(String relatedSearchesParamName) { 305 return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean( 306 ChromeFeatureList.RELATED_SEARCHES, relatedSearchesParamName, false); 307 } 308 309 // -------------------------------------------------------------------------------------------- 310 // Helpers. 311 // -------------------------------------------------------------------------------------------- 312 detectEnabled()313 private static boolean detectEnabled() { 314 if (SysUtils.isLowEndDevice()) return false; 315 316 // Allow this user-flippable flag to disable the feature. 317 if (CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_CONTEXTUAL_SEARCH)) { 318 return false; 319 } 320 321 // Allow this user-flippable flag to enable the feature. 322 if (CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_CONTEXTUAL_SEARCH)) { 323 return true; 324 } 325 326 // Allow disabling the feature remotely. 327 if (getBooleanParam(DISABLED_PARAM)) return false; 328 329 return true; 330 } 331 332 /** 333 * Gets a boolean Finch parameter, assuming the <paramName>="true" format. Also checks for 334 * a command-line switch with the same name, for easy local testing. 335 * @param paramName The name of the Finch parameter (or command-line switch) to get a value 336 * for. 337 * @return Whether the Finch param is defined with a value "true", if there's a command-line 338 * flag present with any value. 339 */ getBooleanParam(String paramName)340 private static boolean getBooleanParam(String paramName) { 341 if (CommandLine.getInstance().hasSwitch(paramName)) { 342 return true; 343 } 344 return TextUtils.equals(ENABLED_VALUE, 345 VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME, paramName)); 346 } 347 348 /** 349 * Returns an integer value for a Finch parameter, or the default value if no parameter 350 * exists in the current configuration. Also checks for a command-line switch with the same 351 * name. 352 * @param paramName The name of the Finch parameter (or command-line switch) to get a value 353 * for. 354 * @param defaultValue The default value to return when there's no param or switch. 355 * @return An integer value -- either the param or the default. 356 */ getIntParamValueOrDefault(String paramName, int defaultValue)357 private static int getIntParamValueOrDefault(String paramName, int defaultValue) { 358 String value = CommandLine.getInstance().getSwitchValue(paramName); 359 if (TextUtils.isEmpty(value)) { 360 value = VariationsAssociatedData.getVariationParamValue(FIELD_TRIAL_NAME, paramName); 361 } 362 if (!TextUtils.isEmpty(value)) { 363 try { 364 return Integer.parseInt(value); 365 } catch (NumberFormatException e) { 366 return defaultValue; 367 } 368 } 369 370 return defaultValue; 371 } 372 } 373