Load aconfig default values into SettingsProvider.

Change-Id: If62b00c3642a4b5ac9328a84e559b39724418147

Test: atest SettingsStateTest
Bug: 308977556
Change-Id: If62b00c3642a4b5ac9328a84e559b39724418147
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index adebdcd..d5814e3 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -59,10 +59,10 @@
         // Note we statically link SettingsProviderLib to do some unit tests.  It's not accessible otherwise
         // because this test is not an instrumentation test. (because the target runs in the system process.)
         "SettingsProviderLib",
-
         "androidx.test.rules",
         "flag-junit",
         "junit",
+        "libaconfig_java_proto_lite",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
         "truth",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f459c6..73c2e22 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -18,6 +18,9 @@
 
 import static android.os.Process.FIRST_APPLICATION_UID;
 
+import android.aconfig.Aconfig.flag_state;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -147,6 +150,17 @@
      */
     private static final String CONFIG_STAGED_PREFIX = "staged/";
 
+    private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
+            "/system/etc/aconfig_flags.pb",
+            "/system_ext/etc/aconfig_flags.pb",
+            "/product/etc/aconfig_flags.pb",
+            "/vendor/etc/aconfig_flags.pb");
+
+    /**
+     * This tag is applied to all aconfig default value-loaded flags.
+     */
+    private static final String BOOT_LOADED_DEFAULT_TAG = "BOOT_LOADED_DEFAULT";
+
     // This was used in version 120 and before.
     private static final String NULL_VALUE_OLD_STYLE = "null";
 
@@ -315,6 +329,59 @@
 
         synchronized (mLock) {
             readStateSyncLocked();
+
+            if (Flags.loadAconfigDefaults()) {
+                // Only load aconfig defaults if this is the first boot, the XML
+                // file doesn't exist yet, or this device is on its first boot after
+                // an OTA.
+                boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
+                        && (!file.exists()
+                                || mContext.getPackageManager().isDeviceUpgrading());
+                if (shouldLoadAconfigValues) {
+                    loadAconfigDefaultValuesLocked();
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void loadAconfigDefaultValuesLocked() {
+        for (String fileName : sAconfigTextProtoFilesOnDevice) {
+            try (FileInputStream inputStream = new FileInputStream(fileName)) {
+                byte[] contents = inputStream.readAllBytes();
+                loadAconfigDefaultValues(contents);
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "failed to read protobuf", e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    public void loadAconfigDefaultValues(byte[] fileContents) {
+        try {
+            parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+
+            if (parsedFlags == null) {
+                Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
+                return;
+            }
+
+            for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
+                String flagName = flag.getNamespace() + "/"
+                        + flag.getPackage() + "." + flag.getName();
+                String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
+
+                Setting existingSetting = getSettingLocked(flagName);
+                boolean isDefaultLoaded = existingSetting.getTag() != null
+                        && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
+                if (existingSetting.getValue() == null || isDefaultLoaded) {
+                    insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
+                            false, flag.getPackage());
+                }
+            }
+        } catch (IOException e) {
+            Slog.e(LOG_TAG, "failed to parse protobuf", e);
         }
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 27ce0d4..ecac5ee 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -6,3 +6,11 @@
     description: "When enabled, allows setting and displaying local overrides via adb."
     bug: "298392357"
 }
+
+flag {
+    name: "load_aconfig_defaults"
+    namespace: "core_experiments_team_internal"
+    description: "When enabled, loads aconfig default values into DeviceConfig on boot."
+    bug: "311155098"
+    is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 02a7bc1..24625ea 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.providers.settings;
 
+import android.aconfig.Aconfig;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
 import android.os.Looper;
 import android.test.AndroidTestCase;
 import android.util.Xml;
@@ -84,6 +87,86 @@
         super.tearDown();
     }
 
+    public void testLoadValidAconfigProto() {
+        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+        Object lock = new Object();
+        SettingsState settingsState = new SettingsState(
+                getContext(), lock, mSettingsFile, configKey,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+        parsed_flags flags = parsed_flags
+                .newBuilder()
+                .addParsedFlag(parsed_flag
+                    .newBuilder()
+                        .setPackage("com.android.flags")
+                        .setName("flag1")
+                        .setNamespace("test_namespace")
+                        .setDescription("test flag")
+                        .addBug("12345678")
+                        .setState(Aconfig.flag_state.DISABLED)
+                        .setPermission(Aconfig.flag_permission.READ_WRITE))
+                .addParsedFlag(parsed_flag
+                    .newBuilder()
+                        .setPackage("com.android.flags")
+                        .setName("flag2")
+                        .setNamespace("test_namespace")
+                        .setDescription("another test flag")
+                        .addBug("12345678")
+                        .setState(Aconfig.flag_state.ENABLED)
+                        .setPermission(Aconfig.flag_permission.READ_WRITE))
+                .build();
+
+        synchronized (lock) {
+            settingsState.loadAconfigDefaultValues(flags.toByteArray());
+            settingsState.persistSettingsLocked();
+        }
+        settingsState.waitForHandler();
+
+        synchronized (lock) {
+            assertEquals("false",
+                    settingsState.getSettingLocked(
+                        "test_namespace/com.android.flags.flag1").getValue());
+            assertEquals("true",
+                    settingsState.getSettingLocked(
+                        "test_namespace/com.android.flags.flag2").getValue());
+        }
+    }
+
+    public void testSkipLoadingAconfigFlagWithMissingFields() {
+        int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+        Object lock = new Object();
+        SettingsState settingsState = new SettingsState(
+                getContext(), lock, mSettingsFile, configKey,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+        parsed_flags flags = parsed_flags
+                .newBuilder()
+                .addParsedFlag(parsed_flag
+                    .newBuilder()
+                        .setDescription("test flag")
+                        .addBug("12345678")
+                        .setState(Aconfig.flag_state.DISABLED)
+                        .setPermission(Aconfig.flag_permission.READ_WRITE))
+                .build();
+
+        synchronized (lock) {
+            settingsState.loadAconfigDefaultValues(flags.toByteArray());
+            settingsState.persistSettingsLocked();
+        }
+        settingsState.waitForHandler();
+
+        synchronized (lock) {
+            assertEquals(null,
+                    settingsState.getSettingLocked(
+                        "test_namespace/com.android.flags.flag1").getValue());
+        }
+    }
+
+    public void testInvalidAconfigProtoDoesNotCrash() {
+        SettingsState settingsState = getSettingStateObject();
+        settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+    }
+
     public void testIsBinary() {
         assertFalse(SettingsState.isBinary(" abc 日本語"));