Add flags to control generation of Smart Suggestions in Notifications

Add flags to the notification_assistant DeviceConfig namespace to
control smart suggestions generation for notifications.

The existing flag in Settings.Global
SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS will be removed in a follow-up.

Bug: 120792826
Test: atest AssistantSettingsTest
Test: play around with 'adb shell device_config put
notification_assistant notification_assistant_generate_replies X', and
'adb shell device_config put
notification_assistant notification_assistant_generate_actions X', where
X is false or true.
Change-Id: I0252bff48f053aab4b81d74147579416b28c515a
diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
index 39a1676..406b44d 100644
--- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -20,8 +20,11 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -29,6 +32,7 @@
  * Observes the settings for {@link Assistant}.
  */
 final class AssistantSettings extends ContentObserver {
+    private static final String LOG_TAG = "AssistantSettings";
     public static Factory FACTORY = AssistantSettings::createAndRegister;
     private static final boolean DEFAULT_GENERATE_REPLIES = true;
     private static final boolean DEFAULT_GENERATE_ACTIONS = true;
@@ -39,19 +43,33 @@
     private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
             Settings.Global.getUriFor(
                     Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
-    private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
-            Settings.Global.getUriFor(
-                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
     private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
             Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
 
-    private static final String KEY_GENERATE_REPLIES = "generate_replies";
-    private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+    /**
+     * Flag determining whether the Notification Assistant should generate replies for
+     * notifications.
+     * <p>
+     * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+     */
+    @VisibleForTesting
+    static final String KEY_GENERATE_REPLIES = "notification_assistant_generate_replies";
+
+    /**
+     * Flag determining whether the Notification Assistant should generate contextual actions in
+     * notifications.
+     * <p>
+     * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+     */
+    @VisibleForTesting
+    static final String KEY_GENERATE_ACTIONS = "notification_assistant_generate_actions";
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final ContentResolver mResolver;
     private final int mUserId;
 
+    private final Handler mHandler;
+
     @VisibleForTesting
     protected final Runnable mOnUpdateRunnable;
 
@@ -65,6 +83,7 @@
     private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
             Runnable onUpdateRunnable) {
         super(handler);
+        mHandler = handler;
         mResolver = resolver;
         mUserId = userId;
         mOnUpdateRunnable = onUpdateRunnable;
@@ -75,6 +94,7 @@
         AssistantSettings assistantSettings =
                 new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
         assistantSettings.register();
+        assistantSettings.registerDeviceConfigs();
         return assistantSettings;
     }
 
@@ -91,13 +111,62 @@
         mResolver.registerContentObserver(
                 DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
         mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
-        mResolver.registerContentObserver(
-                SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
 
         // Update all uris on creation.
         update(null);
     }
 
+    private void registerDeviceConfigs() {
+        DeviceConfig.addOnPropertyChangedListener(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                this::postToHandler,
+                this::onDeviceConfigPropertyChanged);
+
+        // Update the fields in this class from the current state of the device config.
+        updateFromDeviceConfigFlags();
+    }
+
+    private void postToHandler(Runnable r) {
+        this.mHandler.post(r);
+    }
+
+    @VisibleForTesting
+    void onDeviceConfigPropertyChanged(String namespace, String name, String value) {
+        if (!DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT.equals(namespace)) {
+            Log.e(LOG_TAG, "Received update from DeviceConfig for unrelated namespace: "
+                    + namespace + " " + name + "=" + value);
+            return;
+        }
+
+        updateFromDeviceConfigFlags();
+    }
+
+    private void updateFromDeviceConfigFlags() {
+        String generateRepliesFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                KEY_GENERATE_REPLIES);
+        if (TextUtils.isEmpty(generateRepliesFlag)) {
+            mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+        } else {
+            // parseBoolean returns false for everything that isn't 'true' so there's no need to
+            // sanitise the flag string here.
+            mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag);
+        }
+
+        String generateActionsFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                KEY_GENERATE_ACTIONS);
+        if (TextUtils.isEmpty(generateActionsFlag)) {
+            mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+        } else {
+            // parseBoolean returns false for everything that isn't 'true' so there's no need to
+            // sanitise the flag string here.
+            mGenerateActions = Boolean.parseBoolean(generateActionsFlag);
+        }
+
+        mOnUpdateRunnable.run();
+    }
+
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         update(uri);
@@ -114,15 +183,6 @@
                     mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
                     ChannelImpressions.DEFAULT_STREAK_LIMIT);
         }
-        if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
-            mParser.setString(
-                    Settings.Global.getString(mResolver,
-                            Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-            mGenerateReplies =
-                    mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
-            mGenerateActions =
-                    mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
-        }
         if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
             int mNewInterruptionModelInt = Settings.Secure.getInt(
                     mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,