Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6e24f0d0 authored by Ted Bauer's avatar Ted Bauer
Browse files

Load aconfig default values into SettingsProvider.

Change-Id: If62b00c3642a4b5ac9328a84e559b39724418147

Test: atest SettingsStateTest
Bug: 308977556
Change-Id: If62b00c3642a4b5ac9328a84e559b39724418147
parent e7e12e35
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -59,10 +59,10 @@ android_test {
        // 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",
+67 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ package com.android.providers.settings;

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 @@ final class SettingsState {
     */
    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 @@ final class SettingsState {

        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);
        }
    }

+8 −0
Original line number Diff line number Diff line
@@ -6,3 +6,11 @@ flag {
    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
}
+83 −0
Original line number Diff line number Diff line
@@ -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 @@ public class SettingsStateTest extends AndroidTestCase {
        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 日本語"));