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

Commit 2804c41a authored by Ted Bauer's avatar Ted Bauer
Browse files

Apply staged flags in SettingsProvider.

Bug: 301096965
Test: new SettingsStateTest test case
Change-Id: Ieffb3fdf618f1507ef3df3782e05e7e3a04dee81
parent eb254d1e
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -137,6 +137,13 @@ final class SettingsState {
    private static final String ATTR_VALUE_BASE64 = "valueBase64";
    private static final String ATTR_DEFAULT_VALUE_BASE64 = "defaultValueBase64";

    /**
     * In the config table, there are special flags of the form {@code staged/namespace*flagName}.
     * On boot, when the XML file is initially parsed, these transform into
     * {@code namespace/flagName}, and the special staged flags are deleted.
     */
    private static final String CONFIG_STAGED_PREFIX = "staged/";

    // This was used in version 120 and before.
    private static final String NULL_VALUE_OLD_STYLE = "null";

@@ -1191,6 +1198,42 @@ final class SettingsState {
        }
    }

    /**
     * Transforms a staged flag name to its real flag name.
     *
     * Staged flags take the form {@code staged/namespace*flagName}. If
     * {@code stagedFlagName} takes the proper form, returns
     * {@code namespace/flagName}. Otherwise, returns {@code stagedFlagName}
     * unmodified, and logs an error message.
     *
     */
    @VisibleForTesting
    public static String createRealFlagName(String stagedFlagName) {
        int slashIndex = stagedFlagName.indexOf("/");
        if (slashIndex == -1 || slashIndex == stagedFlagName.length() - 1
                || slashIndex == 0) {
            Slog.w(LOG_TAG, "invalid staged flag, not applying: " + stagedFlagName);
            return stagedFlagName;
        }

        String namespaceAndFlag =
                stagedFlagName.substring(slashIndex + 1);

        int starIndex = namespaceAndFlag.indexOf("*");
        if (starIndex == -1 || starIndex == namespaceAndFlag.length() - 1
                || starIndex == 0) {
            Slog.w(LOG_TAG, "invalid staged flag, not applying: " + stagedFlagName);
            return stagedFlagName;
        }

        String namespace =
                namespaceAndFlag.substring(0, starIndex);
        String flagName =
                namespaceAndFlag.substring(starIndex + 1);

        return namespace + "/" + flagName;
    }

    @GuardedBy("mLock")
    private void parseSettingsLocked(TypedXmlPullParser parser)
            throws IOException, XmlPullParserException {
@@ -1199,6 +1242,7 @@ final class SettingsState {

        final int outerDepth = parser.getDepth();
        int type;
        HashSet<String> flagsWithStagedValueApplied = new HashSet<String>();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -1221,6 +1265,18 @@ final class SettingsState {
                    fromSystem = parser.getAttributeBoolean(null, ATTR_DEFAULT_SYS_SET, false);
                    tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64);
                }

                if (isConfigSettingsKey(mKey)) {
                    if (flagsWithStagedValueApplied.contains(name)) {
                        continue;
                    }

                    if (name.startsWith(CONFIG_STAGED_PREFIX)) {
                        name = createRealFlagName(name);
                        flagsWithStagedValueApplied.add(name);
                    }
                }

                mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
                        fromSystem, id, isPreservedInRestore));

@@ -1229,6 +1285,16 @@ final class SettingsState {
                }
            }
        }

        if (isConfigSettingsKey(mKey) && !flagsWithStagedValueApplied.isEmpty()) {
            // On boot, the config table XML file includes special staged flags. On the initial
            // boot XML -> HashMap parse, these staged flags get transformed into real flags.
            // After this, the HashMap contains no special staged flags (only the transformed
            // real flags), but the XML still does. We then have no need for the special staged
            // flags in the XML, so we overwrite the XML with the latest contents of the
            // HashMap.
            writeStateAsyncLocked();
        }
    }

    @GuardedBy("mLock")
+79 −0
Original line number Diff line number Diff line
@@ -51,6 +51,21 @@ public class SettingsStateTest extends AndroidTestCase {
    private static final String SYSTEM_PACKAGE = "android";
    private static final String SETTING_NAME = "test_setting";

    private static final String FLAG_NAME_1 = "namespace123/flag456";
    private static final String FLAG_NAME_1_STAGED = "staged/namespace123*flag456";
    private static final String FLAG_NAME_2 = "not_staged/flag101";

    private static final String INVALID_STAGED_FLAG_1 = "stagednamespace*flagName";
    private static final String INVALID_STAGED_FLAG_2 = "staged/";
    private static final String INVALID_STAGED_FLAG_3 = "staged/namespace*";
    private static final String INVALID_STAGED_FLAG_4 = "staged/*flagName";

    private static final String VALID_STAGED_FLAG_1 = "staged/namespace*flagName";
    private static final String VALID_STAGED_FLAG_1_TRANSFORMED = "namespace/flagName";

    private static final String VALUE1 = "5";
    private static final String VALUE2 = "6";

    private final Object mLock = new Object();

    private File mSettingsFile;
@@ -454,4 +469,68 @@ public class SettingsStateTest extends AndroidTestCase {
            }
        }
    }

    public void testApplyStagedConfigValues() {
        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());

        synchronized (lock) {
            settingsState.insertSettingLocked(
                    FLAG_NAME_1_STAGED, VALUE1, null, false, TEST_PACKAGE);
            settingsState.insertSettingLocked(FLAG_NAME_2, VALUE2, null, false, TEST_PACKAGE);
            settingsState.persistSyncLocked();

            assertEquals(VALUE1, settingsState.getSettingLocked(FLAG_NAME_1_STAGED).getValue());
            assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());
        }

        settingsState = new SettingsState(getContext(), lock, mSettingsFile, configKey,
                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());

        synchronized (lock) {
            assertEquals(VALUE1, settingsState.getSettingLocked(FLAG_NAME_1).getValue());
            assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());

            assertEquals(null, settingsState.getSettingLocked(FLAG_NAME_1_STAGED).getValue());
        }
    }

    public void testStagingTransformation() {
        assertEquals(INVALID_STAGED_FLAG_1,
                SettingsState.createRealFlagName(INVALID_STAGED_FLAG_1));
        assertEquals(INVALID_STAGED_FLAG_2,
                SettingsState.createRealFlagName(INVALID_STAGED_FLAG_2));
        assertEquals(INVALID_STAGED_FLAG_3,
                SettingsState.createRealFlagName(INVALID_STAGED_FLAG_3));
        assertEquals(INVALID_STAGED_FLAG_4,
                SettingsState.createRealFlagName(INVALID_STAGED_FLAG_4));

        assertEquals(VALID_STAGED_FLAG_1_TRANSFORMED,
                SettingsState.createRealFlagName(VALID_STAGED_FLAG_1));
    }

    public void testInvalidStagedFlagsUnaffectedByReboot() {
        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());

        synchronized (lock) {
            settingsState.insertSettingLocked(INVALID_STAGED_FLAG_1,
                    VALUE2, null, false, TEST_PACKAGE);
            settingsState.persistSyncLocked();
            assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
        }

        settingsState = new SettingsState(getContext(), lock, mSettingsFile, configKey,
                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());

        synchronized (lock) {
            assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
        }
    }
}