Add DeviceConfig.setProperties method for atomic writing.

This method accepts multiple flags and overrides the values of all flags
in the provided namespace atomically. If any listeners are registered
for that namespace it will also trigger a callback with all of the flags
that were changed (added, updated, or removed).

Test: atest FrameworksCoreTests:DeviceConfigTest
      atest FrameworksCoreTests:SettingsProviderTest
      atest SettingsProviderTest:DeviceConfigServiceTest
Bug: 136135417

Change-Id: I2e496a3807493750ba8891dd6390055c919b5f45
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f5d1ccf..0959de9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -101,6 +101,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -383,6 +384,13 @@
                 break;
             }
 
+            case Settings.CALL_METHOD_SET_ALL_CONFIG: {
+                String prefix = getSettingPrefix(args);
+                Map<String, String> flags = getSettingFlags(args);
+                setAllConfigSettings(prefix, flags);
+                break;
+            }
+
             case Settings.CALL_METHOD_RESET_CONFIG: {
                 final int mode = getResetModeEnforcingPermission(args);
                 String prefix = getSettingPrefix(args);
@@ -1030,6 +1038,19 @@
                 MUTATION_OPERATION_INSERT, 0);
     }
 
+    private boolean setAllConfigSettings(String prefix, Map<String, String> keyValues) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "setAllConfigSettings for prefix: " + prefix);
+        }
+
+        enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
+
+        synchronized (mLock) {
+            return mSettingsRegistry.setSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM,
+                    prefix, keyValues, resolveCallingPackage());
+        }
+    }
+
     private boolean deleteConfigSetting(String name) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ")");
@@ -2117,6 +2138,11 @@
         return (args != null) ? args.getString(Settings.CALL_METHOD_PREFIX_KEY) : null;
     }
 
+    private static Map<String, String> getSettingFlags(Bundle args) {
+        return (args != null) ? (HashMap) args.getSerializable(Settings.CALL_METHOD_FLAGS_KEY)
+                : Collections.emptyMap();
+    }
+
     private static boolean getSettingMakeDefault(Bundle args) {
         return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
     }
@@ -2485,7 +2511,7 @@
             final int key = makeKey(type, userId);
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState == null) {
-                return new ArrayList<String>();
+                return new ArrayList<>();
             }
             return settingsState.getSettingNamesLocked();
         }
@@ -2645,6 +2671,22 @@
             return success;
         }
 
+        public boolean setSettingsLocked(int type, int userId, String prefix,
+                Map<String, String> keyValues, String packageName) {
+            final int key = makeKey(type, userId);
+
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            if (settingsState != null) {
+                List<String> changedSettings =
+                        settingsState.setSettingsLocked(prefix, keyValues, packageName);
+                if (!changedSettings.isEmpty()) {
+                    notifyForConfigSettingsChangeLocked(key, prefix, changedSettings);
+                }
+            }
+
+            return settingsState != null;
+        }
+
         public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
                 Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
@@ -3043,6 +3085,28 @@
             mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
         }
 
+        private void notifyForConfigSettingsChangeLocked(int key, String prefix,
+                List<String> changedSettings) {
+
+            // Increment the generation first, so observers always see the new value
+            mGenerationRegistry.incrementGeneration(key);
+
+            StringBuilder stringBuilder = new StringBuilder(prefix);
+            for (int i = 0; i < changedSettings.size(); ++i) {
+                stringBuilder.append(changedSettings.get(i).split("/")[1]).append("/");
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                notifySettingChangeForRunningUsers(key, stringBuilder.toString());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+
+            // Always notify that our data changed
+            mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+        }
+
         private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
                 Collection<String> keysCloned) {
             if (keysCloned.contains(name)) {