system_server: bulk sync between old storage and new aconfig storage
message.
Do a bulk flag overrides sync when settings provider is initilized. At
the core, old storage data is stored inside SettingsState class. As the
last step of SettingState constructor, we handle bulk sync request. The
entry point of bulk sync is "handleBulkSyncToNewStorage" call in
SettingsState constructor.
When handling bulk sync requests, we use a setting entry in mSettings as
a marker to indicate if bulk sync has been performed. There are four
possibilities in each boot:
(1) enableAconfigStorageDaemon is on, but marker is false or no there (a setting entry in
mSettings) showing that bulk sync has been done. In this case, do a
bulkd sync, and then writes marker to mSettings and writes to xml file,
so that the marker persists.
(2) enableAconfigStorageDaemon is on, and marker is true. It means we have bulk
synced already, no need to do it again. Nothing to do in this case.
(3) enableAconfigStorageDaemon is off, and maker is true. Then we should clear the
marker from mSettings and trigger a write to xml file.
(4) enableAconfigStorageDaemon is off, and marker is false. Nothing to do in this case.
For bulk sync logic, it does the following things:
(1) write a storage reset request to aconfigd, ensure aconfigd is in
clean state before sync over any flags.
(2) loop over each setting and send over flag override requests.
Bug: b/312444587
Test: m and avd, tested with both server and local overrides
Change-Id: I1e0370da7516cd4cfaec94d428943d8e840b725e
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index e9c2672..75f8384 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -32,6 +32,8 @@
"unsupportedappusage",
],
static_libs: [
+ "aconfig_new_storage_flags_lib",
+ "aconfigd_java_utils",
"aconfig_demo_flags_java_lib",
"device_config_service_flags_java",
"libaconfig_java_proto_lite",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 68bc96d..04922d6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -82,6 +82,18 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+// FOR ACONFIGD TEST MISSION AND ROLLOUT
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
+import android.util.proto.ProtoInputStream;
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
+import android.aconfigd.AconfigdJavaUtils;
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
/**
* This class contains the state for one type of settings. It is responsible
* for saving the state asynchronously to an XML file after a mutation and
@@ -346,6 +358,7 @@
mNamespaceDefaults = new HashMap<>();
+ ProtoOutputStream requests = null;
synchronized (mLock) {
readStateSyncLocked();
@@ -361,7 +374,146 @@
loadAconfigDefaultValuesLocked(apexProtoPaths);
}
}
+
+ if (isConfigSettingsKey(mKey)) {
+ requests = handleBulkSyncToNewStorage();
+ }
}
+
+ if (requests != null) {
+ LocalSocket client = new LocalSocket();
+ try{
+ client.connect(new LocalSocketAddress(
+ "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+ Slog.d(LOG_TAG, "connected to aconfigd socket");
+ } catch (IOException ioe) {
+ Slog.e(LOG_TAG, "failed to connect to aconfigd socket", ioe);
+ return;
+ }
+ AconfigdJavaUtils.sendAconfigdRequests(client, requests);
+ }
+ }
+
+ // TODO(b/341764371): migrate aconfig flag push to GMS core
+ public static class FlagOverrideToSync {
+ public String packageName;
+ public String flagName;
+ public String flagValue;
+ public boolean isLocal;
+ }
+
+ // TODO(b/341764371): migrate aconfig flag push to GMS core
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public FlagOverrideToSync getFlagOverrideToSync(String name, String value) {
+ int slashIdx = name.indexOf("/");
+ if (slashIdx <= 0 || slashIdx >= name.length()-1) {
+ Slog.e(LOG_TAG, "invalid flag name " + name);
+ return null;
+ }
+
+ String namespace = name.substring(0, slashIdx);
+ String fullFlagName = name.substring(slashIdx + 1);
+ boolean isLocal = false;
+
+ // get actual fully qualified flag name <package>.<flag>, note this is done
+ // after staged flag is applied, so no need to check staged flags
+ if (namespace.equals("device_config_overrides")) {
+ int colonIdx = fullFlagName.indexOf(":");
+ if (colonIdx == -1) {
+ Slog.e(LOG_TAG, "invalid local override flag name " + name);
+ return null;
+ }
+ namespace = fullFlagName.substring(0, colonIdx);
+ fullFlagName = fullFlagName.substring(colonIdx + 1);
+ isLocal = true;
+ }
+
+ String aconfigName = namespace + "/" + fullFlagName;
+ boolean isAconfig = mNamespaceDefaults.containsKey(namespace)
+ && mNamespaceDefaults.get(namespace).containsKey(aconfigName);
+ if (!isAconfig) {
+ return null;
+ }
+
+ // get package name and flag name
+ int dotIdx = fullFlagName.lastIndexOf(".");
+ if (dotIdx == -1) {
+ Slog.e(LOG_TAG, "invalid override flag name " + name);
+ return null;
+ }
+
+ FlagOverrideToSync flag = new FlagOverrideToSync();
+ flag.packageName = fullFlagName.substring(0, dotIdx);
+ flag.flagName = fullFlagName.substring(dotIdx + 1);
+ flag.isLocal = isLocal;
+ flag.flagValue = value;
+ return flag;
+ }
+
+
+ // TODO(b/341764371): migrate aconfig flag push to GMS core
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public ProtoOutputStream handleBulkSyncToNewStorage() {
+ // get marker or add marker if it does not exist
+ final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced");
+ Setting markerSetting = mSettings.get(bulkSyncMarkerName);
+ if (markerSetting == null) {
+ markerSetting = new Setting(
+ bulkSyncMarkerName, "false", false, "aconfig", "aconfig");
+ mSettings.put(bulkSyncMarkerName, markerSetting);
+ }
+
+ if (enableAconfigStorageDaemon()) {
+ if (markerSetting.value.equals("true")) {
+ // CASE 1, flag is on, bulk sync marker true, nothing to do
+ return null;
+ } else {
+ // CASE 2, flag is on, bulk sync marker false. Do following two tasks
+ // (1) Do bulk sync here.
+ // (2) After bulk sync, set marker to true.
+
+ // first add storage reset request
+ ProtoOutputStream requests = new ProtoOutputStream();
+ AconfigdJavaUtils.writeResetStorageRequest(requests);
+
+ // loop over all settings and add flag override requests
+ final int numSettings = mSettings.size();
+ int num_requests = 0;
+ for (int i = 0; i < numSettings; i++) {
+ String name = mSettings.keyAt(i);
+ Setting setting = mSettings.valueAt(i);
+ FlagOverrideToSync flag =
+ getFlagOverrideToSync(name, setting.getValue());
+ if (flag == null) {
+ continue;
+ }
+ ++num_requests;
+ AconfigdJavaUtils.writeFlagOverrideRequest(
+ requests, flag.packageName, flag.flagName, flag.flagValue,
+ flag.isLocal);
+ }
+
+ Slog.i(LOG_TAG, num_requests + " flag override requests created");
+
+ // mark sync has been done
+ markerSetting.value = "true";
+ scheduleWriteIfNeededLocked();
+ return requests;
+ }
+ } else {
+ if (markerSetting.value.equals("true")) {
+ // CASE 3, flag is off, bulk sync marker true, clear the marker
+ markerSetting.value = "false";
+ scheduleWriteIfNeededLocked();
+ return null;
+ } else {
+ // CASE 4, flag is off, bulk sync marker false, nothing to do
+ return null;
+ }
+ }
+
}
@GuardedBy("mLock")
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 5db97c6..244c8c4 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -29,12 +29,18 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+import com.android.providers.settings.SettingsState.FlagOverrideToSync;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlSerializer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
import com.google.common.base.Strings;
import java.io.ByteArrayOutputStream;
@@ -947,4 +953,115 @@
+ testValue1.length() /* size for default */) * Character.BYTES;
assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
}
+
+ @Test
+ public void testGetFlagOverrideToSync() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.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))
+ .build();
+
+ synchronized (lock) {
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+ assertEquals(1, namespaceDefaults.keySet().size());
+ settingsState.addAconfigDefaultValuesFromMap(defaults);
+ }
+
+ // invalid flag name
+ assertTrue(settingsState.getFlagOverrideToSync(
+ "invalid_flag", "false") == null);
+
+ // non aconfig flag
+ assertTrue(settingsState.getFlagOverrideToSync(
+ "some_namespace/some_flag", "false") == null);
+
+ // server override
+ FlagOverrideToSync flag = settingsState.getFlagOverrideToSync(
+ "test_namespace/com.android.flags.flag1", "false");
+ assertTrue(flag != null);
+ assertEquals(flag.packageName, "com.android.flags");
+ assertEquals(flag.flagName, "flag1");
+ assertEquals(flag.flagValue, "false");
+ assertEquals(flag.isLocal, false);
+
+ // local override
+ flag = settingsState.getFlagOverrideToSync(
+ "device_config_overrides/test_namespace:com.android.flags.flag1", "false");
+ assertTrue(flag != null);
+ assertEquals(flag.packageName, "com.android.flags");
+ assertEquals(flag.flagName, "flag1");
+ assertEquals(flag.flagValue, "false");
+ assertEquals(flag.isLocal, true);
+ }
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Test
+ @EnableFlags(com.android.aconfig_new_storage.Flags.FLAG_ENABLE_ACONFIG_STORAGE_DAEMON)
+ public void testHandleBulkSyncWithAconfigdEnabled() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ synchronized (lock) {
+ settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
+ "false", null, false, "aconfig");
+
+ // first bulk sync
+ ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
+ assertTrue(requests != null);
+ String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+ assertEquals("true", value);
+
+ // send time should no longer bulk sync
+ requests = settingsState.handleBulkSyncToNewStorage();
+ assertTrue(requests == null);
+ value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+ assertEquals("true", value);
+ }
+ }
+
+ @Test
+ @DisableFlags(com.android.aconfig_new_storage.Flags.FLAG_ENABLE_ACONFIG_STORAGE_DAEMON)
+ public void testHandleBulkSyncWithAconfigdDisabled() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ synchronized (lock) {
+ settingsState.insertSettingLocked("aconfigd_marker/bulk_synced",
+ "true", null, false, "aconfig");
+
+ // when aconfigd is off, should change the marker to false
+ ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage();
+ assertTrue(requests == null);
+ String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+ assertEquals("false", value);
+
+ // marker started with false value, after call, it should remain false
+ requests = settingsState.handleBulkSyncToNewStorage();
+ assertTrue(requests == null);
+ value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue();
+ assertEquals("false", value);
+ }
+ }
}