Add reset and list for device config flags to SettingsProvider to
support command line debug tool.

Test: atest FrameworksCoreTests:SettingsProviderTest
      Further tested manually via command line (see ag/5613024)
Bug:109919982
Bug:113101834
Change-Id: Ib0d9e4c6d806ec3521ac49b8c05fbdad8b5b13d7
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c297ef4..4e11d6a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1680,6 +1680,11 @@
      */
     public static final String CALL_METHOD_TAG_KEY = "_tag";
 
+    /**
+     * @hide - String argument extra to the fast-path call()-based requests
+     */
+    public static final String CALL_METHOD_PREFIX_KEY = "_prefix";
+
     /** @hide - Private call() method to write to 'system' table */
     public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
 
@@ -1701,15 +1706,18 @@
     /** @hide - Private call() method to delete from the 'global' table */
     public static final String CALL_METHOD_DELETE_GLOBAL = "DELETE_global";
 
+    /** @hide - Private call() method to reset to defaults the 'configuration' table */
+    public static final String CALL_METHOD_DELETE_CONFIG = "DELETE_config";
+
+    /** @hide - Private call() method to reset to defaults the 'secure' table */
+    public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
+
     /** @hide - Private call() method to reset to defaults the 'global' table */
     public static final String CALL_METHOD_RESET_GLOBAL = "RESET_global";
 
     /** @hide - Private call() method to reset to defaults the 'configuration' table */
     public static final String CALL_METHOD_RESET_CONFIG = "RESET_config";
 
-    /** @hide - Private call() method to reset to defaults the 'secure' table */
-    public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
-
     /** @hide - Private call() method to query the 'system' table */
     public static final String CALL_METHOD_LIST_SYSTEM = "LIST_system";
 
@@ -1719,6 +1727,9 @@
     /** @hide - Private call() method to query the 'global' table */
     public static final String CALL_METHOD_LIST_GLOBAL = "LIST_global";
 
+    /** @hide - Private call() method to reset to defaults the 'configuration' table */
+    public static final String CALL_METHOD_LIST_CONFIG = "LIST_config";
+
     /**
      * Activity Extra: Limit available options in launched activity based on the given authority.
      * <p>
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 108585d..04e8802 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -16,6 +16,10 @@
 
 package android.provider;
 
+import static org.hamcrest.Matchers.aMapWithSize;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertThat;
+
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -26,6 +30,7 @@
 import android.content.pm.UserInfo;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.AndroidTestCase;
@@ -33,10 +38,19 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.test.suitebuilder.annotation.Suppress;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /** Unit test for SettingsProvider. */
 public class SettingsProviderTest extends AndroidTestCase {
+    /**
+     * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
+     *     API.
+     */
+    private static final Uri CONFIG_CONTENT_URI =
+            Uri.parse("content://" + Settings.AUTHORITY + "/config");
+
     @MediumTest
     public void testNameValueCache() {
         ContentResolver r = getContext().getContentResolver();
@@ -379,4 +393,109 @@
 
         assertTrue(ssaid.equals(ssaid2));
     }
+
+    @MediumTest
+    public void testCall_putAndGetConfig() {
+        ContentResolver r = getContext().getContentResolver();
+        String name = "key1";
+        String value = "value1";
+        String newValue = "value2";
+        Bundle args = new Bundle();
+        args.putString(Settings.NameValueTable.VALUE, value);
+
+        try {
+            // value is empty
+            Bundle results =
+                    r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            assertNull(results.get(Settings.NameValueTable.VALUE));
+
+            // save value
+            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            assertNull(results);
+
+            // value is no longer empty
+            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            assertEquals(value, results.get(Settings.NameValueTable.VALUE));
+
+            // save new value
+            args.putString(Settings.NameValueTable.VALUE, newValue);
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+
+            // new value is returned
+            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
+        } finally {
+            // clean up
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+        }
+    }
+
+    @MediumTest
+    public void testCall_deleteConfig() {
+        ContentResolver r = getContext().getContentResolver();
+        String name = "key1";
+        String value = "value1";
+        Bundle args = new Bundle();
+        args.putString(Settings.NameValueTable.VALUE, value);
+
+        try {
+            // save value
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+
+            // get value
+            Bundle results =
+                    r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            assertEquals(value, results.get(Settings.NameValueTable.VALUE));
+
+            // delete value
+            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+
+            // value is empty now
+            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            assertNull(results.get(Settings.NameValueTable.VALUE));
+        } finally {
+            // clean up
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+        }
+    }
+
+    @MediumTest
+    public void testCall_listConfig() {
+        ContentResolver r = getContext().getContentResolver();
+        String prefix = "foo";
+        String newPrefix = "bar";
+        String name = prefix + "/" + "key1";
+        String newName = newPrefix + "/" + "key1";
+        String value = "value1";
+        String newValue = "value2";
+        Bundle args = new Bundle();
+        args.putString(Settings.NameValueTable.VALUE, value);
+
+        try {
+            // save both values
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            args.putString(Settings.NameValueTable.VALUE, newValue);
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+
+            // list all values
+            Bundle result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+                    null, null);
+            Map<String, String> keyValueMap =
+                    (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
+            assertThat(keyValueMap.size(), greaterThanOrEqualTo(2));
+            assertEquals(value, keyValueMap.get(name));
+            assertEquals(newValue, keyValueMap.get(newName));
+
+            // list values for prefix
+            args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
+            result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+            keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
+            assertThat(keyValueMap, aMapWithSize(1));
+            assertEquals(value, keyValueMap.get(name));
+        } finally {
+            // clean up
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+        }
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 00ea45c..140a5a3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -94,6 +94,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -147,6 +148,7 @@
     private static final String TABLE_SYSTEM = "system";
     private static final String TABLE_SECURE = "secure";
     private static final String TABLE_GLOBAL = "global";
+    private static final String TABLE_CONFIG = "config";
 
     // Old tables no longer exist.
     private static final String TABLE_FAVORITES = "favorites";
@@ -414,9 +416,8 @@
 
             case Settings.CALL_METHOD_PUT_CONFIG: {
                 String value = getSettingValue(args);
-                String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
-                insertConfigSetting(name, value, tag, makeDefault, requestingUserId, false);
+                insertConfigSetting(name, value, null, makeDefault, requestingUserId, false);
                 break;
             }
 
@@ -444,8 +445,8 @@
 
             case Settings.CALL_METHOD_RESET_CONFIG: {
                 final int mode = getResetModeEnforcingPermission(args);
-                String tag = getSettingTag(args);
-                resetConfigSetting(requestingUserId, mode, tag);
+                String prefix = getSettingPrefix(args);
+                resetConfigSetting(requestingUserId, mode, prefix);
                 break;
             }
 
@@ -463,15 +464,8 @@
                 break;
             }
 
-            case Settings.CALL_METHOD_DELETE_SYSTEM: {
-                int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;
-                Bundle result = new Bundle();
-                result.putInt(RESULT_ROWS_DELETED, rows);
-                return result;
-            }
-
-            case Settings.CALL_METHOD_DELETE_SECURE: {
-                int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;
+            case Settings.CALL_METHOD_DELETE_CONFIG: {
+                int rows  = deleteConfigSetting(name, requestingUserId, false) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
@@ -484,10 +478,32 @@
                 return result;
             }
 
-            case Settings.CALL_METHOD_LIST_SYSTEM: {
+            case Settings.CALL_METHOD_DELETE_SECURE: {
+                int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;
+                Bundle result = new Bundle();
+                result.putInt(RESULT_ROWS_DELETED, rows);
+                return result;
+            }
+
+            case Settings.CALL_METHOD_DELETE_SYSTEM: {
+                int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;
+                Bundle result = new Bundle();
+                result.putInt(RESULT_ROWS_DELETED, rows);
+                return result;
+            }
+
+            case Settings.CALL_METHOD_LIST_CONFIG: {
+                String prefix = getSettingPrefix(args);
+                Bundle result = new Bundle();
+                result.putSerializable(
+                        Settings.NameValueTable.VALUE, (HashMap) getAllConfigFlags(prefix));
+                return result;
+            }
+
+            case Settings.CALL_METHOD_LIST_GLOBAL: {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
-                        buildSettingsList(getAllSystemSettings(requestingUserId, null)));
+                        buildSettingsList(getAllGlobalSettings(null)));
                 return result;
             }
 
@@ -498,10 +514,10 @@
                 return result;
             }
 
-            case Settings.CALL_METHOD_LIST_GLOBAL: {
+            case Settings.CALL_METHOD_LIST_SYSTEM: {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
-                        buildSettingsList(getAllGlobalSettings(null)));
+                        buildSettingsList(getAllSystemSettings(requestingUserId, null)));
                 return result;
             }
 
@@ -1061,36 +1077,47 @@
                 MUTATION_OPERATION_INSERT, forceNotify, 0);
     }
 
-    private void resetConfigSetting(int requestingUserId, int mode, String tag) {
+    private boolean deleteConfigSetting(String name, int requestingUserId, boolean forceNotify) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ", " + requestingUserId
+                    + ", " + forceNotify + ")");
+        }
+        return mutateConfigSetting(name, null, null, false, requestingUserId,
+                MUTATION_OPERATION_DELETE, forceNotify, 0);
+    }
+
+    private void resetConfigSetting(int requestingUserId, int mode, String prefix) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "resetConfigSetting(" + requestingUserId + ", "
-                    + mode + ", " + tag + ")");
+                    + mode + ", " + prefix + ")");
         }
-        mutateConfigSetting(null, null, tag, false, requestingUserId,
+        mutateConfigSetting(null, null, prefix, false, requestingUserId,
                 MUTATION_OPERATION_RESET, false, mode);
     }
 
-    private boolean mutateConfigSetting(String name, String value, String tag,
+    private boolean mutateConfigSetting(String name, String value, String prefix,
             boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
             int mode) {
         // TODO(b/117663715): check the new permission when it's added.
         // enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
-        // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
-
         // Perform the mutation.
         synchronized (mLock) {
             switch (operation) {
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
-                            UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
+                            UserHandle.USER_SYSTEM, name, value, null, makeDefault,
                             getCallingPackage(), forceNotify, null);
                 }
 
+                case MUTATION_OPERATION_DELETE: {
+                    return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG,
+                            UserHandle.USER_SYSTEM, name, forceNotify, null);
+                }
+
                 case MUTATION_OPERATION_RESET: {
                     mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
-                            UserHandle.USER_SYSTEM, getCallingPackage(), mode, tag);
+                            UserHandle.USER_SYSTEM, getCallingPackage(), mode, null, prefix);
                 } return true;
             }
         }
@@ -1098,6 +1125,34 @@
         return false;
     }
 
+    private Map<String, String> getAllConfigFlags(@Nullable String prefix) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
+        }
+
+        synchronized (mLock) {
+            // Get the settings.
+            SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+                    SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
+
+            List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_CONFIG,
+                    UserHandle.USER_SYSTEM);
+
+            final int nameCount = names.size();
+            Map<String, String> flagsToValues = new HashMap<>(names.size());
+
+            for (int i = 0; i < nameCount; i++) {
+                String name = names.get(i);
+                Setting setting = settingsState.getSettingLocked(name);
+                if (prefix == null || setting.getName().startsWith(prefix)) {
+                    flagsToValues.put(setting.getName(), setting.getValue());
+                }
+            }
+
+            return flagsToValues;
+        }
+    }
+
     private Cursor getAllGlobalSettings(String[] projection) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getAllGlobalSettings()");
@@ -2085,6 +2140,13 @@
         return (args != null) ? args.getString(Settings.CALL_METHOD_TAG_KEY) : null;
     }
 
+    private static String getSettingPrefix(Bundle args) {
+        String prefix = (args != null) ? args.getString(Settings.CALL_METHOD_PREFIX_KEY) : null;
+        // Append '/' to ensure we only match properties with this exact prefix.
+        // i.e. "foo" should match "foo/property" but not "foobar/property"
+        return prefix != null ? prefix + "/" : null;
+    }
+
     private static boolean getSettingMakeDefault(Bundle args) {
         return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
     }
@@ -2644,6 +2706,11 @@
 
         public void resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag) {
+            resetSettingsLocked(type, userId, packageName, mode, tag, null);
+        }
+
+        public void resetSettingsLocked(int type, int userId, String packageName, int mode,
+                String tag, @Nullable String prefix) {
             final int key = makeKey(type, userId);
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState == null) {
@@ -2656,7 +2723,8 @@
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
                         if (packageName.equals(setting.getPackageName())) {
-                            if (tag != null && !tag.equals(setting.getTag())) {
+                            if ((tag != null && !tag.equals(setting.getTag()))
+                                    || (prefix != null && !setting.getName().startsWith(prefix))) {
                                 continue;
                             }
                             if (settingsState.resetSettingLocked(name)) {
@@ -2676,6 +2744,9 @@
                         Setting setting = settingsState.getSettingLocked(name);
                         if (!SettingsState.isSystemPackage(getContext(),
                                 setting.getPackageName())) {
+                            if (prefix != null && !setting.getName().startsWith(prefix)) {
+                                continue;
+                            }
                             if (settingsState.resetSettingLocked(name)) {
                                 someSettingChanged = true;
                                 notifyForSettingsChange(key, name);
@@ -2693,6 +2764,9 @@
                         Setting setting = settingsState.getSettingLocked(name);
                         if (!SettingsState.isSystemPackage(getContext(),
                                 setting.getPackageName())) {
+                            if (prefix != null && !setting.getName().startsWith(prefix)) {
+                                continue;
+                            }
                             if (setting.isDefaultFromSystem()) {
                                 if (settingsState.resetSettingLocked(name)) {
                                     someSettingChanged = true;
@@ -2713,6 +2787,9 @@
                     for (String name : settingsState.getSettingNamesLocked()) {
                         Setting setting = settingsState.getSettingLocked(name);
                         boolean someSettingChanged = false;
+                        if (prefix != null && !setting.getName().startsWith(prefix)) {
+                            continue;
+                        }
                         if (setting.isDefaultFromSystem()) {
                             if (settingsState.resetSettingLocked(name)) {
                                 someSettingChanged = true;