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

Commit 170445c4 authored by Ted Bauer's avatar Ted Bauer Committed by Android (Google) Code Review
Browse files

Merge "Load aconfig default values into SettingsProvider." into main

parents 203ed8ab 6e24f0d0
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 日本語"));