Merge "Enable Int and String flags." into tm-qpr-dev

This commit is contained in:
Dave Mankoff
2022-11-04 13:31:08 +00:00
committed by Android (Google) Code Review
8 changed files with 273 additions and 31 deletions

View File

@@ -24,6 +24,7 @@ private const val FIELD_VALUE = "value"
private const val FIELD_TYPE = "type"
private const val TYPE_BOOLEAN = "boolean"
private const val TYPE_STRING = "string"
private const val TYPE_INT = "int"
private const val TAG = "FlagSerializer"
@@ -77,4 +78,10 @@ object StringFlagSerializer : FlagSerializer<String>(
JSONObject::getString
)
object IntFlagSerializer : FlagSerializer<Int>(
TYPE_INT,
JSONObject::put,
JSONObject::getInt
)
class InvalidFlagStorageException : Exception("Data found but is invalid")

View File

@@ -44,4 +44,10 @@ interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a string value for the given flag. */
fun getString(flag: ResourceStringFlag): String
/** Returns an int value for a given flag/ */
fun getInt(flag: IntFlag): Int
/** Returns an int value for a given flag/ */
fun getInt(flag: ResourceIntFlag): Int
}

View File

@@ -81,6 +81,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>();
private final Restarter mRestarter;
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -209,6 +210,31 @@ public class FeatureFlagsDebug implements FeatureFlags {
return mStringFlagCache.get(id);
}
@NonNull
@Override
public int getInt(@NonNull IntFlag flag) {
int id = flag.getId();
if (!mIntFlagCache.containsKey(id)) {
mIntFlagCache.put(id,
readFlagValue(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
}
return mIntFlagCache.get(id);
}
@NonNull
@Override
public int getInt(@NonNull ResourceIntFlag flag) {
int id = flag.getId();
if (!mIntFlagCache.containsKey(id)) {
mIntFlagCache.put(id,
readFlagValue(id, mResources.getInteger(flag.getResourceId()),
IntFlagSerializer.INSTANCE));
}
return mIntFlagCache.get(id);
}
/** Specific override for Boolean flags that checks against the teamfood list. */
private boolean readFlagValue(int id, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(id);
@@ -351,6 +377,16 @@ public class FeatureFlagsDebug implements FeatureFlags {
}
}
void setIntFlagInternal(Flag<?> flag, int value) {
if (flag instanceof IntFlag) {
setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceIntFlag) {
setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
} else {
throw new IllegalArgumentException("Unknown flag type");
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {

View File

@@ -165,6 +165,18 @@ public class FeatureFlagsRelease implements FeatureFlags {
return defaultValue;
}
@NonNull
@Override
public int getInt(@NonNull IntFlag flag) {
return flag.getDefault();
}
@NonNull
@Override
public int getInt(@NonNull ResourceIntFlag flag) {
return mResources.getInteger(flag.getResourceId());
}
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: false");

View File

@@ -38,6 +38,7 @@ public class FlagCommand implements Command {
private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
private final List<String> mSetCommands = List.of("set", "put");
private final FeatureFlagsDebug mFeatureFlags;
private final Map<Integer, Flag<?>> mAllFlags;
@@ -60,12 +61,6 @@ public class FlagCommand implements Command {
return;
}
if (args.size() > 2) {
pw.println("Invalid number of arguments.");
help(pw);
return;
}
int id = 0;
try {
id = Integer.parseInt(args.get(0));
@@ -85,48 +80,113 @@ public class FlagCommand implements Command {
Flag<?> flag = mAllFlags.get(id);
String cmd = "";
if (args.size() == 2) {
if (args.size() > 1) {
cmd = args.get(1).toLowerCase();
}
if ("erase".equals(cmd) || "reset".equals(cmd)) {
if (args.size() > 2) {
pw.println("Invalid number of arguments to reset a flag.");
help(pw);
return;
}
mFeatureFlags.eraseFlag(flag);
return;
}
boolean newValue = true;
if (args.size() == 1 || "toggle".equals(cmd)) {
boolean enabled = isBooleanFlagEnabled(flag);
if (args.size() == 1) {
pw.println("Flag " + id + " is " + enabled);
boolean shouldSet = true;
if (args.size() == 1) {
shouldSet = false;
}
if (isBooleanFlag(flag)) {
if (args.size() > 2) {
pw.println("Invalid number of arguments for a boolean flag.");
help(pw);
return;
}
newValue = !enabled;
} else {
newValue = mOnCommands.contains(cmd);
if (!newValue && !mOffCommands.contains(cmd)) {
boolean newValue = isBooleanFlagEnabled(flag);
if ("toggle".equals(cmd)) {
newValue = !newValue;
} else if (mOnCommands.contains(cmd)) {
newValue = true;
} else if (mOffCommands.contains(cmd)) {
newValue = false;
} else if (shouldSet) {
pw.println("Invalid on/off argument supplied");
help(pw);
return;
}
}
pw.flush(); // Next command will restart sysui, so flush before we do so.
mFeatureFlags.setBooleanFlagInternal(flag, newValue);
pw.println("Flag " + id + " is " + newValue);
pw.flush(); // Next command will restart sysui, so flush before we do so.
if (shouldSet) {
mFeatureFlags.setBooleanFlagInternal(flag, newValue);
}
return;
} else if (isStringFlag(flag)) {
if (shouldSet) {
if (args.size() != 3) {
pw.println("Invalid number of arguments a StringFlag.");
help(pw);
return;
} else if (!mSetCommands.contains(cmd)) {
pw.println("Unknown command: " + cmd);
help(pw);
return;
}
String value = args.get(2);
pw.println("Setting Flag " + id + " to " + value);
pw.flush(); // Next command will restart sysui, so flush before we do so.
mFeatureFlags.setStringFlagInternal(flag, args.get(2));
} else {
pw.println("Flag " + id + " is " + getStringFlag(flag));
}
return;
} else if (isIntFlag(flag)) {
if (shouldSet) {
if (args.size() != 3) {
pw.println("Invalid number of arguments for an IntFlag.");
help(pw);
return;
} else if (!mSetCommands.contains(cmd)) {
pw.println("Unknown command: " + cmd);
help(pw);
return;
}
int value = Integer.parseInt(args.get(2));
pw.println("Setting Flag " + id + " to " + value);
pw.flush(); // Next command will restart sysui, so flush before we do so.
mFeatureFlags.setIntFlagInternal(flag, value);
} else {
pw.println("Flag " + id + " is " + getIntFlag(flag));
}
return;
}
}
@Override
public void help(PrintWriter pw) {
pw.println(
"Usage: adb shell cmd statusbar flag <id> "
pw.println("Usage: adb shell cmd statusbar flag <id> [options]");
pw.println();
pw.println(" Boolean Flag Options: "
+ "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
pw.println(" String Flag Options: [set|put \"<value>\"]");
pw.println(" Int Flag Options: [set|put <value>]");
pw.println();
pw.println("The id can either be a numeric integer or the corresponding field name");
pw.println(
"If no argument is supplied after the id, the flags runtime value is output");
}
private boolean isBooleanFlag(Flag<?> flag) {
return (flag instanceof BooleanFlag)
|| (flag instanceof ResourceBooleanFlag)
|| (flag instanceof SysPropFlag)
|| (flag instanceof DeviceConfigBooleanFlag);
}
private boolean isBooleanFlagEnabled(Flag<?> flag) {
if (flag instanceof ReleasedFlag) {
return mFeatureFlags.isEnabled((ReleasedFlag) flag);
@@ -141,6 +201,34 @@ public class FlagCommand implements Command {
return false;
}
private boolean isStringFlag(Flag<?> flag) {
return (flag instanceof StringFlag) || (flag instanceof ResourceStringFlag);
}
private String getStringFlag(Flag<?> flag) {
if (flag instanceof StringFlag) {
return mFeatureFlags.getString((StringFlag) flag);
} else if (flag instanceof ResourceStringFlag) {
return mFeatureFlags.getString((ResourceStringFlag) flag);
}
return "";
}
private boolean isIntFlag(Flag<?> flag) {
return (flag instanceof IntFlag) || (flag instanceof ResourceIntFlag);
}
private int getIntFlag(Flag<?> flag) {
if (flag instanceof IntFlag) {
return mFeatureFlags.getInt((IntFlag) flag);
} else if (flag instanceof ResourceIntFlag) {
return mFeatureFlags.getInt((ResourceIntFlag) flag);
}
return 0;
}
private int flagNameToId(String flagName) {
List<Field> fields = Flags.getFlagFields();
for (Field field : fields) {
@@ -176,13 +264,15 @@ public class FlagCommand implements Command {
for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
pw.print(" ");
}
pw.println("ID Enabled?");
pw.println("ID Value");
for (int i = 0; i < longestFieldName; i++) {
pw.print("=");
}
pw.println(" ==== ========");
pw.println(" ==== =====");
for (Field field : fields) {
int id = fieldToId(field);
Flag<?> flag = mAllFlags.get(id);
if (id == 0 || !mAllFlags.containsKey(id)) {
continue;
}
@@ -192,7 +282,15 @@ public class FlagCommand implements Command {
pw.print(" ");
}
pw.printf("%-4d ", id);
pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
if (isBooleanFlag(flag)) {
pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
} else if (isStringFlag(flag)) {
pw.println(getStringFlag(flag));
} else if (isIntFlag(flag)) {
pw.println(getIntFlag(flag));
} else {
pw.println("<unknown flag type>");
}
}
}
}

View File

@@ -20,6 +20,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -245,6 +246,43 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
}
}
@Test
fun readIntFlag() {
whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, 12))).isEqualTo(12)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, 93))).isEqualTo(93)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, 8))).isEqualTo(22)
assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, 234))).isEqualTo(48)
}
@Test
fun readResourceIntFlag() {
whenever(resources.getInteger(1001)).thenReturn(88)
whenever(resources.getInteger(1002)).thenReturn(61)
whenever(resources.getInteger(1003)).thenReturn(9342)
whenever(resources.getInteger(1004)).thenThrow(NotFoundException("unknown resource"))
whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource"))
whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource"))
whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(20)
whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, 1001))).isEqualTo(88)
assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, 1002))).isEqualTo(61)
assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, 1003))).isEqualTo(20)
Assert.assertThrows(NotFoundException::class.java) {
mFeatureFlagsDebug.getInt(ResourceIntFlag(4, 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NotFoundException::class.java) {
mFeatureFlagsDebug.getInt(ResourceIntFlag(5, 1005))
}
}
@Test
fun broadcastReceiver_IgnoresInvalidData() {
addFlag(UnreleasedFlag(1))

View File

@@ -35,6 +35,8 @@ class FlagCommandTest : SysuiTestCase() {
private val flagMap = mutableMapOf<Int, Flag<*>>()
private val flagA = UnreleasedFlag(500)
private val flagB = ReleasedFlag(501)
private val stringFlag = StringFlag(502, "abracadabra")
private val intFlag = IntFlag(503, 12)
private lateinit var cmd: FlagCommand
@@ -44,26 +46,59 @@ class FlagCommandTest : SysuiTestCase() {
whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
whenever(featureFlags.getString(any(StringFlag::class.java))).thenAnswer { invocation ->
(invocation.getArgument(0) as StringFlag).default
}
whenever(featureFlags.getInt(any(IntFlag::class.java))).thenAnswer { invocation ->
(invocation.getArgument(0) as IntFlag).default
}
flagMap.put(flagA.id, flagA)
flagMap.put(flagB.id, flagB)
flagMap.put(stringFlag.id, stringFlag)
flagMap.put(intFlag.id, intFlag)
cmd = FlagCommand(featureFlags, flagMap)
}
@Test
fun readFlagCommand() {
fun readBooleanFlagCommand() {
cmd.execute(pw, listOf(flagA.id.toString()))
Mockito.verify(featureFlags).isEnabled(flagA)
}
@Test
fun setFlagCommand() {
fun readStringFlagCommand() {
cmd.execute(pw, listOf(stringFlag.id.toString()))
Mockito.verify(featureFlags).getString(stringFlag)
}
@Test
fun readIntFlag() {
cmd.execute(pw, listOf(intFlag.id.toString()))
Mockito.verify(featureFlags).getInt(intFlag)
}
@Test
fun setBooleanFlagCommand() {
cmd.execute(pw, listOf(flagB.id.toString(), "on"))
Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
}
@Test
fun toggleFlagCommand() {
fun setStringFlagCommand() {
cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar"))
Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar")
}
@Test
fun setIntFlag() {
cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123"))
Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123)
}
@Test
fun toggleBooleanFlagCommand() {
cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
}

View File

@@ -21,6 +21,7 @@ import java.io.PrintWriter
class FakeFeatureFlags : FeatureFlags {
private val booleanFlags = mutableMapOf<Int, Boolean>()
private val stringFlags = mutableMapOf<Int, String>()
private val intFlags = mutableMapOf<Int, Int>()
private val knownFlagNames = mutableMapOf<Int, String>()
private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
@@ -95,6 +96,10 @@ class FakeFeatureFlags : FeatureFlags {
override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
override fun getInt(flag: IntFlag): Int = requireIntValue(flag.id)
override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.id)
override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
@@ -118,11 +123,16 @@ class FakeFeatureFlags : FeatureFlags {
private fun requireBooleanValue(flagId: Int): Boolean {
return booleanFlags[flagId]
?: error("Flag ${flagName(flagId)} was accessed but not specified.")
?: error("Flag ${flagName(flagId)} was accessed as boolean but not specified.")
}
private fun requireStringValue(flagId: Int): String {
return stringFlags[flagId]
?: error("Flag ${flagName(flagId)} was accessed but not specified.")
?: error("Flag ${flagName(flagId)} was accessed as string but not specified.")
}
private fun requireIntValue(flagId: Int): Int {
return intFlags[flagId]
?: error("Flag ${flagName(flagId)} was accessed as int but not specified.")
}
}