1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.textclassifier; 18 19 import android.provider.DeviceConfig; 20 import android.view.textclassifier.ConversationAction; 21 import android.view.textclassifier.TextClassifier; 22 import com.android.textclassifier.utils.IndentingPrintWriter; 23 import com.google.common.annotations.VisibleForTesting; 24 import com.google.common.base.Splitter; 25 import com.google.common.collect.ImmutableList; 26 import java.util.Arrays; 27 import java.util.Collections; 28 import java.util.List; 29 import javax.annotation.Nullable; 30 31 /** 32 * TextClassifier specific settings. 33 * 34 * <p>Currently, this class does not guarantee co-diverted flags are updated atomically. 35 * 36 * <p>Example of setting the values for testing. 37 * 38 * <pre> 39 * adb shell cmd device_config put textclassifier system_textclassifier_enabled true 40 * </pre> 41 * 42 * @see android.provider.DeviceConfig#NAMESPACE_TEXTCLASSIFIER 43 */ 44 public final class TextClassifierSettings { 45 private static final String DELIMITER = ":"; 46 47 /** Whether the user language profile feature is enabled. */ 48 private static final String USER_LANGUAGE_PROFILE_ENABLED = "user_language_profile_enabled"; 49 /** Max length of text that suggestSelection can accept. */ 50 @VisibleForTesting 51 static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = "suggest_selection_max_range_length"; 52 /** Max length of text that classifyText can accept. */ 53 private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = "classify_text_max_range_length"; 54 /** Max length of text that generateLinks can accept. */ 55 private static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length"; 56 /** Sampling rate for generateLinks logging. */ 57 private static final String GENERATE_LINKS_LOG_SAMPLE_RATE = "generate_links_log_sample_rate"; 58 /** 59 * Extra count that is added to some languages, e.g. system languages, when deducing the frequent 60 * languages in {@link 61 * com.android.textclassifier.ulp.LanguageProfileAnalyzer#getFrequentLanguages(int)}. 62 */ 63 64 /** 65 * A colon(:) separated string that specifies the default entities types for generateLinks when 66 * hint is not given. 67 */ 68 @VisibleForTesting static final String ENTITY_LIST_DEFAULT = "entity_list_default"; 69 /** 70 * A colon(:) separated string that specifies the default entities types for generateLinks when 71 * the text is in a not editable UI widget. 72 */ 73 private static final String ENTITY_LIST_NOT_EDITABLE = "entity_list_not_editable"; 74 /** 75 * A colon(:) separated string that specifies the default entities types for generateLinks when 76 * the text is in an editable UI widget. 77 */ 78 private static final String ENTITY_LIST_EDITABLE = "entity_list_editable"; 79 /** 80 * A colon(:) separated string that specifies the default action types for 81 * suggestConversationActions when the suggestions are used in an app. 82 */ 83 private static final String IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT = 84 "in_app_conversation_action_types_default"; 85 /** 86 * A colon(:) separated string that specifies the default action types for 87 * suggestConversationActions when the suggestions are used in a notification. 88 */ 89 private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT = 90 "notification_conversation_action_types_default"; 91 /** Threshold to accept a suggested language from LangID model. */ 92 @VisibleForTesting static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override"; 93 /** Whether to enable {@link com.android.textclassifier.intent.TemplateIntentFactory}. */ 94 @VisibleForTesting 95 static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled"; 96 /** Whether to enable "translate" action in classifyText. */ 97 private static final String TRANSLATE_IN_CLASSIFICATION_ENABLED = 98 "translate_in_classification_enabled"; 99 /** 100 * Whether to detect the languages of the text in request by using langId for the native model. 101 */ 102 private static final String DETECT_LANGUAGES_FROM_TEXT_ENABLED = 103 "detect_languages_from_text_enabled"; 104 /** 105 * A colon(:) separated string that specifies the configuration to use when including surrounding 106 * context text in language detection queries. 107 * 108 * <p>Format= minimumTextSize<int>:penalizeRatio<float>:textScoreRatio<float> 109 * 110 * <p>e.g. 20:1.0:0.4 111 * 112 * <p>Accept all text lengths with minimumTextSize=0 113 * 114 * <p>Reject all text less than minimumTextSize with penalizeRatio=0 115 * 116 * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference. 117 */ 118 @VisibleForTesting static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings"; 119 /** Default threshold to translate the language of the context the user selects */ 120 private static final String TRANSLATE_ACTION_THRESHOLD = "translate_action_threshold"; 121 122 // Sync this with ConversationAction.TYPE_ADD_CONTACT; 123 public static final String TYPE_ADD_CONTACT = "add_contact"; 124 // Sync this with ConversationAction.COPY; 125 public static final String TYPE_COPY = "copy"; 126 127 private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000; 128 private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000; 129 private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000; 130 private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100; 131 132 private static final ImmutableList<String> ENTITY_LIST_DEFAULT_VALUE = 133 ImmutableList.of( 134 TextClassifier.TYPE_ADDRESS, 135 TextClassifier.TYPE_EMAIL, 136 TextClassifier.TYPE_PHONE, 137 TextClassifier.TYPE_URL, 138 TextClassifier.TYPE_DATE, 139 TextClassifier.TYPE_DATE_TIME, 140 TextClassifier.TYPE_FLIGHT_NUMBER); 141 private static final ImmutableList<String> CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES = 142 ImmutableList.of( 143 ConversationAction.TYPE_TEXT_REPLY, 144 ConversationAction.TYPE_CREATE_REMINDER, 145 ConversationAction.TYPE_CALL_PHONE, 146 ConversationAction.TYPE_OPEN_URL, 147 ConversationAction.TYPE_SEND_EMAIL, 148 ConversationAction.TYPE_SEND_SMS, 149 ConversationAction.TYPE_TRACK_FLIGHT, 150 ConversationAction.TYPE_VIEW_CALENDAR, 151 ConversationAction.TYPE_VIEW_MAP, 152 TYPE_ADD_CONTACT, 153 TYPE_COPY); 154 /** 155 * < 0 : Not set. Use value from LangId model. 0 - 1: Override value in LangId model. 156 * 157 * @see EntityConfidence 158 */ 159 private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f; 160 161 private static final float TRANSLATE_ACTION_THRESHOLD_DEFAULT = 0.5f; 162 163 private static final boolean USER_LANGUAGE_PROFILE_ENABLED_DEFAULT = true; 164 private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true; 165 private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true; 166 private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true; 167 private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f}; 168 169 public int getSuggestSelectionMaxRangeLength() { 170 return DeviceConfig.getInt( 171 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 172 SUGGEST_SELECTION_MAX_RANGE_LENGTH, 173 SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT); 174 } 175 176 public int getClassifyTextMaxRangeLength() { 177 return DeviceConfig.getInt( 178 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 179 CLASSIFY_TEXT_MAX_RANGE_LENGTH, 180 CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT); 181 } 182 183 public int getGenerateLinksMaxTextLength() { 184 return DeviceConfig.getInt( 185 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 186 GENERATE_LINKS_MAX_TEXT_LENGTH, 187 GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT); 188 } 189 190 public int getGenerateLinksLogSampleRate() { 191 return DeviceConfig.getInt( 192 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 193 GENERATE_LINKS_LOG_SAMPLE_RATE, 194 GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT); 195 } 196 197 public List<String> getEntityListDefault() { 198 return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE); 199 } 200 201 public List<String> getEntityListNotEditable() { 202 return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE); 203 } 204 205 public List<String> getEntityListEditable() { 206 return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE); 207 } 208 209 public List<String> getInAppConversationActionTypes() { 210 return getDeviceConfigStringList( 211 IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES); 212 } 213 214 public List<String> getNotificationConversationActionTypes() { 215 return getDeviceConfigStringList( 216 NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES); 217 } 218 219 public float getLangIdThresholdOverride() { 220 return DeviceConfig.getFloat( 221 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 222 LANG_ID_THRESHOLD_OVERRIDE, 223 LANG_ID_THRESHOLD_OVERRIDE_DEFAULT); 224 } 225 226 public float getTranslateActionThreshold() { 227 return DeviceConfig.getFloat( 228 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 229 TRANSLATE_ACTION_THRESHOLD, 230 TRANSLATE_ACTION_THRESHOLD_DEFAULT); 231 } 232 233 public boolean isUserLanguageProfileEnabled() { 234 return DeviceConfig.getBoolean( 235 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 236 USER_LANGUAGE_PROFILE_ENABLED, 237 USER_LANGUAGE_PROFILE_ENABLED_DEFAULT); 238 } 239 240 public boolean isTemplateIntentFactoryEnabled() { 241 return DeviceConfig.getBoolean( 242 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 243 TEMPLATE_INTENT_FACTORY_ENABLED, 244 TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT); 245 } 246 247 public boolean isTranslateInClassificationEnabled() { 248 return DeviceConfig.getBoolean( 249 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 250 TRANSLATE_IN_CLASSIFICATION_ENABLED, 251 TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT); 252 } 253 254 public boolean isDetectLanguagesFromTextEnabled() { 255 return DeviceConfig.getBoolean( 256 DeviceConfig.NAMESPACE_TEXTCLASSIFIER, 257 DETECT_LANGUAGES_FROM_TEXT_ENABLED, 258 DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT); 259 } 260 261 public float[] getLangIdContextSettings() { 262 return getDeviceConfigFloatArray(LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT); 263 } 264 265 void dump(IndentingPrintWriter pw) { 266 pw.println("TextClassifierSettings:"); 267 pw.increaseIndent(); 268 pw.printPair("classify_text_max_range_length", getClassifyTextMaxRangeLength()); 269 pw.printPair("detect_language_from_text_enabled", isDetectLanguagesFromTextEnabled()); 270 pw.printPair("entity_list_default", getEntityListDefault()); 271 pw.printPair("entity_list_editable", getEntityListEditable()); 272 pw.printPair("entity_list_not_editable", getEntityListNotEditable()); 273 pw.printPair("generate_links_log_sample_rate", getGenerateLinksLogSampleRate()); 274 pw.printPair("generate_links_max_text_length", getGenerateLinksMaxTextLength()); 275 pw.printPair("in_app_conversation_action_types_default", getInAppConversationActionTypes()); 276 pw.printPair("lang_id_context_settings", Arrays.toString(getLangIdContextSettings())); 277 pw.printPair("lang_id_threshold_override", getLangIdThresholdOverride()); 278 pw.printPair("translate_action_threshold", getTranslateActionThreshold()); 279 pw.printPair( 280 "notification_conversation_action_types_default", getNotificationConversationActionTypes()); 281 pw.printPair("suggest_selection_max_range_length", getSuggestSelectionMaxRangeLength()); 282 pw.printPair("user_language_profile_enabled", isUserLanguageProfileEnabled()); 283 pw.printPair("template_intent_factory_enabled", isTemplateIntentFactoryEnabled()); 284 pw.printPair("translate_in_classification_enabled", isTranslateInClassificationEnabled()); 285 pw.decreaseIndent(); 286 } 287 288 private static List<String> getDeviceConfigStringList(String key, List<String> defaultValue) { 289 return parse( 290 DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null), defaultValue); 291 } 292 293 private static float[] getDeviceConfigFloatArray(String key, float[] defaultValue) { 294 return parse( 295 DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null), defaultValue); 296 } 297 298 private static List<String> parse(@Nullable String listStr, List<String> defaultValue) { 299 if (listStr != null) { 300 return Collections.unmodifiableList(Arrays.asList(listStr.split(DELIMITER))); 301 } 302 return defaultValue; 303 } 304 305 private static float[] parse(@Nullable String arrayStr, float[] defaultValue) { 306 if (arrayStr != null) { 307 final List<String> split = Splitter.onPattern(DELIMITER).splitToList(arrayStr); 308 if (split.size() != defaultValue.length) { 309 return defaultValue; 310 } 311 final float[] result = new float[split.size()]; 312 for (int i = 0; i < split.size(); i++) { 313 try { 314 result[i] = Float.parseFloat(split.get(i)); 315 } catch (NumberFormatException e) { 316 return defaultValue; 317 } 318 } 319 return result; 320 } else { 321 return defaultValue; 322 } 323 } 324 } 325