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

Commit 27195676 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add ability for flags to be server-overridden.

This works in both debug and release builds. In debug builds,
all flags can be overridden via the same mechanisms as before,
but can also be overridden by flags defined on the server.

On release builds, only `ReleasedFlag`s can be overridden.
`UnreleasedFlag`s remain set in their default state.

With this cl, server side flags do not get their new value
until SystemUI is restarted (via a reboot or a crash).

Bug: 239863271
Test: atest SystemUITests
Change-Id: I08166cb90675dea06b2671669cecfc16ac58ab61
Merged-In: I08166cb90675dea06b2671669cecfc16ac58ab61
parent 382cf794
Loading
Loading
Loading
Loading
+48 −3
Original line number Diff line number Diff line
@@ -48,9 +48,13 @@ interface SysPropFlag<T> : Flag<T> {
    val default: T
}

/**
 * Base class for most common boolean flags.
 *
 * See [UnreleasedFlag] and [ReleasedFlag] for useful implementations.
 */
// Consider using the "parcelize" kotlin library.

data class BooleanFlag @JvmOverloads constructor(
abstract class BooleanFlag constructor(
    override val id: Int,
    override val default: Boolean = false,
    override val teamfood: Boolean = false,
@@ -60,7 +64,7 @@ data class BooleanFlag @JvmOverloads constructor(
    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<BooleanFlag> {
            override fun createFromParcel(parcel: Parcel) = BooleanFlag(parcel)
            override fun createFromParcel(parcel: Parcel) = object : BooleanFlag(parcel) {}
            override fun newArray(size: Int) = arrayOfNulls<BooleanFlag>(size)
        }
    }
@@ -80,12 +84,46 @@ data class BooleanFlag @JvmOverloads constructor(
    }
}

/**
 * A Flag that is is false by default.
 *
 * It can be changed or overridden in debug builds but not in release builds.
 */
data class UnreleasedFlag @JvmOverloads constructor(
    override val id: Int,
    override val teamfood: Boolean = false,
    override val overridden: Boolean = false
) : BooleanFlag(id, false, teamfood, overridden)

/**
 * A Flag that is is true by default.
 *
 * It can be changed or overridden in any build, meaning it can be turned off if needed.
 */
data class ReleasedFlag @JvmOverloads constructor(
    override val id: Int,
    override val teamfood: Boolean = false,
    override val overridden: Boolean = false
) : BooleanFlag(id, true, teamfood, overridden)

/**
 * A Flag that reads its default values from a resource overlay instead of code.
 *
 * Prefer [UnreleasedFlag] and [ReleasedFlag].
 */
data class ResourceBooleanFlag @JvmOverloads constructor(
    override val id: Int,
    @BoolRes override val resourceId: Int,
    override val teamfood: Boolean = false
) : ResourceFlag<Boolean>

/**
 * A Flag that can reads its overrides from DeviceConfig.
 *
 * This is generally useful for flags that come from or are used _outside_ of SystemUI.
 *
 * Prefer [UnreleasedFlag] and [ReleasedFlag].
 */
data class DeviceConfigBooleanFlag @JvmOverloads constructor(
    override val id: Int,
    override val name: String,
@@ -94,6 +132,13 @@ data class DeviceConfigBooleanFlag @JvmOverloads constructor(
    override val teamfood: Boolean = false
) : DeviceConfigFlag<Boolean>

/**
 * A Flag that can reads its overrides from System Properties.
 *
 * This is generally useful for flags that come from or are used _outside_ of SystemUI.
 *
 * Prefer [UnreleasedFlag] and [ReleasedFlag].
 */
data class SysPropBooleanFlag @JvmOverloads constructor(
    override val id: Int,
    override val name: String,
+3 −2
Original line number Diff line number Diff line
@@ -27,7 +27,8 @@ import dagger.Provides
import javax.inject.Named

@Module(includes = [
    SettingsUtilModule::class
    ServerFlagReaderModule::class,
    SettingsUtilModule::class,
])
abstract class FlagsModule {
    @Binds
+2 −2
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.systemui.flags
import dagger.Binds
import dagger.Module

@Module
@Module(includes = [ServerFlagReaderModule::class])
abstract class FlagsModule {
    @Binds
    abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+5 −2
Original line number Diff line number Diff line
@@ -23,7 +23,10 @@ package com.android.systemui.flags
 */
interface FeatureFlags : FlagListenable {
    /** Returns a boolean value for the given flag.  */
    fun isEnabled(flag: BooleanFlag): Boolean
    fun isEnabled(flag: UnreleasedFlag): Boolean

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

    /** Returns a boolean value for the given flag.  */
    fun isEnabled(flag: ResourceBooleanFlag): Boolean
+54 −33
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;

import org.jetbrains.annotations.NotNull;

import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -83,6 +85,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    private final Resources mResources;
    private final SystemPropertiesHelper mSystemProperties;
    private final DeviceConfigProxy mDeviceConfigProxy;
    private final ServerFlagReader mServerFlagReader;
    private final Map<Integer, Flag<?>> mAllFlags;
    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
@@ -97,6 +100,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            @Main Resources resources,
            DumpManager dumpManager,
            DeviceConfigProxy deviceConfigProxy,
            ServerFlagReader serverFlagReader,
            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
            CommandRegistry commandRegistry,
            IStatusBarService barService) {
@@ -105,6 +109,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        mResources = resources;
        mSystemProperties = systemProperties;
        mDeviceConfigProxy = deviceConfigProxy;
        mServerFlagReader = serverFlagReader;
        mAllFlags = allFlags;
        mBarService = barService;

@@ -120,7 +125,16 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    }

    @Override
    public boolean isEnabled(@NonNull BooleanFlag flag) {
    public boolean isEnabled(@NotNull UnreleasedFlag flag) {
        return isEnabledInternal(flag);
    }

    @Override
    public boolean isEnabled(@NotNull ReleasedFlag flag) {
        return isEnabledInternal(flag);
    }

    private boolean isEnabledInternal(@NotNull BooleanFlag flag) {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            mBooleanFlagCache.put(id,
@@ -197,14 +211,17 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    /** Specific override for Boolean flags that checks against the teamfood list.*/
    private boolean readFlagValue(int id, boolean defaultValue) {
        Boolean result = readBooleanFlagOverride(id);
        // Only check for teamfood if the default is false.
        if (!defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
        boolean hasServerOverride = mServerFlagReader.hasOverride(id);

        // Only check for teamfood if the default is false
        // and there is no server override.
        if (!hasServerOverride && !defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
            if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
                return isEnabled(Flags.TEAMFOOD);
            }
        }

        return result == null ? defaultValue : result;
        return result == null ? mServerFlagReader.readServerOverride(id, defaultValue) : result;
    }

    private Boolean readBooleanFlagOverride(int id) {
@@ -409,37 +426,39 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
         */
        @Nullable
        private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
            if (f instanceof BooleanFlag) {
                return new BooleanFlag(
                        f.getId(),
                        isEnabled((BooleanFlag) f),
                        f.getTeamfood(),
                        readBooleanFlagOverride(f.getId()) != null);
            }
            if (f instanceof ResourceBooleanFlag) {
                return new BooleanFlag(
                        f.getId(),
                        isEnabled((ResourceBooleanFlag) f),
                        f.getTeamfood(),
                        readBooleanFlagOverride(f.getId()) != null);
            }
            if (f instanceof DeviceConfigBooleanFlag) {
                return new BooleanFlag(
                        f.getId(), isEnabled((DeviceConfigBooleanFlag) f), f.getTeamfood());
            }
            if (f instanceof SysPropBooleanFlag) {
            boolean enabled;
            boolean teamfood = f.getTeamfood();
            boolean overridden;

            if (f instanceof ReleasedFlag) {
                enabled = isEnabled((ReleasedFlag) f);
                overridden = readBooleanFlagOverride(f.getId()) != null;
            } else if (f instanceof UnreleasedFlag) {
                enabled = isEnabled((UnreleasedFlag) f);
                overridden = readBooleanFlagOverride(f.getId()) != null;
            } else if (f instanceof ResourceBooleanFlag) {
                enabled = isEnabled((ResourceBooleanFlag) f);
                overridden = readBooleanFlagOverride(f.getId()) != null;
            } else if (f instanceof DeviceConfigBooleanFlag) {
                enabled = isEnabled((DeviceConfigBooleanFlag) f);
                overridden = false;
            } else if (f instanceof SysPropBooleanFlag) {
                // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                return new BooleanFlag(
                        f.getId(),
                        ((SysPropBooleanFlag) f).getDefault(),
                        false,
                        !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty());
            }

                enabled = isEnabled((SysPropBooleanFlag) f);
                teamfood = false;
                overridden = !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty();
            } else {
                // TODO: add support for other flag types.
                Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
                return null;
            }

            if (enabled) {
                return new ReleasedFlag(f.getId(), teamfood, overridden);
            } else {
                return new UnreleasedFlag(f.getId(), teamfood, overridden);
            }
        }
    };

    private void removeFromCache(int id) {
@@ -539,8 +558,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        }

        private boolean isBooleanFlagEnabled(Flag<?> flag) {
            if (flag instanceof BooleanFlag) {
                return isEnabled((BooleanFlag) flag);
            if (flag instanceof ReleasedFlag) {
                return isEnabled((ReleasedFlag) flag);
            } else if (flag instanceof UnreleasedFlag) {
                return isEnabled((UnreleasedFlag) flag);
            } else if (flag instanceof ResourceBooleanFlag) {
                return isEnabled((ResourceBooleanFlag) flag);
            } else if (flag instanceof SysPropFlag) {
Loading