Merge "SettingsToPropertiesMapper integrate with DeviceConfig API"
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 4c4a090..d5ede5b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -19,8 +19,10 @@
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.SystemProperties;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -61,13 +63,18 @@
     // Add the global setting you want to push to native level as experiment flag into this list.
     //
     // NOTE: please grant write permission system property prefix
-    // with format persist.experiment.[experiment_category_name]. in system_server.te and grant read
-    // permission in the corresponding .te file your feature belongs to.
+    // with format persist.device_config.global_settings.[flag_name] in system_server.te and grant
+    // read permission in the corresponding .te file your feature belongs to.
     @VisibleForTesting
     static final String[] sGlobalSettings = new String[] {
             Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
     };
 
+    // All the flags under the listed DeviceConfig scopes will be synced to native level.
+    //
+    // NOTE: please grant write permission system property prefix
+    // with format persist.device_config.[device_config_scope]. in system_server.te and grant read
+    // permission in the corresponding .te file your feature belongs to.
     @VisibleForTesting
     static final String[] sDeviceConfigScopes = new String[] {
     };
@@ -104,19 +111,31 @@
             ContentObserver co = new ContentObserver(null) {
                 @Override
                 public void onChange(boolean selfChange) {
-                    updatePropertyFromSetting(globalSetting, propName, true);
+                    updatePropertyFromSetting(globalSetting, propName);
                 }
             };
 
             // only updating on starting up when no native flags reset is performed during current
             // booting.
             if (!isNativeFlagsResetPerformed()) {
-                updatePropertyFromSetting(globalSetting, propName, true);
+                updatePropertyFromSetting(globalSetting, propName);
             }
             mContentResolver.registerContentObserver(settingUri, false, co);
         }
 
-        // TODO: address sDeviceConfigScopes after DeviceConfig APIs are available.
+        for (String deviceConfigScope : mDeviceConfigScopes) {
+            DeviceConfig.addOnPropertyChangedListener(
+                    deviceConfigScope,
+                    AsyncTask.THREAD_POOL_EXECUTOR,
+                    (String scope, String name, String value) -> {
+                        String propertyName = makePropertyName(scope, name);
+                        if (propertyName == null) {
+                            log("unable to construct system property for " + scope + "/" + name);
+                            return;
+                        }
+                        setProperty(propertyName, value);
+                    });
+        }
     }
 
     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -184,15 +203,6 @@
         return propertyName;
     }
 
-    private String getSetting(String name, boolean isGlobalSetting) {
-        if (isGlobalSetting) {
-            return Settings.Global.getString(mContentResolver, name);
-        } else {
-            // TODO: complete the code after DeviceConfig APIs implemented.
-            return null;
-        }
-    }
-
     private void setProperty(String key, String value) {
         // Check if need to clear the property
         if (value == null) {
@@ -259,8 +269,8 @@
     }
 
     @VisibleForTesting
-    void updatePropertyFromSetting(String settingName, String propName, boolean isGlobalSetting) {
-        String settingValue = getSetting(settingName, isGlobalSetting);
+    void updatePropertyFromSetting(String settingName, String propName) {
+        String settingValue = Settings.Global.getString(mContentResolver, settingName);
         setProperty(propName, settingValue);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index d965f8a..0fd5921 100644
--- a/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -41,11 +41,14 @@
 
 /**
  * Tests for {@link SettingsToPropertiesMapper}
+ *
+ *  Build/Install/Run:
+ *  atest FrameworksServicesTests:SettingsToPropertiesMapperTest
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SettingsToPropertiesMapperTest {
-    private static final String NAME_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$";
+    private static final String NAME_VALID_CHARACTERS_REGEX = "^[\\w\\-@:]*$";
     private static final String[] TEST_MAPPING = new String[] {
             Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS
     };
@@ -77,7 +80,28 @@
             }
             if (!globalSetting.matches(NAME_VALID_CHARACTERS_REGEX)) {
                 Assert.fail(globalSetting + " contains invalid characters. "
-                        + "Only alphanumeric characters, '.', '-', '@', ':' and '_' are valid.");
+                        + "Only alphanumeric characters, '-', '@', ':' and '_' are valid.");
+            }
+        }
+    }
+
+    @Test
+    public void validateRegisteredDeviceConfigScopes() {
+        HashSet<String> hashSet = new HashSet<>();
+        for (String deviceConfigScope : SettingsToPropertiesMapper.sDeviceConfigScopes) {
+            if (hashSet.contains(deviceConfigScope)) {
+                Assert.fail("deviceConfigScope "
+                        + deviceConfigScope
+                        + " is registered more than once in "
+                        + "SettingsToPropertiesMapper.sDeviceConfigScopes.");
+            }
+            hashSet.add(deviceConfigScope);
+            if (TextUtils.isEmpty(deviceConfigScope)) {
+                Assert.fail("empty deviceConfigScope registered.");
+            }
+            if (!deviceConfigScope.matches(NAME_VALID_CHARACTERS_REGEX)) {
+                Assert.fail(deviceConfigScope + " contains invalid characters. "
+                        + "Only alphanumeric characters, '-', '@', ':' and '_' are valid.");
             }
         }
     }
@@ -98,8 +122,7 @@
                 Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2");
         mTestMapper.updatePropertyFromSetting(
                 Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
-                systemPropertyName,
-                true);
+                systemPropertyName);
         propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
         Assert.assertEquals("testValue2", propValue);
 
@@ -107,8 +130,7 @@
                 Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null);
         mTestMapper.updatePropertyFromSetting(
                 Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
-                systemPropertyName,
-                true);
+                systemPropertyName);
         propValue = mTestMapper.systemPropertiesGet(systemPropertyName);
         Assert.assertEquals("", propValue);
     }