Enable Int and String flags.
Puts mechanics in to support IntFlag and ResourceIntFlag. Also adds support for both IntFlag and StringFlag via the command line. Support in the app will come later. Bug: 257066497 Test: manual via the command line. Change-Id: I378f0f1e0db028cee33f729c47a63262124389d7
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user