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

Commit 214c0014 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add support for TEAMFOOD flag.

A list of boolean teamfood flags is now available. Adding a flag to
the list means that the flag is tied to the value of the teamfood
flag.

If the flag is manually set, that manually set value will take
precedence.

If the flag is true by default, the true value will take precedence.

Otherwise, the flag will fallback to the value of the teamfood flag.

Bug: 223379190
Test: atest SystemUITests
Change-Id: Id4739679e633c25abccd9093d15eb5e016cd5ac2
parent e29c639e
Loading
Loading
Loading
Loading
+30 −16
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.Parcelable

interface Flag<T> {
    val id: Int
    val teamfood: Boolean
}

interface ParcelableFlag<T> : Flag<T>, Parcelable {
@@ -44,7 +45,8 @@ interface SysPropFlag<T> : Flag<T> {

data class BooleanFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Boolean = false
    override val default: Boolean = false,
    override val teamfood: Boolean = false
) : ParcelableFlag<Boolean> {

    companion object {
@@ -66,20 +68,25 @@ data class BooleanFlag @JvmOverloads constructor(
    }
}

data class ResourceBooleanFlag constructor(
data class ResourceBooleanFlag @JvmOverloads constructor(
    override val id: Int,
    @BoolRes override val resourceId: Int
    @BoolRes override val resourceId: Int,
    override val teamfood: Boolean = false
) : ResourceFlag<Boolean>

data class SysPropBooleanFlag constructor(
data class SysPropBooleanFlag @JvmOverloads constructor(
    override val id: Int,
    override val name: String,
    override val default: Boolean = false
) : SysPropFlag<Boolean>
) : SysPropFlag<Boolean> {
    // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
    override val teamfood: Boolean = false
}

data class StringFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: String = ""
    override val default: String = "",
    override val teamfood: Boolean = false
) : ParcelableFlag<String> {
    companion object {
        @JvmField
@@ -100,14 +107,16 @@ data class StringFlag @JvmOverloads constructor(
    }
}

data class ResourceStringFlag constructor(
data class ResourceStringFlag @JvmOverloads constructor(
    override val id: Int,
    @StringRes override val resourceId: Int
    @StringRes override val resourceId: Int,
    override val teamfood: Boolean = false
) : ResourceFlag<String>

data class IntFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Int = 0
    override val default: Int = 0,
    override val teamfood: Boolean = false
) : ParcelableFlag<Int> {

    companion object {
@@ -129,14 +138,16 @@ data class IntFlag @JvmOverloads constructor(
    }
}

data class ResourceIntFlag constructor(
data class ResourceIntFlag @JvmOverloads constructor(
    override val id: Int,
    @IntegerRes override val resourceId: Int
    @IntegerRes override val resourceId: Int,
    override val teamfood: Boolean = false
) : ResourceFlag<Int>

data class LongFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Long = 0
    override val default: Long = 0,
    override val teamfood: Boolean = false
) : ParcelableFlag<Long> {

    companion object {
@@ -160,7 +171,8 @@ data class LongFlag @JvmOverloads constructor(

data class FloatFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Float = 0f
    override val default: Float = 0f,
    override val teamfood: Boolean = false
) : ParcelableFlag<Float> {

    companion object {
@@ -182,14 +194,16 @@ data class FloatFlag @JvmOverloads constructor(
    }
}

data class ResourceFloatFlag constructor(
data class ResourceFloatFlag @JvmOverloads constructor(
    override val id: Int,
    override val resourceId: Int
    override val resourceId: Int,
    override val teamfood: Boolean = false
) : ResourceFlag<Int>

data class DoubleFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Double = 0.0
    override val default: Double = 0.0,
    override val teamfood: Boolean = false
) : ParcelableFlag<Double> {

    companion object {
+2 −2
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@ class FlagManager constructor(
        intent.setPackage(RECEIVING_PACKAGE)

        return CallbackToFutureAdapter.getFuture {
            completer: CallbackToFutureAdapter.Completer<Any?> ->
            completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
                context.sendOrderedBroadcast(intent, null,
                    object : BroadcastReceiver() {
                        override fun onReceive(context: Context, intent: Intent) {
@@ -79,7 +79,7 @@ class FlagManager constructor(
                        }
                    }, null, Activity.RESULT_OK, "extra data", null)
            "QueryingFlags"
        } as ListenableFuture<Collection<Flag<*>>>
        }
    }

    /**
+4 −2
Original line number Diff line number Diff line
@@ -19,11 +19,12 @@ package com.android.systemui.flags
import android.content.Context
import android.os.Handler
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
import dagger.Binds
import dagger.Module
import dagger.Provides
import java.util.function.Supplier
import javax.inject.Named

@Module(includes = [
    SettingsUtilModule::class
@@ -42,6 +43,7 @@ abstract class FlagsModule {

        @JvmStatic
        @Provides
        fun providesFlagCollector(): Supplier<Map<Int, Flag<*>>>? = null
        @Named(ALL_FLAGS)
        fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
    }
}
 No newline at end of file
+35 −16
Original line number Diff line number Diff line
@@ -50,9 +50,9 @@ 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;
import javax.inject.Named;

/**
 * Concrete implementation of the a Flag manager that returns default values for debug builds
@@ -66,12 +66,13 @@ import javax.inject.Inject;
@SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    private static final String TAG = "SysUIFlags";
    static final String ALL_FLAGS = "all_flags";

    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, Flag<?>> mAllFlags;
    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
    private final IStatusBarService mBarService;
@@ -84,13 +85,13 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            SystemPropertiesHelper systemProperties,
            @Main Resources resources,
            DumpManager dumpManager,
            @Nullable Supplier<Map<Integer, Flag<?>>> flagsCollector,
            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
            IStatusBarService barService) {
        mFlagManager = flagManager;
        mSecureSettings = secureSettings;
        mResources = resources;
        mSystemProperties = systemProperties;
        mFlagsCollector = flagsCollector != null ? flagsCollector : Flags::collectFlags;
        mAllFlags = allFlags;
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_SET_FLAG);
        filter.addAction(ACTION_GET_FLAGS);
@@ -107,7 +108,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            mBooleanFlagCache.put(id,
                    readFlagValue(id, flag.getDefault(), BooleanFlagSerializer.INSTANCE));
                    readFlagValue(id, flag.getDefault()));
        }

        return mBooleanFlagCache.get(id);
@@ -118,8 +119,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            mBooleanFlagCache.put(id,
                    readFlagValue(id, mResources.getBoolean(flag.getResourceId()),
                            BooleanFlagSerializer.INSTANCE));
                    readFlagValue(id, mResources.getBoolean(flag.getResourceId())));
        }

        return mBooleanFlagCache.get(id);
@@ -129,8 +129,13 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            // Use #readFlagValue to get the default. That will allow it to fall through to
            // teamfood if need be.
            mBooleanFlagCache.put(
                    id, mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
                    id,
                    mSystemProperties.getBoolean(
                            flag.getName(),
                            readFlagValue(id, flag.getDefault())));
        }

        return mBooleanFlagCache.get(id);
@@ -161,6 +166,19 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        return mStringFlagCache.get(id);
    }

    /** Specific override for Boolean flags that checks against the teamfood list.*/
    private boolean readFlagValue(int id, boolean defaultValue) {
        Boolean result = readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
        // Only check for teamfood if the default is false.
        if (!defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
            if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
                return isEnabled(Flags.TEAMFOOD);
            }
        }

        return result == null ? defaultValue : result;
    }

    @NonNull
    private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
        requireNonNull(defaultValue, "defaultValue");
@@ -266,8 +284,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            if (ACTION_SET_FLAG.equals(action)) {
                handleSetFlag(intent.getExtras());
            } else if (ACTION_GET_FLAGS.equals(action)) {
                Map<Integer, Flag<?>> knownFlagMap = mFlagsCollector.get();
                ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
                ArrayList<Flag<?>> flags = new ArrayList<>(mAllFlags.values());

                // Convert all flags to parcelable flags.
                ArrayList<ParcelableFlag<?>> pFlags = new ArrayList<>();
@@ -296,12 +313,11 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
                return;
            }

            Map<Integer, Flag<?>> flagMap = mFlagsCollector.get();
            if (!flagMap.containsKey(id)) {
            if (!mAllFlags.containsKey(id)) {
                Log.w(TAG, "Tried to set unknown id: " + id);
                return;
            }
            Flag<?> flag = flagMap.get(id);
            Flag<?> flag = mAllFlags.get(id);

            if (!extras.containsKey(EXTRA_VALUE)) {
                eraseFlag(flag);
@@ -338,13 +354,16 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        @Nullable
        private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
            if (f instanceof BooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f));
                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f), f.getTeamfood());
            }
            if (f instanceof ResourceBooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
                return new BooleanFlag(
                        f.getId(), isEnabled((ResourceBooleanFlag) f), f.getTeamfood());
            }
            if (f instanceof SysPropBooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((SysPropBooleanFlag) f));
                // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                return new BooleanFlag(
                        f.getId(), isEnabled((SysPropBooleanFlag) f), false);
            }

            // TODO: add support for other flag types.
+45 −2
Original line number Diff line number Diff line
@@ -67,9 +67,14 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
    private lateinit var mBroadcastReceiver: BroadcastReceiver
    private lateinit var mClearCacheAction: Consumer<Int>

    private val teamfoodableFlagA = BooleanFlag(500, false, true)
    private val teamfoodableFlagB = BooleanFlag(501, true, true)

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        mFlagMap.put(teamfoodableFlagA.id, teamfoodableFlagA)
        mFlagMap.put(teamfoodableFlagB.id, teamfoodableFlagB)
        mFeatureFlagsDebug = FeatureFlagsDebug(
            mFlagManager,
            mMockContext,
@@ -77,7 +82,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
            mSystemProperties,
            mResources,
            mDumpManager,
            { mFlagMap },
            mFlagMap,
            mBarService
        )
        verify(mFlagManager).onSettingsChangedAction = any()
@@ -93,12 +98,50 @@ class FeatureFlagsDebugTest : SysuiTestCase() {

    @Test
    fun testReadBooleanFlag() {
        // Remember that the TEAMFOOD flag is id#1 and has special behavior.
        whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
        whenever(mFlagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(1, false))).isFalse()
        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(2, true))).isTrue()
        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(3, false))).isTrue()
        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(4, true))).isFalse()
        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(5, false))).isFalse()
    }

    @Test
    fun testTeamFoodFlag_False() {
        whenever(mFlagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false)
        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()

        // Regular boolean flags should still test the same.
        // Only our teamfoodableFlag should change.
        testReadBooleanFlag()
    }

    @Test
    fun testTeamFoodFlag_True() {
        whenever(mFlagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()

        // Regular boolean flags should still test the same.
        // Only our teamfoodableFlag should change.
        testReadBooleanFlag()
    }

    @Test
    fun testTeamFoodFlag_Overridden() {
        whenever(mFlagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
                .thenReturn(true)
        whenever(mFlagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
                .thenReturn(false)
        whenever(mFlagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()

        // Regular boolean flags should still test the same.
        // Only our teamfoodableFlag should change.
        testReadBooleanFlag()
    }

    @Test