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.provider.DeviceConfig;
22 import android.provider.DeviceConfig.Properties;
23 import android.view.textclassifier.ConversationAction;
24 import android.view.textclassifier.TextClassifier;
25 import androidx.annotation.NonNull;
26 import com.android.textclassifier.utils.IndentingPrintWriter;
27 import com.google.common.annotations.VisibleForTesting;
28 import com.google.common.base.Splitter;
29 import com.google.common.collect.ImmutableList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.List;
33 import javax.annotation.Nullable;
34 
35 /**
36  * TextClassifier specific settings.
37  *
38  * <p>Currently, this class does not guarantee co-diverted flags are updated atomically.
39  *
40  * <p>Example of setting the values for testing.
41  *
42  * <pre>
43  * adb shell cmd device_config put textclassifier system_textclassifier_enabled true
44  * </pre>
45  *
46  * @see android.provider.DeviceConfig#NAMESPACE_TEXTCLASSIFIER
47  */
48 public final class TextClassifierSettings {
49   private static final String TAG = "TextClassifierSettings";
50   public static final String NAMESPACE = DeviceConfig.NAMESPACE_TEXTCLASSIFIER;
51 
52   private static final String DELIMITER = ":";
53 
54   /** Whether the user language profile feature is enabled. */
55   private static final String USER_LANGUAGE_PROFILE_ENABLED = "user_language_profile_enabled";
56   /** Max length of text that suggestSelection can accept. */
57   @VisibleForTesting
58   static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = "suggest_selection_max_range_length";
59   /** Max length of text that classifyText can accept. */
60   private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = "classify_text_max_range_length";
61   /** Max length of text that generateLinks can accept. */
62   private static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length";
63   /** Sampling rate for generateLinks logging. */
64   private static final String GENERATE_LINKS_LOG_SAMPLE_RATE = "generate_links_log_sample_rate";
65   /**
66    * Extra count that is added to some languages, e.g. system languages, when deducing the frequent
67    * languages in {@link
68    * com.android.textclassifier.ulp.LanguageProfileAnalyzer#getFrequentLanguages(int)}.
69    */
70 
71   /**
72    * A colon(:) separated string that specifies the default entities types for generateLinks when
73    * hint is not given.
74    */
75   @VisibleForTesting static final String ENTITY_LIST_DEFAULT = "entity_list_default";
76   /**
77    * A colon(:) separated string that specifies the default entities types for generateLinks when
78    * the text is in a not editable UI widget.
79    */
80   private static final String ENTITY_LIST_NOT_EDITABLE = "entity_list_not_editable";
81   /**
82    * A colon(:) separated string that specifies the default entities types for generateLinks when
83    * the text is in an editable UI widget.
84    */
85   private static final String ENTITY_LIST_EDITABLE = "entity_list_editable";
86   /**
87    * A colon(:) separated string that specifies the default action types for
88    * suggestConversationActions when the suggestions are used in an app.
89    */
90   private static final String IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT =
91       "in_app_conversation_action_types_default";
92   /**
93    * A colon(:) separated string that specifies the default action types for
94    * suggestConversationActions when the suggestions are used in a notification.
95    */
96   private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT =
97       "notification_conversation_action_types_default";
98   /** Threshold to accept a suggested language from LangID model. */
99   @VisibleForTesting static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override";
100   /** Whether to enable {@link com.android.textclassifier.intent.TemplateIntentFactory}. */
101   @VisibleForTesting
102   static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled";
103   /** Whether to enable "translate" action in classifyText. */
104   private static final String TRANSLATE_IN_CLASSIFICATION_ENABLED =
105       "translate_in_classification_enabled";
106   /**
107    * Whether to detect the languages of the text in request by using langId for the native model.
108    */
109   private static final String DETECT_LANGUAGES_FROM_TEXT_ENABLED =
110       "detect_languages_from_text_enabled";
111 
112   /** Whether to enable model downloading with ModelDownloadManager */
113   @VisibleForTesting
114   public static final String MODEL_DOWNLOAD_MANAGER_ENABLED = "model_download_manager_enabled";
115   /** Type of network to download model manifest. A String value of androidx.work.NetworkType. */
116   private static final String MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE =
117       "manifest_download_required_network_type";
118   /** Max attempts allowed for a single ModelDownloader downloading task. */
119   @VisibleForTesting
120   static final String MODEL_DOWNLOAD_MAX_ATTEMPTS = "model_download_max_attempts";
121 
122   @VisibleForTesting
123   static final String MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS =
124       "model_download_backoff_delay_in_millis";
125   /** Flag name for manifest url is dynamically formatted based on model type and model language. */
126   @VisibleForTesting public static final String MANIFEST_URL_TEMPLATE = "manifest_url_%s_%s";
127   /** Sampling rate for TextClassifier API logging. */
128   static final String TEXTCLASSIFIER_API_LOG_SAMPLE_RATE = "textclassifier_api_log_sample_rate";
129 
130   /** The size of the cache of the mapping of session id to text classification context. */
131   private static final String SESSION_ID_TO_CONTEXT_CACHE_SIZE = "session_id_to_context_cache_size";
132 
133   /**
134    * A colon(:) separated string that specifies the configuration to use when including surrounding
135    * context text in language detection queries.
136    *
137    * <p>Format= minimumTextSize<int>:penalizeRatio<float>:textScoreRatio<float>
138    *
139    * <p>e.g. 20:1.0:0.4
140    *
141    * <p>Accept all text lengths with minimumTextSize=0
142    *
143    * <p>Reject all text less than minimumTextSize with penalizeRatio=0
144    *
145    * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference.
146    */
147   @VisibleForTesting static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings";
148   /** Default threshold to translate the language of the context the user selects */
149   private static final String TRANSLATE_ACTION_THRESHOLD = "translate_action_threshold";
150 
151   // Sync this with ConversationAction.TYPE_ADD_CONTACT;
152   public static final String TYPE_ADD_CONTACT = "add_contact";
153   // Sync this with ConversationAction.COPY;
154   public static final String TYPE_COPY = "copy";
155 
156   private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
157   private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
158   private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
159   private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100;
160 
161   private static final ImmutableList<String> ENTITY_LIST_DEFAULT_VALUE =
162       ImmutableList.of(
163           TextClassifier.TYPE_ADDRESS,
164           TextClassifier.TYPE_EMAIL,
165           TextClassifier.TYPE_PHONE,
166           TextClassifier.TYPE_URL,
167           TextClassifier.TYPE_DATE,
168           TextClassifier.TYPE_DATE_TIME,
169           TextClassifier.TYPE_FLIGHT_NUMBER);
170   private static final ImmutableList<String> CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES =
171       ImmutableList.of(
172           ConversationAction.TYPE_TEXT_REPLY,
173           ConversationAction.TYPE_CREATE_REMINDER,
174           ConversationAction.TYPE_CALL_PHONE,
175           ConversationAction.TYPE_OPEN_URL,
176           ConversationAction.TYPE_SEND_EMAIL,
177           ConversationAction.TYPE_SEND_SMS,
178           ConversationAction.TYPE_TRACK_FLIGHT,
179           ConversationAction.TYPE_VIEW_CALENDAR,
180           ConversationAction.TYPE_VIEW_MAP,
181           TYPE_ADD_CONTACT,
182           TYPE_COPY);
183   /**
184    * < 0 : Not set. Use value from LangId model. 0 - 1: Override value in LangId model.
185    *
186    * @see EntityConfidence
187    */
188   private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f;
189 
190   private static final float TRANSLATE_ACTION_THRESHOLD_DEFAULT = 0.5f;
191 
192   private static final boolean USER_LANGUAGE_PROFILE_ENABLED_DEFAULT = true;
193   private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true;
194   private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true;
195   private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true;
196   private static final boolean MODEL_DOWNLOAD_MANAGER_ENABLED_DEFAULT = false;
197   private static final String MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE_DEFAULT = "UNMETERED";
198   private static final int MODEL_DOWNLOAD_MAX_ATTEMPTS_DEFAULT = 5;
199   private static final long MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS_DEFAULT = HOURS.toMillis(1);
200   private static final String MANIFEST_URL_DEFAULT = "";
201   private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f};
202   /**
203    * Sampling rate for API logging. For example, 100 means there is a 0.01 chance that the API call
204    * is the logged.
205    */
206   private static final int TEXTCLASSIFIER_API_LOG_SAMPLE_RATE_DEFAULT = 10;
207 
208   private static final int SESSION_ID_TO_CONTEXT_CACHE_SIZE_DEFAULT = 10;
209 
210   // TODO(licha): Consider removing this. We can use real device config for testing.
211   /** DeviceConfig interface to facilitate testing. */
212   @VisibleForTesting
213   public interface IDeviceConfig {
getProperties(@onNull String namespace, @NonNull String... names)214     default Properties getProperties(@NonNull String namespace, @NonNull String... names) {
215       return new Properties.Builder(namespace).build();
216     }
217 
getInt(@onNull String namespace, @NonNull String name, @NonNull int defaultValue)218     default int getInt(@NonNull String namespace, @NonNull String name, @NonNull int defaultValue) {
219       return defaultValue;
220     }
221 
getLong( @onNull String namespace, @NonNull String name, @NonNull long defaultValue)222     default long getLong(
223         @NonNull String namespace, @NonNull String name, @NonNull long defaultValue) {
224       return defaultValue;
225     }
226 
getFloat( @onNull String namespace, @NonNull String name, @NonNull float defaultValue)227     default float getFloat(
228         @NonNull String namespace, @NonNull String name, @NonNull float defaultValue) {
229       return defaultValue;
230     }
231 
getString( @onNull String namespace, @NonNull String name, @Nullable String defaultValue)232     default String getString(
233         @NonNull String namespace, @NonNull String name, @Nullable String defaultValue) {
234       return defaultValue;
235     }
236 
getBoolean( @onNull String namespace, @NonNull String name, boolean defaultValue)237     default boolean getBoolean(
238         @NonNull String namespace, @NonNull String name, boolean defaultValue) {
239       return defaultValue;
240     }
241   }
242 
243   private static final IDeviceConfig DEFAULT_DEVICE_CONFIG =
244       new IDeviceConfig() {
245         @Override
246         public Properties getProperties(@NonNull String namespace, @NonNull String... names) {
247           return DeviceConfig.getProperties(namespace, names);
248         }
249 
250         @Override
251         public int getInt(
252             @NonNull String namespace, @NonNull String name, @NonNull int defaultValue) {
253           return DeviceConfig.getInt(namespace, name, defaultValue);
254         }
255 
256         @Override
257         public long getLong(
258             @NonNull String namespace, @NonNull String name, @NonNull long defaultValue) {
259           return DeviceConfig.getLong(namespace, name, defaultValue);
260         }
261 
262         @Override
263         public float getFloat(
264             @NonNull String namespace, @NonNull String name, @NonNull float defaultValue) {
265           return DeviceConfig.getFloat(namespace, name, defaultValue);
266         }
267 
268         @Override
269         public String getString(
270             @NonNull String namespace, @NonNull String name, @NonNull String defaultValue) {
271           return DeviceConfig.getString(namespace, name, defaultValue);
272         }
273 
274         @Override
275         public boolean getBoolean(
276             @NonNull String namespace, @NonNull String name, @NonNull boolean defaultValue) {
277           return DeviceConfig.getBoolean(namespace, name, defaultValue);
278         }
279       };
280 
281   private final IDeviceConfig deviceConfig;
282 
TextClassifierSettings()283   public TextClassifierSettings() {
284     this(DEFAULT_DEVICE_CONFIG);
285   }
286 
287   @VisibleForTesting
TextClassifierSettings(IDeviceConfig deviceConfig)288   public TextClassifierSettings(IDeviceConfig deviceConfig) {
289     this.deviceConfig = deviceConfig;
290   }
291 
getSuggestSelectionMaxRangeLength()292   public int getSuggestSelectionMaxRangeLength() {
293     return deviceConfig.getInt(
294         NAMESPACE, SUGGEST_SELECTION_MAX_RANGE_LENGTH, SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
295   }
296 
getClassifyTextMaxRangeLength()297   public int getClassifyTextMaxRangeLength() {
298     return deviceConfig.getInt(
299         NAMESPACE, CLASSIFY_TEXT_MAX_RANGE_LENGTH, CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
300   }
301 
getGenerateLinksMaxTextLength()302   public int getGenerateLinksMaxTextLength() {
303     return deviceConfig.getInt(
304         NAMESPACE, GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
305   }
306 
getGenerateLinksLogSampleRate()307   public int getGenerateLinksLogSampleRate() {
308     return deviceConfig.getInt(
309         NAMESPACE, GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
310   }
311 
getEntityListDefault()312   public List<String> getEntityListDefault() {
313     return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE);
314   }
315 
getEntityListNotEditable()316   public List<String> getEntityListNotEditable() {
317     return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
318   }
319 
getEntityListEditable()320   public List<String> getEntityListEditable() {
321     return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
322   }
323 
getInAppConversationActionTypes()324   public List<String> getInAppConversationActionTypes() {
325     return getDeviceConfigStringList(
326         IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
327   }
328 
getNotificationConversationActionTypes()329   public List<String> getNotificationConversationActionTypes() {
330     return getDeviceConfigStringList(
331         NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
332   }
333 
getLangIdThresholdOverride()334   public float getLangIdThresholdOverride() {
335     return deviceConfig.getFloat(
336         NAMESPACE, LANG_ID_THRESHOLD_OVERRIDE, LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
337   }
338 
getTranslateActionThreshold()339   public float getTranslateActionThreshold() {
340     return deviceConfig.getFloat(
341         NAMESPACE, TRANSLATE_ACTION_THRESHOLD, TRANSLATE_ACTION_THRESHOLD_DEFAULT);
342   }
343 
isUserLanguageProfileEnabled()344   public boolean isUserLanguageProfileEnabled() {
345     return deviceConfig.getBoolean(
346         NAMESPACE, USER_LANGUAGE_PROFILE_ENABLED, USER_LANGUAGE_PROFILE_ENABLED_DEFAULT);
347   }
348 
isTemplateIntentFactoryEnabled()349   public boolean isTemplateIntentFactoryEnabled() {
350     return deviceConfig.getBoolean(
351         NAMESPACE, TEMPLATE_INTENT_FACTORY_ENABLED, TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
352   }
353 
isTranslateInClassificationEnabled()354   public boolean isTranslateInClassificationEnabled() {
355     return deviceConfig.getBoolean(
356         NAMESPACE,
357         TRANSLATE_IN_CLASSIFICATION_ENABLED,
358         TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT);
359   }
360 
isDetectLanguagesFromTextEnabled()361   public boolean isDetectLanguagesFromTextEnabled() {
362     return deviceConfig.getBoolean(
363         NAMESPACE, DETECT_LANGUAGES_FROM_TEXT_ENABLED, DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT);
364   }
365 
getLangIdContextSettings()366   public float[] getLangIdContextSettings() {
367     return getDeviceConfigFloatArray(LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT);
368   }
369 
isModelDownloadManagerEnabled()370   public boolean isModelDownloadManagerEnabled() {
371     return deviceConfig.getBoolean(
372         NAMESPACE, MODEL_DOWNLOAD_MANAGER_ENABLED, MODEL_DOWNLOAD_MANAGER_ENABLED_DEFAULT);
373   }
374 
375   /** Returns a string which represents a androidx.work.NetworkType enum. */
getManifestDownloadRequiredNetworkType()376   public String getManifestDownloadRequiredNetworkType() {
377     return deviceConfig.getString(
378         NAMESPACE,
379         MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE,
380         MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE_DEFAULT);
381   }
382 
getModelDownloadMaxAttempts()383   public int getModelDownloadMaxAttempts() {
384     return deviceConfig.getInt(
385         NAMESPACE, MODEL_DOWNLOAD_MAX_ATTEMPTS, MODEL_DOWNLOAD_MAX_ATTEMPTS_DEFAULT);
386   }
387 
getModelDownloadBackoffDelayInMillis()388   public long getModelDownloadBackoffDelayInMillis() {
389     return deviceConfig.getLong(
390         NAMESPACE,
391         MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS,
392         MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS_DEFAULT);
393   }
394 
395   /**
396    * Get model's manifest url for given model type and language.
397    *
398    * @param modelType the type of model for the target url
399    * @param modelLanguageTag the language tag for the model (e.g. en), but can also be "universal"
400    * @return DeviceConfig configured url or empty string if not set
401    */
getManifestURL(@odelType.ModelTypeDef String modelType, String modelLanguageTag)402   public String getManifestURL(@ModelType.ModelTypeDef String modelType, String modelLanguageTag) {
403     // E.g: manifest_url_annotator_zh, manifest_url_lang_id_universal,
404     // manifest_url_actions_suggestions_en
405     String urlFlagName = String.format(MANIFEST_URL_TEMPLATE, modelType, modelLanguageTag);
406     return deviceConfig.getString(NAMESPACE, urlFlagName, MANIFEST_URL_DEFAULT);
407   }
408 
409   /**
410    * Gets all language variants configured for a specific ModelType.
411    *
412    * <p>For a specific language, there can be many variants: de-CH, de-LI, zh-Hans, zh-Hant. There
413    * is no easy way to hardcode the list in client. Therefore, we parse all configured flag's name
414    * in DeviceConfig, and let the client to choose the best variant to download.
415    */
getLanguageTagsForManifestURL( @odelType.ModelTypeDef String modelType)416   public ImmutableList<String> getLanguageTagsForManifestURL(
417       @ModelType.ModelTypeDef String modelType) {
418     String urlFlagBaseName = String.format(MANIFEST_URL_TEMPLATE, modelType, /* language */ "");
419     Properties properties = deviceConfig.getProperties(NAMESPACE);
420     ImmutableList.Builder<String> variantsBuilder = ImmutableList.builder();
421     for (String name : properties.getKeyset()) {
422       if (name.startsWith(urlFlagBaseName)
423           && properties.getString(name, /* defaultValue= */ null) != null) {
424         variantsBuilder.add(name.substring(urlFlagBaseName.length()));
425       }
426     }
427     return variantsBuilder.build();
428   }
429 
getTextClassifierApiLogSampleRate()430   public int getTextClassifierApiLogSampleRate() {
431     return deviceConfig.getInt(
432         NAMESPACE, TEXTCLASSIFIER_API_LOG_SAMPLE_RATE, TEXTCLASSIFIER_API_LOG_SAMPLE_RATE_DEFAULT);
433   }
434 
getSessionIdToContextCacheSize()435   public int getSessionIdToContextCacheSize() {
436     return deviceConfig.getInt(
437         NAMESPACE, SESSION_ID_TO_CONTEXT_CACHE_SIZE, SESSION_ID_TO_CONTEXT_CACHE_SIZE_DEFAULT);
438   }
439 
dump(IndentingPrintWriter pw)440   public void dump(IndentingPrintWriter pw) {
441     pw.println("TextClassifierSettings:");
442     pw.increaseIndent();
443     pw.printPair(CLASSIFY_TEXT_MAX_RANGE_LENGTH, getClassifyTextMaxRangeLength());
444     pw.printPair(DETECT_LANGUAGES_FROM_TEXT_ENABLED, isDetectLanguagesFromTextEnabled());
445     pw.printPair(ENTITY_LIST_DEFAULT, getEntityListDefault());
446     pw.printPair(ENTITY_LIST_EDITABLE, getEntityListEditable());
447     pw.printPair(ENTITY_LIST_NOT_EDITABLE, getEntityListNotEditable());
448     pw.printPair(GENERATE_LINKS_LOG_SAMPLE_RATE, getGenerateLinksLogSampleRate());
449     pw.printPair(GENERATE_LINKS_MAX_TEXT_LENGTH, getGenerateLinksMaxTextLength());
450     pw.printPair(IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, getInAppConversationActionTypes());
451     pw.printPair(LANG_ID_CONTEXT_SETTINGS, Arrays.toString(getLangIdContextSettings()));
452     pw.printPair(LANG_ID_THRESHOLD_OVERRIDE, getLangIdThresholdOverride());
453     pw.printPair(TRANSLATE_ACTION_THRESHOLD, getTranslateActionThreshold());
454     pw.printPair(
455         NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, getNotificationConversationActionTypes());
456     pw.printPair(SUGGEST_SELECTION_MAX_RANGE_LENGTH, getSuggestSelectionMaxRangeLength());
457     pw.printPair(USER_LANGUAGE_PROFILE_ENABLED, isUserLanguageProfileEnabled());
458     pw.printPair(TEMPLATE_INTENT_FACTORY_ENABLED, isTemplateIntentFactoryEnabled());
459     pw.printPair(TRANSLATE_IN_CLASSIFICATION_ENABLED, isTranslateInClassificationEnabled());
460     pw.printPair(MODEL_DOWNLOAD_MANAGER_ENABLED, isModelDownloadManagerEnabled());
461     pw.printPair(MODEL_DOWNLOAD_MAX_ATTEMPTS, getModelDownloadMaxAttempts());
462     pw.decreaseIndent();
463     pw.printPair(TEXTCLASSIFIER_API_LOG_SAMPLE_RATE, getTextClassifierApiLogSampleRate());
464     pw.printPair(SESSION_ID_TO_CONTEXT_CACHE_SIZE, getSessionIdToContextCacheSize());
465     pw.decreaseIndent();
466   }
467 
getDeviceConfigStringList(String key, List<String> defaultValue)468   private List<String> getDeviceConfigStringList(String key, List<String> defaultValue) {
469     return parse(deviceConfig.getString(NAMESPACE, key, null), defaultValue);
470   }
471 
getDeviceConfigFloatArray(String key, float[] defaultValue)472   private float[] getDeviceConfigFloatArray(String key, float[] defaultValue) {
473     return parse(deviceConfig.getString(NAMESPACE, key, null), defaultValue);
474   }
475 
parse(@ullable String listStr, List<String> defaultValue)476   private static List<String> parse(@Nullable String listStr, List<String> defaultValue) {
477     if (listStr != null) {
478       return Collections.unmodifiableList(Arrays.asList(listStr.split(DELIMITER)));
479     }
480     return defaultValue;
481   }
482 
parse(@ullable String arrayStr, float[] defaultValue)483   private static float[] parse(@Nullable String arrayStr, float[] defaultValue) {
484     if (arrayStr != null) {
485       final List<String> split = Splitter.onPattern(DELIMITER).splitToList(arrayStr);
486       if (split.size() != defaultValue.length) {
487         return defaultValue;
488       }
489       final float[] result = new float[split.size()];
490       for (int i = 0; i < split.size(); i++) {
491         try {
492           result[i] = Float.parseFloat(split.get(i));
493         } catch (NumberFormatException e) {
494           return defaultValue;
495         }
496       }
497       return result;
498     } else {
499       return defaultValue;
500     }
501   }
502 }
503