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

Commit 7277cee4 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add support for SystemProperty flags to SysUI.

This will allow the flag flippin' app to change boolean system
properties that launcher, settings, and system server can read.
Some of these flags are already being read between the various systems
and this gives a unified ux for changing the flags.

Bug: 219067621
Test: manual
Change-Id: I7d1a1e758915f43cac114054a2c2e055706ef3f3
parent e90d6b5e
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -35,6 +35,11 @@ interface ResourceFlag<T> : Flag<T> {
    val resourceId: Int
}

interface SysPropFlag<T> : Flag<T> {
    val name: String
    val default: T
}

// Consider using the "parcelize" kotlin library.

data class BooleanFlag @JvmOverloads constructor(
@@ -66,6 +71,12 @@ data class ResourceBooleanFlag constructor(
    @BoolRes override val resourceId: Int
) : ResourceFlag<Boolean>

data class SysPropBooleanFlag constructor(
    override val id: Int,
    override val name: String,
    override val default: Boolean = false
) : SysPropFlag<Boolean>

data class StringFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: String = ""
+3 −3
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@ class FlagManager constructor(
     * An action called on restart which takes as an argument whether the listeners requested
     * that the restart be suppressed
     */
    var restartAction: Consumer<Boolean>? = null
    var onSettingsChangedAction: Consumer<Boolean>? = null
    var clearCacheAction: Consumer<Int>? = null
    private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
    private val settingsObserver: ContentObserver = SettingsObserver()
@@ -154,11 +154,11 @@ class FlagManager constructor(
            val idStr = parts[parts.size - 1]
            val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
            clearCacheAction?.accept(id)
            dispatchListenersAndMaybeRestart(id)
            dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
        }
    }

    fun dispatchListenersAndMaybeRestart(id: Int) {
    fun dispatchListenersAndMaybeRestart(id: Int, restartAction: Consumer<Boolean>?) {
        val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
            listeners.mapNotNull { if (it.id == id) it.listener else null }
        }
+3 −0
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ interface FeatureFlags : FlagListenable {
    /** Returns a boolean value for the given flag.  */
    fun isEnabled(flag: ResourceBooleanFlag): Boolean

    /** Returns a boolean value for the given flag.  */
    fun isEnabled(flag: SysPropBooleanFlag): Boolean

    /** Returns a string value for the given flag.  */
    fun getString(flag: StringFlag): String

+44 −6
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.Supplier;

import javax.inject.Inject;
@@ -69,6 +70,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    private final FlagManager mFlagManager;
    private final SecureSettings mSecureSettings;
    private final Resources mResources;
    private final SystemPropertiesHelper mSystemProperties;
    private final Supplier<Map<Integer, Flag<?>>> mFlagsCollector;
    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
@@ -79,6 +81,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            FlagManager flagManager,
            Context context,
            SecureSettings secureSettings,
            SystemPropertiesHelper systemProperties,
            @Main Resources resources,
            DumpManager dumpManager,
            @Nullable Supplier<Map<Integer, Flag<?>>> flagsCollector,
@@ -86,11 +89,12 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        mFlagManager = flagManager;
        mSecureSettings = secureSettings;
        mResources = resources;
        mSystemProperties = systemProperties;
        mFlagsCollector = flagsCollector != null ? flagsCollector : Flags::collectFlags;
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_SET_FLAG);
        filter.addAction(ACTION_GET_FLAGS);
        flagManager.setRestartAction(this::restartSystemUI);
        flagManager.setOnSettingsChangedAction(this::restartSystemUI);
        flagManager.setClearCacheAction(this::removeFromCache);
        context.registerReceiver(mReceiver, filter, null, null,
                Context.RECEIVER_EXPORTED_UNAUDITED);
@@ -121,6 +125,17 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        return mBooleanFlagCache.get(id);
    }

    @Override
    public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            mBooleanFlagCache.put(
                    id, mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
        }

        return mBooleanFlagCache.get(id);
    }

    @NonNull
    @Override
    public String getString(@NonNull StringFlag flag) {
@@ -180,16 +195,28 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        mSecureSettings.putString(mFlagManager.idToSettingsKey(id), data);
        Log.i(TAG, "Set id " + id + " to " + value);
        removeFromCache(id);
        mFlagManager.dispatchListenersAndMaybeRestart(id);
        mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
    }

    private <T> void eraseFlag(Flag<T> flag) {
        if (flag instanceof SysPropFlag) {
            mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
            dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
        } else {
            eraseFlag(flag.getId());
        }
    }

    /** Erase a flag's overridden value if there is one. */
    public void eraseFlag(int id) {
    private void eraseFlag(int id) {
        eraseInternal(id);
        removeFromCache(id);
        mFlagManager.dispatchListenersAndMaybeRestart(id);
        dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
    }

    private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
        mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
    }
    /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
    private void eraseInternal(int id) {
        // We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -217,7 +244,11 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        System.exit(0);
    }

    private void restartAndroid() {
    private void restartAndroid(boolean requestSuppress) {
        if (requestSuppress) {
            Log.i(TAG, "Android Restart Suppressed");
            return;
        }
        Log.i(TAG, "Restarting Android");
        try {
            mBarService.restart();
@@ -273,7 +304,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            Flag<?> flag = flagMap.get(id);

            if (!extras.containsKey(EXTRA_VALUE)) {
                eraseFlag(id);
                eraseFlag(flag);
                return;
            }

@@ -282,6 +313,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
                setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
            } else  if (flag instanceof ResourceBooleanFlag && value instanceof Boolean) {
                setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
            } else  if (flag instanceof SysPropBooleanFlag && value instanceof Boolean) {
                // Store SysProp flags in SystemProperties where they can read by outside parties.
                mSystemProperties.setBoolean(
                        ((SysPropBooleanFlag) flag).getName(), (Boolean) value);
            } else if (flag instanceof StringFlag && value instanceof String) {
                setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE);
            } else if (flag instanceof ResourceStringFlag && value instanceof String) {
@@ -306,6 +341,9 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            if (f instanceof ResourceBooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
            }
            if (f instanceof SysPropBooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((SysPropBooleanFlag) f));
            }

            // TODO: add support for other flag types.
            Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
+18 −1
Original line number Diff line number Diff line
@@ -43,11 +43,17 @@ import javax.inject.Inject;
@SysUISingleton
public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
    private final Resources mResources;
    private final SystemPropertiesHelper mSystemProperties;
    SparseBooleanArray mBooleanCache = new SparseBooleanArray();
    SparseArray<String> mStringCache = new SparseArray<>();

    @Inject
    public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
    public FeatureFlagsRelease(
            @Main Resources resources,
            SystemPropertiesHelper systemProperties,
            DumpManager dumpManager) {
        mResources = resources;
        mSystemProperties = systemProperties;
        dumpManager.registerDumpable("SysUIFlags", this);
    }

@@ -72,6 +78,17 @@ public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
        return mBooleanCache.valueAt(cacheIndex);
    }

    @Override
    public boolean isEnabled(SysPropBooleanFlag flag) {
        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
        if (cacheIndex < 0) {
            return isEnabled(
                    flag.getId(), mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
        }

        return mBooleanCache.valueAt(cacheIndex);
    }

    private boolean isEnabled(int key, boolean defaultValue) {
        mBooleanCache.append(key, defaultValue);
        return defaultValue;
Loading