• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.common;
18 
19 import static java.util.concurrent.TimeUnit.HOURS;
20 
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.provider.DeviceConfig;
24 import android.provider.DeviceConfig.Properties;
25 import android.text.TextUtils;
26 import android.view.textclassifier.ConversationAction;
27 import android.view.textclassifier.TextClassifier;
28 import androidx.annotation.NonNull;
29 import com.android.textclassifier.utils.IndentingPrintWriter;
30 import com.google.common.annotations.VisibleForTesting;
31 import com.google.common.base.Splitter;
32 import com.google.common.collect.ImmutableList;
33 import com.google.common.collect.ImmutableMap;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.List;
37 import javax.annotation.Nullable;
38 
39 /**
40  * TextClassifier specific settings.
41  *
42  * <p>Currently, this class does not guarantee co-diverted flags are updated atomically.
43  *
44  * <p>Example of setting the values for testing.
45  *
46  * <pre>
47  * adb shell cmd device_config put textclassifier system_textclassifier_enabled true
48  * </pre>
49  *
50  * @see android.provider.DeviceConfig#NAMESPACE_TEXTCLASSIFIER
51  */
52 public final class TextClassifierSettings {
53   private static final String TAG = "TextClassifierSettings";
54   public static final String NAMESPACE = DeviceConfig.NAMESPACE_TEXTCLASSIFIER;
55 
56   private static final String DELIMITER = ":";
57 
58   /** Whether the user language profile feature is enabled. */
59   private static final String USER_LANGUAGE_PROFILE_ENABLED = "user_language_profile_enabled";
60 
61   /** Max length of text that suggestSelection can accept. */
62   @VisibleForTesting
63   static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = "suggest_selection_max_range_length";
64 
65   /** Max length of text that classifyText can accept. */
66   private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = "classify_text_max_range_length";
67 
68   /** Max length of text that generateLinks can accept. */
69   private static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length";
70 
71   /** Sampling rate for generateLinks logging. */
72   private static final String GENERATE_LINKS_LOG_SAMPLE_RATE = "generate_links_log_sample_rate";
73 
74   /**
75    * Extra count that is added to some languages, e.g. system languages, when deducing the frequent
76    * languages in {@link
77    * com.android.textclassifier.ulp.LanguageProfileAnalyzer#getFrequentLanguages(int)}.
78    */
79 
80   /**
81    * A colon(:) separated string that specifies the default entities types for generateLinks when
82    * hint is not given.
83    */
84   @VisibleForTesting static final String ENTITY_LIST_DEFAULT = "entity_list_default";
85 
86   /**
87    * A colon(:) separated string that specifies the default entities types for generateLinks when
88    * the text is in a not editable UI widget.
89    */
90   private static final String ENTITY_LIST_NOT_EDITABLE = "entity_list_not_editable";
91 
92   /**
93    * A colon(:) separated string that specifies the default entities types for generateLinks when
94    * the text is in an editable UI widget.
95    */
96   private static final String ENTITY_LIST_EDITABLE = "entity_list_editable";
97 
98   /**
99    * A colon(:) separated string that specifies the default action types for
100    * suggestConversationActions when the suggestions are used in an app.
101    */
102   private static final String IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT =
103       "in_app_conversation_action_types_default";
104 
105   /**
106    * A colon(:) separated string that specifies the default action types for
107    * suggestConversationActions when the suggestions are used in a notification.
108    */
109   private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT =
110       "notification_conversation_action_types_default";
111 
112   /** Threshold to accept a suggested language from LangID model. */
113   @VisibleForTesting static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override";
114 
115   /** Whether to enable {@link com.android.textclassifier.intent.TemplateIntentFactory}. */
116   @VisibleForTesting
117   static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled";
118 
119   /** Whether to enable "translate" action in classifyText. */
120   private static final String TRANSLATE_IN_CLASSIFICATION_ENABLED =
121       "translate_in_classification_enabled";
122 
123   /**
124    * Whether to detect the languages of the text in request by using langId for the native model.
125    */
126   private static final String DETECT_LANGUAGES_FROM_TEXT_ENABLED =
127       "detect_languages_from_text_enabled";
128 
129   /** Whether to use models downloaded by config updater. */
130   private static final String CONFIG_UPDATER_MODEL_ENABLED = "config_updater_model_enabled";
131 
132   /** Whether to enable model downloading with ModelDownloadManager */
133   @VisibleForTesting
134   public static final String MODEL_DOWNLOAD_MANAGER_ENABLED = "model_download_manager_enabled";
135 
136   /** Type of network to download model manifest. A String value of androidx.work.NetworkType. */
137   private static final String MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE =
138       "manifest_download_required_network_type";
139 
140   /** Max attempts allowed for a single ModelDownloader downloading task. */
141   @VisibleForTesting
142   static final String MODEL_DOWNLOAD_WORKER_MAX_ATTEMPTS = "model_download_worker_max_attempts";
143 
144   /** Max attempts allowed for a certain manifest url. */
145   @VisibleForTesting
146   public static final String MANIFEST_DOWNLOAD_MAX_ATTEMPTS = "manifest_download_max_attempts";
147 
148   @VisibleForTesting
149   static final String MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS =
150       "model_download_backoff_delay_in_millis";
151 
152   private static final String MANIFEST_DOWNLOAD_REQUIRES_CHARGING =
153       "manifest_download_requires_charging";
154   private static final String MANIFEST_DOWNLOAD_REQUIRES_DEVICE_IDLE =
155       "manifest_download_requires_device_idle";
156 
157   /** Flag name for manifest url is dynamically formatted based on model type and model language. */
158   @VisibleForTesting public static final String MANIFEST_URL_TEMPLATE = "manifest_url_%s_%s";
159 
160   @VisibleForTesting public static final String MODEL_URL_BLOCKLIST = "model_url_blocklist";
161   @VisibleForTesting public static final String MODEL_URL_BLOCKLIST_SEPARATOR = ",";
162 
163   /** Flags to control multi-language support settings. */
164   @VisibleForTesting
165   public static final String MULTI_LANGUAGE_SUPPORT_ENABLED = "multi_language_support_enabled";
166 
167   @VisibleForTesting
168   public static final String MULTI_LANGUAGE_MODELS_LIMIT = "multi_language_models_limit";
169 
170   @VisibleForTesting
171   public static final String ENABLED_MODEL_TYPES_FOR_MULTI_LANGUAGE_SUPPORT =
172       "enabled_model_types_for_multi_language_support";
173 
174   @VisibleForTesting
175   public static final String MULTI_ANNOTATOR_CACHE_ENABLED = "multi_annotator_cache_enabled";
176 
177   private static final String MULTI_ANNOTATOR_CACHE_SIZE = "multi_annotator_cache_size";
178 
179   /** List of locale tags to override LocaleList for TextClassifier. Testing/debugging only. */
180   @VisibleForTesting
181   public static final String TESTING_LOCALE_LIST_OVERRIDE = "testing_locale_list_override";
182 
183   /** Sampling rate for TextClassifier API logging. */
184   static final String TEXTCLASSIFIER_API_LOG_SAMPLE_RATE = "textclassifier_api_log_sample_rate";
185 
186   /** The size of the cache of the mapping of session id to text classification context. */
187   private static final String SESSION_ID_TO_CONTEXT_CACHE_SIZE = "session_id_to_context_cache_size";
188 
189   /**
190    * A colon(:) separated string that specifies the configuration to use when including surrounding
191    * context text in language detection queries.
192    *
193    * <p>Format= minimumTextSize<int>:penalizeRatio<float>:textScoreRatio<float>
194    *
195    * <p>e.g. 20:1.0:0.4
196    *
197    * <p>Accept all text lengths with minimumTextSize=0
198    *
199    * <p>Reject all text less than minimumTextSize with penalizeRatio=0
200    *
201    * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference.
202    */
203   @VisibleForTesting static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings";
204 
205   /** Default threshold to translate the language of the context the user selects */
206   private static final String TRANSLATE_ACTION_THRESHOLD = "translate_action_threshold";
207 
208   // Sync this with ConversationAction.TYPE_ADD_CONTACT;
209   public static final String TYPE_ADD_CONTACT = "add_contact";
210   // Sync this with ConversationAction.COPY;
211   public static final String TYPE_COPY = "copy";
212 
213   private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
214   private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
215   private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
216   private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100;
217 
218   private static final ImmutableList<String> ENTITY_LIST_DEFAULT_VALUE =
219       ImmutableList.of(
220           TextClassifier.TYPE_ADDRESS,
221           TextClassifier.TYPE_EMAIL,
222           TextClassifier.TYPE_PHONE,
223           TextClassifier.TYPE_URL,
224           TextClassifier.TYPE_DATE,
225           TextClassifier.TYPE_DATE_TIME,
226           TextClassifier.TYPE_FLIGHT_NUMBER);
227   private static final ImmutableList<String> CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES =
228       ImmutableList.of(
229           ConversationAction.TYPE_TEXT_REPLY,
230           ConversationAction.TYPE_CREATE_REMINDER,
231           ConversationAction.TYPE_CALL_PHONE,
232           ConversationAction.TYPE_OPEN_URL,
233           ConversationAction.TYPE_SEND_EMAIL,
234           ConversationAction.TYPE_SEND_SMS,
235           ConversationAction.TYPE_TRACK_FLIGHT,
236           ConversationAction.TYPE_VIEW_CALENDAR,
237           ConversationAction.TYPE_VIEW_MAP,
238           TYPE_ADD_CONTACT,
239           TYPE_COPY);
240 
241   /**
242    * < 0 : Not set. Use value from LangId model. 0 - 1: Override value in LangId model.
243    *
244    * @see EntityConfidence
245    */
246   private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f;
247 
248   private static final float TRANSLATE_ACTION_THRESHOLD_DEFAULT = 0.5f;
249 
250   private static final boolean USER_LANGUAGE_PROFILE_ENABLED_DEFAULT = true;
251   private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true;
252   private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true;
253   private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true;
254   private static final boolean CONFIG_UPDATER_MODEL_ENABLED_DEFAULT = true;
255   private static final boolean MODEL_DOWNLOAD_MANAGER_ENABLED_DEFAULT = false;
256   private static final String MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE_DEFAULT = "UNMETERED";
257   private static final int MODEL_DOWNLOAD_WORKER_MAX_ATTEMPTS_DEFAULT = 5;
258   private static final int MANIFEST_DOWNLOAD_MAX_ATTEMPTS_DEFAULT = 3;
259   private static final long MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS_DEFAULT = HOURS.toMillis(1);
260   private static final boolean MANIFEST_DOWNLOAD_REQUIRES_DEVICE_IDLE_DEFAULT = false;
261   private static final boolean MANIFEST_DOWNLOAD_REQUIRES_CHARGING_WEAR_DEFAULT = true;
262   private static final boolean MANIFEST_DOWNLOAD_REQUIRES_CHARGING_DEFAULT = false;
263   private static final boolean MULTI_LANGUAGE_SUPPORT_ENABLED_DEFAULT = false;
264   private static final int MULTI_LANGUAGE_MODELS_LIMIT_DEFAULT = 2;
265   private static final ImmutableList<String>
266       ENABLED_MODEL_TYPES_FOR_MULTI_LANGUAGE_SUPPORT_DEFAULT =
267           ImmutableList.of(ModelType.ANNOTATOR);
268   private static final boolean MULTI_ANNOTATOR_CACHE_ENABLED_DEFAULT = false;
269   private static final int MULTI_ANNOTATOR_CACHE_SIZE_DEFAULT = 2;
270   private static final String MANIFEST_URL_DEFAULT = "";
271   private static final String TESTING_LOCALE_LIST_OVERRIDE_DEFAULT = "";
272   private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f};
273 
274   /**
275    * Sampling rate for API logging. For example, 100 means there is a 0.01 chance that the API call
276    * is the logged.
277    */
278   private static final int TEXTCLASSIFIER_API_LOG_SAMPLE_RATE_DEFAULT = 10;
279 
280   private static final int SESSION_ID_TO_CONTEXT_CACHE_SIZE_DEFAULT = 10;
281 
282   // TODO(licha): Consider removing this. We can use real device config for testing.
283   /** DeviceConfig interface to facilitate testing. */
284   @VisibleForTesting
285   public interface IDeviceConfig {
getProperties(@onNull String namespace, @NonNull String... names)286     default Properties getProperties(@NonNull String namespace, @NonNull String... names) {
287       return new Properties.Builder(namespace).build();
288     }
289 
getInt(@onNull String namespace, @NonNull String name, @NonNull int defaultValue)290     default int getInt(@NonNull String namespace, @NonNull String name, @NonNull int defaultValue) {
291       return defaultValue;
292     }
293 
getLong( @onNull String namespace, @NonNull String name, @NonNull long defaultValue)294     default long getLong(
295         @NonNull String namespace, @NonNull String name, @NonNull long defaultValue) {
296       return defaultValue;
297     }
298 
getFloat( @onNull String namespace, @NonNull String name, @NonNull float defaultValue)299     default float getFloat(
300         @NonNull String namespace, @NonNull String name, @NonNull float defaultValue) {
301       return defaultValue;
302     }
303 
getString( @onNull String namespace, @NonNull String name, @Nullable String defaultValue)304     default String getString(
305         @NonNull String namespace, @NonNull String name, @Nullable String defaultValue) {
306       return defaultValue;
307     }
308 
getBoolean( @onNull String namespace, @NonNull String name, boolean defaultValue)309     default boolean getBoolean(
310         @NonNull String namespace, @NonNull String name, boolean defaultValue) {
311       return defaultValue;
312     }
313   }
314 
315   private static final IDeviceConfig DEFAULT_DEVICE_CONFIG =
316       new IDeviceConfig() {
317         @Override
318         public Properties getProperties(@NonNull String namespace, @NonNull String... names) {
319           return DeviceConfig.getProperties(namespace, names);
320         }
321 
322         @Override
323         public int getInt(
324             @NonNull String namespace, @NonNull String name, @NonNull int defaultValue) {
325           return DeviceConfig.getInt(namespace, name, defaultValue);
326         }
327 
328         @Override
329         public long getLong(
330             @NonNull String namespace, @NonNull String name, @NonNull long defaultValue) {
331           return DeviceConfig.getLong(namespace, name, defaultValue);
332         }
333 
334         @Override
335         public float getFloat(
336             @NonNull String namespace, @NonNull String name, @NonNull float defaultValue) {
337           return DeviceConfig.getFloat(namespace, name, defaultValue);
338         }
339 
340         @Override
341         public String getString(
342             @NonNull String namespace, @NonNull String name, @NonNull String defaultValue) {
343           return DeviceConfig.getString(namespace, name, defaultValue);
344         }
345 
346         @Override
347         public boolean getBoolean(
348             @NonNull String namespace, @NonNull String name, @NonNull boolean defaultValue) {
349           return DeviceConfig.getBoolean(namespace, name, defaultValue);
350         }
351       };
352 
353   private final IDeviceConfig deviceConfig;
354   private final boolean isWear;
355 
TextClassifierSettings(Context context)356   public TextClassifierSettings(Context context) {
357     this(
358         DEFAULT_DEVICE_CONFIG,
359         context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
360   }
361 
362   @VisibleForTesting
TextClassifierSettings(IDeviceConfig deviceConfig, boolean isWear)363   public TextClassifierSettings(IDeviceConfig deviceConfig, boolean isWear) {
364     this.deviceConfig = deviceConfig;
365     this.isWear = isWear;
366   }
367 
getSuggestSelectionMaxRangeLength()368   public int getSuggestSelectionMaxRangeLength() {
369     return deviceConfig.getInt(
370         NAMESPACE, SUGGEST_SELECTION_MAX_RANGE_LENGTH, SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
371   }
372 
getClassifyTextMaxRangeLength()373   public int getClassifyTextMaxRangeLength() {
374     return deviceConfig.getInt(
375         NAMESPACE, CLASSIFY_TEXT_MAX_RANGE_LENGTH, CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
376   }
377 
getGenerateLinksMaxTextLength()378   public int getGenerateLinksMaxTextLength() {
379     return deviceConfig.getInt(
380         NAMESPACE, GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
381   }
382 
getGenerateLinksLogSampleRate()383   public int getGenerateLinksLogSampleRate() {
384     return deviceConfig.getInt(
385         NAMESPACE, GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
386   }
387 
getEntityListDefault()388   public List<String> getEntityListDefault() {
389     return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE);
390   }
391 
getEntityListNotEditable()392   public List<String> getEntityListNotEditable() {
393     return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
394   }
395 
getEntityListEditable()396   public List<String> getEntityListEditable() {
397     return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
398   }
399 
getInAppConversationActionTypes()400   public List<String> getInAppConversationActionTypes() {
401     return getDeviceConfigStringList(
402         IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
403   }
404 
getNotificationConversationActionTypes()405   public List<String> getNotificationConversationActionTypes() {
406     return getDeviceConfigStringList(
407         NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
408   }
409 
getLangIdThresholdOverride()410   public float getLangIdThresholdOverride() {
411     return deviceConfig.getFloat(
412         NAMESPACE, LANG_ID_THRESHOLD_OVERRIDE, LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
413   }
414 
getTranslateActionThreshold()415   public float getTranslateActionThreshold() {
416     return deviceConfig.getFloat(
417         NAMESPACE, TRANSLATE_ACTION_THRESHOLD, TRANSLATE_ACTION_THRESHOLD_DEFAULT);
418   }
419 
isUserLanguageProfileEnabled()420   public boolean isUserLanguageProfileEnabled() {
421     return deviceConfig.getBoolean(
422         NAMESPACE, USER_LANGUAGE_PROFILE_ENABLED, USER_LANGUAGE_PROFILE_ENABLED_DEFAULT);
423   }
424 
isTemplateIntentFactoryEnabled()425   public boolean isTemplateIntentFactoryEnabled() {
426     return deviceConfig.getBoolean(
427         NAMESPACE, TEMPLATE_INTENT_FACTORY_ENABLED, TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
428   }
429 
isTranslateInClassificationEnabled()430   public boolean isTranslateInClassificationEnabled() {
431     return deviceConfig.getBoolean(
432         NAMESPACE,
433         TRANSLATE_IN_CLASSIFICATION_ENABLED,
434         TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT);
435   }
436 
isDetectLanguagesFromTextEnabled()437   public boolean isDetectLanguagesFromTextEnabled() {
438     return deviceConfig.getBoolean(
439         NAMESPACE, DETECT_LANGUAGES_FROM_TEXT_ENABLED, DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT);
440   }
441 
getLangIdContextSettings()442   public float[] getLangIdContextSettings() {
443     return getDeviceConfigFloatArray(LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT);
444   }
445 
isConfigUpdaterModelEnabled()446   public boolean isConfigUpdaterModelEnabled() {
447     return deviceConfig.getBoolean(
448         NAMESPACE, CONFIG_UPDATER_MODEL_ENABLED, CONFIG_UPDATER_MODEL_ENABLED_DEFAULT);
449   }
450 
isModelDownloadManagerEnabled()451   public boolean isModelDownloadManagerEnabled() {
452     return deviceConfig.getBoolean(
453         NAMESPACE, MODEL_DOWNLOAD_MANAGER_ENABLED, MODEL_DOWNLOAD_MANAGER_ENABLED_DEFAULT);
454   }
455 
456   /** Returns a string which represents a androidx.work.NetworkType enum. */
getManifestDownloadRequiredNetworkType()457   public String getManifestDownloadRequiredNetworkType() {
458     return deviceConfig.getString(
459         NAMESPACE,
460         MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE,
461         MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE_DEFAULT);
462   }
463 
getModelDownloadWorkerMaxAttempts()464   public int getModelDownloadWorkerMaxAttempts() {
465     return deviceConfig.getInt(
466         NAMESPACE, MODEL_DOWNLOAD_WORKER_MAX_ATTEMPTS, MODEL_DOWNLOAD_WORKER_MAX_ATTEMPTS_DEFAULT);
467   }
468 
getManifestDownloadMaxAttempts()469   public int getManifestDownloadMaxAttempts() {
470     return deviceConfig.getInt(
471         NAMESPACE, MANIFEST_DOWNLOAD_MAX_ATTEMPTS, MANIFEST_DOWNLOAD_MAX_ATTEMPTS_DEFAULT);
472   }
473 
getModelDownloadBackoffDelayInMillis()474   public long getModelDownloadBackoffDelayInMillis() {
475     return deviceConfig.getLong(
476         NAMESPACE,
477         MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS,
478         MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS_DEFAULT);
479   }
480 
getManifestDownloadRequiresDeviceIdle()481   public boolean getManifestDownloadRequiresDeviceIdle() {
482     return deviceConfig.getBoolean(
483         NAMESPACE,
484         MANIFEST_DOWNLOAD_REQUIRES_DEVICE_IDLE,
485         MANIFEST_DOWNLOAD_REQUIRES_DEVICE_IDLE_DEFAULT);
486   }
487 
getManifestDownloadRequiresCharging()488   public boolean getManifestDownloadRequiresCharging() {
489     return deviceConfig.getBoolean(
490         NAMESPACE,
491         MANIFEST_DOWNLOAD_REQUIRES_CHARGING,
492         isWear
493             ? MANIFEST_DOWNLOAD_REQUIRES_CHARGING_WEAR_DEFAULT
494             : MANIFEST_DOWNLOAD_REQUIRES_CHARGING_DEFAULT);
495   }
496 
497   /* Gets a list of models urls that should not be used. Usually used for a quick rollback.  */
getModelUrlBlocklist()498   public ImmutableList<String> getModelUrlBlocklist() {
499     return ImmutableList.copyOf(
500         Splitter.on(MODEL_URL_BLOCKLIST_SEPARATOR)
501             .split(deviceConfig.getString(NAMESPACE, MODEL_URL_BLOCKLIST, "")));
502   }
503 
isMultiLanguageSupportEnabled()504   public boolean isMultiLanguageSupportEnabled() {
505     return deviceConfig.getBoolean(
506         NAMESPACE, MULTI_LANGUAGE_SUPPORT_ENABLED, MULTI_LANGUAGE_SUPPORT_ENABLED_DEFAULT);
507   }
508 
getMultiLanguageModelsLimit()509   public int getMultiLanguageModelsLimit() {
510     return deviceConfig.getInt(
511         NAMESPACE, MULTI_LANGUAGE_MODELS_LIMIT, MULTI_LANGUAGE_MODELS_LIMIT_DEFAULT);
512   }
513 
getEnabledModelTypesForMultiLanguageSupport()514   public List<String> getEnabledModelTypesForMultiLanguageSupport() {
515     return getDeviceConfigStringList(
516         ENABLED_MODEL_TYPES_FOR_MULTI_LANGUAGE_SUPPORT,
517         ENABLED_MODEL_TYPES_FOR_MULTI_LANGUAGE_SUPPORT_DEFAULT);
518   }
519 
getMultiAnnotatorCacheEnabled()520   public boolean getMultiAnnotatorCacheEnabled() {
521     return deviceConfig.getBoolean(
522         NAMESPACE, MULTI_ANNOTATOR_CACHE_ENABLED, MULTI_ANNOTATOR_CACHE_ENABLED_DEFAULT);
523   }
524 
getMultiAnnotatorCacheSize()525   public int getMultiAnnotatorCacheSize() {
526     return deviceConfig.getInt(
527         NAMESPACE, MULTI_ANNOTATOR_CACHE_SIZE, MULTI_ANNOTATOR_CACHE_SIZE_DEFAULT);
528   }
529 
530   /**
531    * Gets all language variants and associated manifest url configured for a specific ModelType.
532    *
533    * <p>For a specific language, there can be many variants: de-CH, de-LI, zh-Hans, zh-Hant. There
534    * is no easy way to hardcode the list in client. Therefore, we parse all configured flag's name
535    * in DeviceConfig, and let the client to choose the best variant to download.
536    *
537    * <p>If one flag's value is empty, it will be ignored.
538    *
539    * @param modelType the type of model for the target url
540    * @return <localeTag, flagValue> map.
541    */
getLanguageTagAndManifestUrlMap( @odelType.ModelTypeDef String modelType)542   public ImmutableMap<String, String> getLanguageTagAndManifestUrlMap(
543       @ModelType.ModelTypeDef String modelType) {
544     String urlFlagBaseName = String.format(MANIFEST_URL_TEMPLATE, modelType, /* language */ "");
545     Properties properties = deviceConfig.getProperties(NAMESPACE);
546     ImmutableMap.Builder<String, String> variantsMapBuilder = ImmutableMap.builder();
547     for (String name : properties.getKeyset()) {
548       if (!name.startsWith(urlFlagBaseName)) {
549         continue;
550       }
551       String value = properties.getString(name, /* defaultValue= */ null);
552       if (!TextUtils.isEmpty(value)) {
553         String modelLanguageTag = name.substring(urlFlagBaseName.length());
554         String urlFlagName = String.format(MANIFEST_URL_TEMPLATE, modelType, modelLanguageTag);
555         String urlFlagValue = deviceConfig.getString(NAMESPACE, urlFlagName, MANIFEST_URL_DEFAULT);
556         variantsMapBuilder.put(modelLanguageTag, urlFlagValue);
557       }
558     }
559     return variantsMapBuilder.buildOrThrow();
560   }
561 
getTestingLocaleListOverride()562   public String getTestingLocaleListOverride() {
563     return deviceConfig.getString(
564         NAMESPACE, TESTING_LOCALE_LIST_OVERRIDE, TESTING_LOCALE_LIST_OVERRIDE_DEFAULT);
565   }
566 
getTextClassifierApiLogSampleRate()567   public int getTextClassifierApiLogSampleRate() {
568     return deviceConfig.getInt(
569         NAMESPACE, TEXTCLASSIFIER_API_LOG_SAMPLE_RATE, TEXTCLASSIFIER_API_LOG_SAMPLE_RATE_DEFAULT);
570   }
571 
getSessionIdToContextCacheSize()572   public int getSessionIdToContextCacheSize() {
573     return deviceConfig.getInt(
574         NAMESPACE, SESSION_ID_TO_CONTEXT_CACHE_SIZE, SESSION_ID_TO_CONTEXT_CACHE_SIZE_DEFAULT);
575   }
576 
dump(IndentingPrintWriter pw)577   public void dump(IndentingPrintWriter pw) {
578     pw.println("TextClassifierSettings:");
579     pw.increaseIndent();
580     pw.printPair(CLASSIFY_TEXT_MAX_RANGE_LENGTH, getClassifyTextMaxRangeLength());
581     pw.printPair(DETECT_LANGUAGES_FROM_TEXT_ENABLED, isDetectLanguagesFromTextEnabled());
582     pw.printPair(ENTITY_LIST_DEFAULT, getEntityListDefault());
583     pw.printPair(ENTITY_LIST_EDITABLE, getEntityListEditable());
584     pw.printPair(ENTITY_LIST_NOT_EDITABLE, getEntityListNotEditable());
585     pw.printPair(GENERATE_LINKS_LOG_SAMPLE_RATE, getGenerateLinksLogSampleRate());
586     pw.printPair(GENERATE_LINKS_MAX_TEXT_LENGTH, getGenerateLinksMaxTextLength());
587     pw.printPair(IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, getInAppConversationActionTypes());
588     pw.printPair(LANG_ID_CONTEXT_SETTINGS, Arrays.toString(getLangIdContextSettings()));
589     pw.printPair(LANG_ID_THRESHOLD_OVERRIDE, getLangIdThresholdOverride());
590     pw.printPair(TRANSLATE_ACTION_THRESHOLD, getTranslateActionThreshold());
591     pw.printPair(
592         NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, getNotificationConversationActionTypes());
593     pw.printPair(SUGGEST_SELECTION_MAX_RANGE_LENGTH, getSuggestSelectionMaxRangeLength());
594     pw.printPair(USER_LANGUAGE_PROFILE_ENABLED, isUserLanguageProfileEnabled());
595     pw.printPair(TEMPLATE_INTENT_FACTORY_ENABLED, isTemplateIntentFactoryEnabled());
596     pw.printPair(TRANSLATE_IN_CLASSIFICATION_ENABLED, isTranslateInClassificationEnabled());
597     pw.printPair(CONFIG_UPDATER_MODEL_ENABLED, isConfigUpdaterModelEnabled());
598     pw.printPair(MODEL_DOWNLOAD_MANAGER_ENABLED, isModelDownloadManagerEnabled());
599     pw.printPair(MULTI_LANGUAGE_SUPPORT_ENABLED, isMultiLanguageSupportEnabled());
600     pw.printPair(MULTI_LANGUAGE_MODELS_LIMIT, getMultiLanguageModelsLimit());
601     pw.printPair(
602         ENABLED_MODEL_TYPES_FOR_MULTI_LANGUAGE_SUPPORT,
603         getEnabledModelTypesForMultiLanguageSupport());
604     pw.printPair(MULTI_ANNOTATOR_CACHE_ENABLED, getMultiAnnotatorCacheEnabled());
605     pw.printPair(MULTI_ANNOTATOR_CACHE_SIZE, getMultiAnnotatorCacheSize());
606     pw.printPair(MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE, getManifestDownloadRequiredNetworkType());
607     pw.printPair(MODEL_DOWNLOAD_WORKER_MAX_ATTEMPTS, getModelDownloadWorkerMaxAttempts());
608     pw.printPair(MANIFEST_DOWNLOAD_MAX_ATTEMPTS, getManifestDownloadMaxAttempts());
609     pw.printPair(MANIFEST_DOWNLOAD_REQUIRES_CHARGING, getManifestDownloadRequiresCharging());
610     pw.printPair(MANIFEST_DOWNLOAD_REQUIRES_DEVICE_IDLE, getManifestDownloadRequiresDeviceIdle());
611     pw.printPair(TESTING_LOCALE_LIST_OVERRIDE, getTestingLocaleListOverride());
612     pw.decreaseIndent();
613     pw.printPair(TEXTCLASSIFIER_API_LOG_SAMPLE_RATE, getTextClassifierApiLogSampleRate());
614     pw.printPair(SESSION_ID_TO_CONTEXT_CACHE_SIZE, getSessionIdToContextCacheSize());
615     pw.decreaseIndent();
616   }
617 
getDeviceConfigStringList(String key, List<String> defaultValue)618   private List<String> getDeviceConfigStringList(String key, List<String> defaultValue) {
619     return parse(deviceConfig.getString(NAMESPACE, key, null), defaultValue);
620   }
621 
getDeviceConfigFloatArray(String key, float[] defaultValue)622   private float[] getDeviceConfigFloatArray(String key, float[] defaultValue) {
623     return parse(deviceConfig.getString(NAMESPACE, key, null), defaultValue);
624   }
625 
parse(@ullable String listStr, List<String> defaultValue)626   private static List<String> parse(@Nullable String listStr, List<String> defaultValue) {
627     if (listStr != null) {
628       return Collections.unmodifiableList(Arrays.asList(listStr.split(DELIMITER)));
629     }
630     return defaultValue;
631   }
632 
parse(@ullable String arrayStr, float[] defaultValue)633   private static float[] parse(@Nullable String arrayStr, float[] defaultValue) {
634     if (arrayStr != null) {
635       final List<String> split = Splitter.onPattern(DELIMITER).splitToList(arrayStr);
636       if (split.size() != defaultValue.length) {
637         return defaultValue;
638       }
639       final float[] result = new float[split.size()];
640       for (int i = 0; i < split.size(); i++) {
641         try {
642           result[i] = Float.parseFloat(split.get(i));
643         } catch (NumberFormatException e) {
644           return defaultValue;
645         }
646       }
647       return result;
648     } else {
649       return defaultValue;
650     }
651   }
652 }
653