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

Commit 62ac7729 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Better support for Resource based flags.

Introduce ResourceFlag class and ParcelableFlag classes, from which
other flag types are based.

ResourceFlags always pull there default values out of
context.getResources. They can not, however, be directly
serialized and sent to the flag app.

For that, we now have ParcelableFlag. Prior to this change, we
were not properly alerting the Flag app about the current value of
flags when it asked. This change now also fixes that.

All flags are coerced into ParcelableFlag before being sent to the
app.

Note that ResourceFlags perform runtime logic, even in release builds,
meaing that the opportunity for optimizing them at compile time is
much smaller. Tools such as proguard will not have the opportunity
to strip them out entirely.

Bug: 203548827
Test: atest SystemUITests
Change-Id: I3c37ccae16c00237f9cffffc9c22580454ecb8cc
parent 2389bc5b
Loading
Loading
Loading
Loading
+42 −24
Original line number Diff line number Diff line
@@ -16,28 +16,31 @@

package com.android.systemui.flags

import android.annotation.BoolRes
import android.annotation.IntegerRes
import android.annotation.StringRes
import android.os.Parcel
import android.os.Parcelable

interface Flag<T> : Parcelable {
interface Flag<T> {
    val id: Int
    val default: T
    val resourceOverride: Int
}

interface ParcelableFlag<T> : Flag<T>, Parcelable {
    val default: T
    override fun describeContents() = 0

    fun hasResourceOverride(): Boolean {
        return resourceOverride != -1
}

interface ResourceFlag<T> : Flag<T> {
    val resourceId: Int
}

// Consider using the "parcelize" kotlin library.

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

    companion object {
        @JvmField
@@ -58,11 +61,15 @@ data class BooleanFlag @JvmOverloads constructor(
    }
}

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

data class StringFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: String = "",
    override val resourceOverride: Int = -1
) : Flag<String> {
    override val default: String = ""
) : ParcelableFlag<String> {
    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<StringFlag> {
@@ -82,11 +89,15 @@ data class StringFlag @JvmOverloads constructor(
    }
}

data class ResourceStringFlag constructor(
    override val id: Int,
    @StringRes override val resourceId: Int
) : ResourceFlag<String>

data class IntFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Int = 0,
    override val resourceOverride: Int = -1
) : Flag<Int> {
    override val default: Int = 0
) : ParcelableFlag<Int> {

    companion object {
        @JvmField
@@ -107,11 +118,15 @@ data class IntFlag @JvmOverloads constructor(
    }
}

data class ResourceIntFlag constructor(
    override val id: Int,
    @IntegerRes override val resourceId: Int
) : ResourceFlag<Int>

data class LongFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Long = 0,
    override val resourceOverride: Int = -1
) : Flag<Long> {
    override val default: Long = 0
) : ParcelableFlag<Long> {

    companion object {
        @JvmField
@@ -134,9 +149,8 @@ data class LongFlag @JvmOverloads constructor(

data class FloatFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Float = 0f,
    override val resourceOverride: Int = -1
) : Flag<Float> {
    override val default: Float = 0f
) : ParcelableFlag<Float> {

    companion object {
        @JvmField
@@ -157,11 +171,15 @@ data class FloatFlag @JvmOverloads constructor(
    }
}

data class ResourceFloatFlag constructor(
    override val id: Int,
    override val resourceId: Int
) : ResourceFlag<Int>

data class DoubleFlag @JvmOverloads constructor(
    override val id: Int,
    override val default: Double = 0.0,
    override val resourceOverride: Int = -1
) : Flag<Double> {
    override val default: Double = 0.0
) : ParcelableFlag<Double> {

    companion object {
        @JvmField
+5 −1
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ class FlagManager constructor(
                    object : BroadcastReceiver() {
                        override fun onReceive(context: Context, intent: Intent) {
                            val extras: Bundle? = getResultExtras(false)
                            val listOfFlags: java.util.ArrayList<Flag<*>>? =
                            val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
                                extras?.getParcelableArrayList(FIELD_FLAGS)
                            if (listOfFlags != null) {
                                completer.set(listOfFlags)
@@ -108,6 +108,10 @@ class FlagManager constructor(
        }
    }

    override fun isEnabled(flag: ResourceBooleanFlag): Boolean {
        throw RuntimeException("Not implemented in FlagManager")
    }

    override fun addListener(listener: FlagReader.Listener) {
        synchronized(listeners) {
            val registerNeeded = listeners.isEmpty()
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ interface FlagReader {
        return flag.default
    }

    fun isEnabled(flag: ResourceBooleanFlag): Boolean

    /** Returns a boolean value for the given flag.  */
    fun isEnabled(id: Int, def: Boolean): Boolean {
        return def
+43 −15
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.systemui.flags.FlagManager.FIELD_FLAGS;
import static com.android.systemui.flags.FlagManager.FIELD_ID;
import static com.android.systemui.flags.FlagManager.FIELD_VALUE;

import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -30,7 +31,6 @@ import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.BoolRes;
import androidx.annotation.NonNull;

import com.android.systemui.Dumpable;
@@ -89,16 +89,18 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
    public boolean isEnabled(BooleanFlag flag) {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            boolean def = flag.getDefault();
            if (flag.hasResourceOverride()) {
                try {
                    def = isEnabledInOverlay(flag.getResourceOverride());
                } catch (Resources.NotFoundException e) {
                    // no-op
            mBooleanFlagCache.put(id, isEnabled(id, flag.getDefault()));
        }

        return mBooleanFlagCache.get(id);
    }

            mBooleanFlagCache.put(id, isEnabled(id, def));
    @Override
    public boolean isEnabled(ResourceBooleanFlag flag) {
        int id = flag.getId();
        if (!mBooleanFlagCache.containsKey(id)) {
            mBooleanFlagCache.put(
                    id, isEnabled(id, mResources.getBoolean(flag.getResourceId())));
        }

        return mBooleanFlagCache.get(id);
@@ -111,6 +113,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        return result == null ? defaultValue : result;
    }


    /** Returns the stored value or null if not set. */
    private Boolean isEnabledInternal(int id) {
        try {
@@ -121,10 +124,6 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
        return null;
    }

    private boolean isEnabledInOverlay(@BoolRes int resId) {
        return mResources.getBoolean(resId);
    }

    /** Set whether a given {@link BooleanFlag} is enabled or not. */
    public void setEnabled(int id, boolean value) {
        Boolean currentValue = isEnabledInternal(id);
@@ -185,9 +184,19 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
            } else if (ACTION_GET_FLAGS.equals(action)) {
                Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
                ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());

                // Convert all flags to parcelable flags.
                ArrayList<ParcelableFlag<?>> pFlags = new ArrayList<>();
                for (Flag<?> f : flags) {
                    ParcelableFlag<?> pf = toParcelableFlag(f);
                    if (pf != null) {
                        pFlags.add(pf);
                    }
                }

                Bundle extras =  getResultExtras(true);
                if (extras != null) {
                    extras.putParcelableArrayList(FIELD_FLAGS, flags);
                    extras.putParcelableArrayList(FIELD_FLAGS, pFlags);
                }
            }
        }
@@ -215,6 +224,25 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
                setEnabled(id, extras.getBoolean(FIELD_VALUE));
            }
        }

        /**
         * Ensures that the data we send to the app reflects the current state of the flags.
         *
         * Also converts an non-parcelable versions of the flags to their parcelable versions.
         */
        @Nullable
        private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
            if (f instanceof BooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f));
            }
            if (f instanceof ResourceBooleanFlag) {
                return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
            }

            // TODO: add support for other flag types.
            Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
            return null;
        }
    };

    @Override
+20 −6
Original line number Diff line number Diff line
@@ -16,12 +16,14 @@

package com.android.systemui.flags;

import android.content.res.Resources;
import android.util.SparseBooleanArray;

import androidx.annotation.NonNull;

import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;

import java.io.FileDescriptor;
@@ -37,9 +39,11 @@ import javax.inject.Inject;
 */
@SysUISingleton
public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
    SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
    private final Resources mResources;
    SparseBooleanArray mFlagCache = new SparseBooleanArray();
    @Inject
    public FeatureFlagsRelease(DumpManager dumpManager) {
    public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
        mResources = resources;
        dumpManager.registerDumpable("SysUIFlags", this);
    }

@@ -54,19 +58,29 @@ public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
        return isEnabled(flag.getId(), flag.getDefault());
    }

    @Override
    public boolean isEnabled(ResourceBooleanFlag flag) {
        int cacheIndex = mFlagCache.indexOfKey(flag.getId());
        if (cacheIndex < 0) {
            return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
        }

        return mFlagCache.valueAt(cacheIndex);
    }

    @Override
    public boolean isEnabled(int key, boolean defaultValue) {
        mAccessedFlags.append(key, defaultValue);
        mFlagCache.append(key, defaultValue);
        return defaultValue;
    }

    @Override
    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
        pw.println("can override: false");
        int size = mAccessedFlags.size();
        int size = mFlagCache.size();
        for (int i = 0; i < size; i++) {
            pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
                    + ": " + mAccessedFlags.valueAt(i));
            pw.println("  sysui_flag_" + mFlagCache.keyAt(i)
                    + ": " + mFlagCache.valueAt(i));
        }
    }
}
Loading