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 日本語"));