Migrate to DeviceConfig in TextClassifier

ConfigParser is introduced to read the flags from DeviceConfig.
If the flag is missing, fallback to Settings.

Also, adds a new setting key: TEXT_CLASSIFIER_ACTION_MODEL_PARAMS

Test: atest frameworks/base/core/tests/coretests/src/android/view/textclassifier/
Test: adb shell cmd device_config put textclassifier system_textclassifier_enabled  false
      adb shell dumpsys textclassification, observed that the flag is updated.

BUG: 123389900
Change-Id: Icbd26ec7ed223e40b60696d12327cb123b96c4fd
diff --git a/api/system-current.txt b/api/system-current.txt
index 8dedd2c..eac3e99 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5862,6 +5862,7 @@
     field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
     field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
     field public static final String NAMESPACE_SYSTEMUI = "systemui";
+    field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
   }
 
   public static interface DeviceConfig.AttentionManagerService {
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 5d4539c..0b106d9 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -144,6 +144,14 @@
     public static final String NAMESPACE_SYSTEMUI = "systemui";
 
     /**
+     * Namespace for TextClassifier related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
+
+    /**
      * Namespace for all runtime related features.
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 45219d6..e619461 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11895,22 +11895,24 @@
          * entity_list_default use ":" as delimiter for values. Ex:
          *
          * <pre>
-         * smart_linkify_enabled                    (boolean)
-         * system_textclassifier_enabled            (boolean)
-         * model_dark_launch_enabled                (boolean)
-         * smart_selection_enabled                  (boolean)
-         * smart_text_share_enabled                 (boolean)
-         * smart_linkify_enabled                    (boolean)
-         * smart_select_animation_enabled           (boolean)
-         * suggest_selection_max_range_length       (int)
-         * classify_text_max_range_length           (int)
-         * generate_links_max_text_length           (int)
-         * generate_links_log_sample_rate           (int)
-         * entity_list_default                      (String[])
-         * entity_list_not_editable                 (String[])
-         * entity_list_editable                     (String[])
-         * lang_id_threshold_override               (float)
-         * template_intent_factory_enabled          (boolean)
+         * smart_linkify_enabled                            (boolean)
+         * system_textclassifier_enabled                    (boolean)
+         * model_dark_launch_enabled                        (boolean)
+         * smart_selection_enabled                          (boolean)
+         * smart_text_share_enabled                         (boolean)
+         * smart_linkify_enabled                            (boolean)
+         * smart_select_animation_enabled                   (boolean)
+         * suggest_selection_max_range_length               (int)
+         * classify_text_max_range_length                   (int)
+         * generate_links_max_text_length                   (int)
+         * generate_links_log_sample_rate                   (int)
+         * entity_list_default                              (String[])
+         * entity_list_not_editable                         (String[])
+         * entity_list_editable                             (String[])
+         * in_app_conversation_action_types_default         (String[])
+         * notification_conversation_action_types_default   (String[])
+         * lang_id_threshold_override                       (float)
+         * template_intent_factory_enabled                  (boolean)
          * </pre>
          *
          * <p>
@@ -14565,6 +14567,14 @@
          */
         public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY =
                 "battery_charging_state_update_delay";
+
+        /**
+         * A serialized string of params that will be loaded into a text classifier action model.
+         *
+         * @hide
+         */
+        public static final String TEXT_CLASSIFIER_ACTION_MODEL_PARAMS =
+                "text_classifier_action_model_params";
     }
 
     /**
diff --git a/core/java/android/view/textclassifier/ConfigParser.java b/core/java/android/view/textclassifier/ConfigParser.java
new file mode 100644
index 0000000..8e0bdf9
--- /dev/null
+++ b/core/java/android/view/textclassifier/ConfigParser.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
+import android.util.KeyValueListParser;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Retrieves settings from {@link DeviceConfig} and {@link android.provider.Settings}.
+ * It will try DeviceConfig first and then Settings.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public final class ConfigParser {
+    private static final String TAG = "ConfigParser";
+
+    private final KeyValueListParser mParser;
+
+    public ConfigParser(@Nullable String textClassifierConstants) {
+        final KeyValueListParser parser = new KeyValueListParser(',');
+        try {
+            parser.setString(textClassifierConstants);
+        } catch (IllegalArgumentException e) {
+            // Failed to parse the settings string, log this and move on with defaults.
+            Log.w(TAG, "Bad text_classifier_constants: " + textClassifierConstants);
+        }
+        mParser = parser;
+    }
+
+    /**
+     * Reads a boolean flag.
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                key,
+                mParser.getBoolean(key, defaultValue));
+    }
+
+    /**
+     * Reads an integer flag.
+     */
+    public int getInt(String key, int defaultValue) {
+        return DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                key,
+                mParser.getInt(key, defaultValue));
+    }
+
+    /**
+     * Reads a float flag.
+     */
+    public float getFloat(String key, float defaultValue) {
+        return DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                key,
+                mParser.getFloat(key, defaultValue));
+    }
+
+    /**
+     * Reads a string flag.
+     */
+    public String getString(String key, String defaultValue) {
+        return DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                key,
+                mParser.getString(key, defaultValue));
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index 2ef8d04..125b0d3 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -17,8 +17,6 @@
 package android.view.textclassifier;
 
 import android.annotation.Nullable;
-import android.util.KeyValueListParser;
-import android.util.Slog;
 
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -32,22 +30,24 @@
  * This is encoded as a key=value list, separated by commas. Ex:
  *
  * <pre>
- * smart_linkify_enabled                    (boolean)
- * system_textclassifier_enabled            (boolean)
- * model_dark_launch_enabled                (boolean)
- * smart_selection_enabled                  (boolean)
- * smart_text_share_enabled                 (boolean)
- * smart_linkify_enabled                    (boolean)
- * smart_select_animation_enabled           (boolean)
- * suggest_selection_max_range_length       (int)
- * classify_text_max_range_length           (int)
- * generate_links_max_text_length           (int)
- * generate_links_log_sample_rate           (int)
- * entity_list_default                      (String[])
- * entity_list_not_editable                 (String[])
- * entity_list_editable                     (String[])
- * lang_id_threshold_override               (float)
- * template_intent_factory_enabled          (boolean)
+ * smart_linkify_enabled                            (boolean)
+ * system_textclassifier_enabled                    (boolean)
+ * model_dark_launch_enabled                        (boolean)
+ * smart_selection_enabled                          (boolean)
+ * smart_text_share_enabled                         (boolean)
+ * smart_linkify_enabled                            (boolean)
+ * smart_select_animation_enabled                   (boolean)
+ * suggest_selection_max_range_length               (int)
+ * classify_text_max_range_length                   (int)
+ * generate_links_max_text_length                   (int)
+ * generate_links_log_sample_rate                   (int)
+ * entity_list_default                              (String[])
+ * entity_list_not_editable                         (String[])
+ * entity_list_editable                             (String[])
+ * in_app_conversation_action_types_default         (String[])
+ * notification_conversation_action_types_default   (String[])
+ * lang_id_threshold_override                       (float)
+ * template_intent_factory_enabled                  (boolean)
  * </pre>
  *
  * <p>
@@ -61,43 +61,90 @@
  * @hide
  */
 public final class TextClassificationConstants {
-
     private static final String LOG_TAG = "TextClassificationConstants";
 
-    private static final String LOCAL_TEXT_CLASSIFIER_ENABLED =
-            "local_textclassifier_enabled";
-    private static final String SYSTEM_TEXT_CLASSIFIER_ENABLED =
-            "system_textclassifier_enabled";
-    private static final String MODEL_DARK_LAUNCH_ENABLED =
-            "model_dark_launch_enabled";
-    private static final String SMART_SELECTION_ENABLED =
-            "smart_selection_enabled";
-    private static final String SMART_TEXT_SHARE_ENABLED =
-            "smart_text_share_enabled";
-    private static final String SMART_LINKIFY_ENABLED =
-            "smart_linkify_enabled";
+    /**
+     * Whether the smart linkify feature is enabled.
+     */
+    private static final String SMART_LINKIFY_ENABLED = "smart_linkify_enabled";
+    /**
+     * Whether SystemTextClassifier is enabled.
+     */
+    private static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled";
+    /**
+     * Whether TextClassifierImpl is enabled.
+     */
+    private static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled";
+    /**
+     * Enable smart selection without a visible UI changes.
+     */
+    private static final String MODEL_DARK_LAUNCH_ENABLED = "model_dark_launch_enabled";
+
+    /**
+     * Whether the smart selection feature is enabled.
+     */
+    private static final String SMART_SELECTION_ENABLED = "smart_selection_enabled";
+    /**
+     * Whether the smart text share feature is enabled.
+     */
+    private static final String SMART_TEXT_SHARE_ENABLED = "smart_text_share_enabled";
+    /**
+     * Whether animation for smart selection is enabled.
+     */
     private static final String SMART_SELECT_ANIMATION_ENABLED =
             "smart_select_animation_enabled";
+    /**
+     * Max length of text that suggestSelection can accept.
+     */
     private static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH =
             "suggest_selection_max_range_length";
-    private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH =
-            "classify_text_max_range_length";
-    private static final String GENERATE_LINKS_MAX_TEXT_LENGTH =
-            "generate_links_max_text_length";
+    /**
+     * Max length of text that classifyText can accept.
+     */
+    private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = "classify_text_max_range_length";
+    /**
+     * Max length of text that generateLinks can accept.
+     */
+    private static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length";
+    /**
+     * Sampling rate for generateLinks logging.
+     */
     private static final String GENERATE_LINKS_LOG_SAMPLE_RATE =
             "generate_links_log_sample_rate";
-    private static final String ENTITY_LIST_DEFAULT =
-            "entity_list_default";
-    private static final String ENTITY_LIST_NOT_EDITABLE =
-            "entity_list_not_editable";
-    private static final String ENTITY_LIST_EDITABLE =
-            "entity_list_editable";
+    /**
+     * A colon(:) separated string that specifies the default entities types for
+     * generateLinks when hint is not given.
+     */
+    private static final String ENTITY_LIST_DEFAULT = "entity_list_default";
+    /**
+     * A colon(:) separated string that specifies the default entities types for
+     * generateLinks when the text is in a not editable UI widget.
+     */
+    private static final String ENTITY_LIST_NOT_EDITABLE = "entity_list_not_editable";
+    /**
+     * A colon(:) separated string that specifies the default entities types for
+     * generateLinks when the text is in an editable UI widget.
+     */
+    private static final String ENTITY_LIST_EDITABLE = "entity_list_editable";
+    /**
+     * A colon(:) separated string that specifies the default action types for
+     * suggestConversationActions when the suggestions are used in an app.
+     */
     private static final String IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT =
             "in_app_conversation_action_types_default";
+    /**
+     * A colon(:) separated string that specifies the default action types for
+     * suggestConversationActions when the suggestions are used in a notification.
+     */
     private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT =
             "notification_conversation_action_types_default";
-    private static final String LANG_ID_THRESHOLD_OVERRIDE =
-            "lang_id_threshold_override";
+    /**
+     * Threshold in classifyText to consider a text is in a foreign language.
+     */
+    private static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override";
+    /**
+     * Whether to enable {@link android.view.textclassifier.TemplateIntentFactory}.
+     */
     private static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled";
 
     private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
@@ -162,66 +209,77 @@
     private final boolean mTemplateIntentFactoryEnabled;
 
     private TextClassificationConstants(@Nullable String settings) {
-        final KeyValueListParser parser = new KeyValueListParser(',');
-        try {
-            parser.setString(settings);
-        } catch (IllegalArgumentException e) {
-            // Failed to parse the settings string, log this and move on with defaults.
-            Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings);
-        }
-        mSystemTextClassifierEnabled = parser.getBoolean(
-                SYSTEM_TEXT_CLASSIFIER_ENABLED,
-                SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
-        mLocalTextClassifierEnabled = parser.getBoolean(
-                LOCAL_TEXT_CLASSIFIER_ENABLED,
-                LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
-        mModelDarkLaunchEnabled = parser.getBoolean(
-                MODEL_DARK_LAUNCH_ENABLED,
-                MODEL_DARK_LAUNCH_ENABLED_DEFAULT);
-        mSmartSelectionEnabled = parser.getBoolean(
-                SMART_SELECTION_ENABLED,
-                SMART_SELECTION_ENABLED_DEFAULT);
-        mSmartTextShareEnabled = parser.getBoolean(
-                SMART_TEXT_SHARE_ENABLED,
-                SMART_TEXT_SHARE_ENABLED_DEFAULT);
-        mSmartLinkifyEnabled = parser.getBoolean(
-                SMART_LINKIFY_ENABLED,
-                SMART_LINKIFY_ENABLED_DEFAULT);
-        mSmartSelectionAnimationEnabled = parser.getBoolean(
-                SMART_SELECT_ANIMATION_ENABLED,
-                SMART_SELECT_ANIMATION_ENABLED_DEFAULT);
-        mSuggestSelectionMaxRangeLength = parser.getInt(
-                SUGGEST_SELECTION_MAX_RANGE_LENGTH,
-                SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
-        mClassifyTextMaxRangeLength = parser.getInt(
-                CLASSIFY_TEXT_MAX_RANGE_LENGTH,
-                CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
-        mGenerateLinksMaxTextLength = parser.getInt(
-                GENERATE_LINKS_MAX_TEXT_LENGTH,
-                GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
-        mGenerateLinksLogSampleRate = parser.getInt(
-                GENERATE_LINKS_LOG_SAMPLE_RATE,
-                GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
-        mEntityListDefault = parseStringList(parser.getString(
+        ConfigParser configParser = new ConfigParser(settings);
+        mSystemTextClassifierEnabled =
+                configParser.getBoolean(
+                        SYSTEM_TEXT_CLASSIFIER_ENABLED,
+                        SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
+        mLocalTextClassifierEnabled =
+                configParser.getBoolean(
+                        LOCAL_TEXT_CLASSIFIER_ENABLED,
+                        LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
+        mModelDarkLaunchEnabled =
+                configParser.getBoolean(
+                        MODEL_DARK_LAUNCH_ENABLED,
+                        MODEL_DARK_LAUNCH_ENABLED_DEFAULT);
+        mSmartSelectionEnabled =
+                configParser.getBoolean(
+                        SMART_SELECTION_ENABLED,
+                        SMART_SELECTION_ENABLED_DEFAULT);
+        mSmartTextShareEnabled =
+                configParser.getBoolean(
+                        SMART_TEXT_SHARE_ENABLED,
+                        SMART_TEXT_SHARE_ENABLED_DEFAULT);
+        mSmartLinkifyEnabled =
+                configParser.getBoolean(
+                        SMART_LINKIFY_ENABLED,
+                        SMART_LINKIFY_ENABLED_DEFAULT);
+        mSmartSelectionAnimationEnabled =
+                configParser.getBoolean(
+                        SMART_SELECT_ANIMATION_ENABLED,
+                        SMART_SELECT_ANIMATION_ENABLED_DEFAULT);
+        mSuggestSelectionMaxRangeLength =
+                configParser.getInt(
+                        SUGGEST_SELECTION_MAX_RANGE_LENGTH,
+                        SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
+        mClassifyTextMaxRangeLength =
+                configParser.getInt(
+                        CLASSIFY_TEXT_MAX_RANGE_LENGTH,
+                        CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
+        mGenerateLinksMaxTextLength =
+                configParser.getInt(
+                        GENERATE_LINKS_MAX_TEXT_LENGTH,
+                        GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
+        mGenerateLinksLogSampleRate =
+                configParser.getInt(
+                        GENERATE_LINKS_LOG_SAMPLE_RATE,
+                        GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
+        mEntityListDefault = parseStringList(configParser.getString(
                 ENTITY_LIST_DEFAULT,
                 ENTITY_LIST_DEFAULT_VALUE));
-        mEntityListNotEditable = parseStringList(parser.getString(
-                ENTITY_LIST_NOT_EDITABLE,
-                ENTITY_LIST_DEFAULT_VALUE));
-        mEntityListEditable = parseStringList(parser.getString(
-                ENTITY_LIST_EDITABLE,
-                ENTITY_LIST_DEFAULT_VALUE));
-        mInAppConversationActionTypesDefault = parseStringList(parser.getString(
-                IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
-                CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
-        mNotificationConversationActionTypesDefault = parseStringList(parser.getString(
-                NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT,
-                CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
-        mLangIdThresholdOverride = parser.getFloat(
-                LANG_ID_THRESHOLD_OVERRIDE,
-                LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
-        mTemplateIntentFactoryEnabled = parser.getBoolean(
-                TEMPLATE_INTENT_FACTORY_ENABLED, TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
+        mEntityListNotEditable = parseStringList(
+                configParser.getString(
+                        ENTITY_LIST_NOT_EDITABLE,
+                        ENTITY_LIST_DEFAULT_VALUE));
+        mEntityListEditable = parseStringList(
+                configParser.getString(
+                        ENTITY_LIST_EDITABLE,
+                        ENTITY_LIST_DEFAULT_VALUE));
+        mInAppConversationActionTypesDefault = parseStringList(
+                configParser.getString(
+                        IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
+                        CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
+        mNotificationConversationActionTypesDefault = parseStringList(
+                configParser.getString(
+                        NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT,
+                        CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
+        mLangIdThresholdOverride =
+                configParser.getFloat(
+                        LANG_ID_THRESHOLD_OVERRIDE,
+                        LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
+        mTemplateIntentFactoryEnabled = configParser.getBoolean(
+                TEMPLATE_INTENT_FACTORY_ENABLED,
+                TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
     }
 
     /** Load from a settings string. */
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index d047c55..868cbb1 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -20,9 +20,11 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.ServiceManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.textclassifier.TextClassifierService;
 import android.view.textclassifier.TextClassifier.TextClassifierType;
@@ -195,6 +197,7 @@
             if (mSettingsObserver != null) {
                 getApplicationContext().getContentResolver()
                         .unregisterContentObserver(mSettingsObserver);
+                DeviceConfig.removeOnPropertyChangedListener(mSettingsObserver);
             }
         } finally {
             super.finalize();
@@ -277,7 +280,8 @@
         }
     }
 
-    private static final class SettingsObserver extends ContentObserver {
+    private static final class SettingsObserver extends ContentObserver
+            implements DeviceConfig.OnPropertyChangedListener {
 
         private final WeakReference<TextClassificationManager> mTcm;
 
@@ -288,10 +292,23 @@
                     Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS),
                     false /* notifyForDescendants */,
                     this);
+            DeviceConfig.addOnPropertyChangedListener(
+                    DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                    ActivityThread.currentApplication().getMainExecutor(),
+                    this);
         }
 
         @Override
         public void onChange(boolean selfChange) {
+            invalidateSettings();
+        }
+
+        @Override
+        public void onPropertyChanged(String namespace, String name, String value) {
+            invalidateSettings();
+        }
+
+        private void invalidateSettings() {
             final TextClassificationManager tcm = mTcm.get();
             if (tcm != null) {
                 tcm.invalidate();
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index c1cbd52..62df6e7 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -1043,7 +1043,9 @@
 
     optional SettingProto app_ops_constants = 148 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
+    optional SettingProto text_classifier_action_model_params = 145 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 145 then 149; // (145 was removed)
+    // Next tag = 149;
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ebc6be7..3da7f72 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -467,6 +467,7 @@
                     Settings.Global.TETHER_SUPPORTED,
                     Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER,
                     Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
+                    Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS,
                     Settings.Global.THEATER_MODE_ON,
                     Settings.Global.TIME_ONLY_MODE_CONSTANTS,
                     Settings.Global.TRANSITION_ANIMATION_SCALE,
diff --git a/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java b/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java
new file mode 100644
index 0000000..1b3c724
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ConfigParserTest {
+    private static final String SETTINGS = "int=42,float=12.3,boolean=true,string=abc";
+    private static final String CLEAR_DEVICE_CONFIG_KEY_CMD =
+            "device_config delete " + DeviceConfig.NAMESPACE_TEXTCLASSIFIER;
+    private static final String[] DEVICE_CONFIG_KEYS = new String[]{
+            "boolean",
+            "string",
+            "int",
+            "float"
+    };
+
+    private ConfigParser mConfigParser;
+
+    @Before
+    public void setup() throws IOException {
+        mConfigParser = new ConfigParser(SETTINGS);
+        clearDeviceConfig();
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        clearDeviceConfig();
+    }
+
+    @Test
+    public void getBoolean_deviceConfig() {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                "boolean",
+                "false",
+                false);
+        boolean value = mConfigParser.getBoolean("boolean", true);
+        assertThat(value).isFalse();
+    }
+
+    @Test
+    public void getBoolean_settings() {
+        boolean value = mConfigParser.getBoolean(
+                "boolean",
+                false);
+        assertThat(value).isTrue();
+    }
+
+    @Test
+    public void getInt_deviceConfig() {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                "int",
+                "1",
+                false);
+        int value = mConfigParser.getInt("int", 0);
+        assertThat(value).isEqualTo(1);
+    }
+
+    @Test
+    public void getInt_settings() {
+        int value = mConfigParser.getInt("int", 0);
+        assertThat(value).isEqualTo(42);
+    }
+
+    @Test
+    public void getFloat_deviceConfig() {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                "float",
+                "3.14",
+                false);
+        float value = mConfigParser.getFloat("float", 0);
+        assertThat(value).isWithin(0.0001f).of(3.14f);
+    }
+
+    @Test
+    public void getFloat_settings() {
+        float value = mConfigParser.getFloat("float", 0);
+        assertThat(value).isWithin(0.0001f).of(12.3f);
+    }
+
+    @Test
+    public void getString_deviceConfig() {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                "string",
+                "hello",
+                false);
+        String value = mConfigParser.getString("string", "");
+        assertThat(value).isEqualTo("hello");
+    }
+
+    @Test
+    public void getString_settings() {
+        String value = mConfigParser.getString("string", "");
+        assertThat(value).isEqualTo("abc");
+    }
+
+    private static void clearDeviceConfig() throws IOException {
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        for (String key : DEVICE_CONFIG_KEYS) {
+            uiDevice.executeShellCommand(CLEAR_DEVICE_CONFIG_KEY_CMD + " " + key);
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 0f8fd92..f7f34f6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1382,6 +1382,9 @@
                 Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
                 GlobalSettingsProto.TEXT_CLASSIFIER_CONSTANTS);
         dumpSetting(s, p,
+                Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS,
+                GlobalSettingsProto.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS);
+        dumpSetting(s, p,
                 Settings.Global.THEATER_MODE_ON,
                 GlobalSettingsProto.THEATER_MODE_ON);
         dumpSetting(s, p,