diff options
3 files changed, 363 insertions, 106 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index aaa4d9eefd29..c4531b573d22 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -42,10 +42,14 @@ 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 com.android.systemui.statusbar.commandline.Command; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; +import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; @@ -59,6 +63,10 @@ import javax.inject.Named; * * Flags can be set (or unset) via the following adb command: * + * adb shell cmd statusbar flag <id> <on|off|toggle|erase> + * + * Alternatively, you can change flags via a broadcast intent: + * * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>] * * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command. @@ -67,6 +75,7 @@ import javax.inject.Named; public class FeatureFlagsDebug implements FeatureFlags, Dumpable { private static final String TAG = "SysUIFlags"; static final String ALL_FLAGS = "all_flags"; + private static final String FLAG_COMMAND = "flag"; private final FlagManager mFlagManager; private final SecureSettings mSecureSettings; @@ -86,12 +95,15 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { @Main Resources resources, DumpManager dumpManager, @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags, + CommandRegistry commandRegistry, IStatusBarService barService) { mFlagManager = flagManager; mSecureSettings = secureSettings; mResources = resources; mSystemProperties = systemProperties; mAllFlags = allFlags; + mBarService = barService; + IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_SET_FLAG); filter.addAction(ACTION_GET_FLAGS); @@ -100,7 +112,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { context.registerReceiver(mReceiver, filter, null, null, Context.RECEIVER_EXPORTED_UNAUDITED); dumpManager.registerDumpable(TAG, this); - mBarService = barService; + commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new); } @Override @@ -276,6 +288,31 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { } } + private void setBooleanFlagInternal(Flag<?> flag, boolean value) { + if (flag instanceof BooleanFlag) { + setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE); + } else if (flag instanceof ResourceBooleanFlag) { + setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE); + } else if (flag instanceof SysPropBooleanFlag) { + // Store SysProp flags in SystemProperties where they can read by outside parties. + mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value); + dispatchListenersAndMaybeRestart(flag.getId(), + FeatureFlagsDebug.this::restartAndroid); + } else { + throw new IllegalArgumentException("Unknown flag type"); + } + } + + private void setStringFlagInternal(Flag<?> flag, String value) { + if (flag instanceof StringFlag) { + setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE); + } else if (flag instanceof ResourceStringFlag) { + setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE); + } else { + throw new IllegalArgumentException("Unknown flag type"); + } + } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -327,24 +364,19 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { } Object value = extras.get(EXTRA_VALUE); - if (flag instanceof BooleanFlag && value instanceof Boolean) { - setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE); - } else if (flag instanceof ResourceBooleanFlag && value instanceof Boolean) { - setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE); - } else if (flag instanceof SysPropBooleanFlag && value instanceof Boolean) { - // Store SysProp flags in SystemProperties where they can read by outside parties. - mSystemProperties.setBoolean( - ((SysPropBooleanFlag) flag).getName(), (Boolean) value); - dispatchListenersAndMaybeRestart(flag.getId(), - FeatureFlagsDebug.this::restartAndroid); - } else if (flag instanceof StringFlag && value instanceof String) { - setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE); - } else if (flag instanceof ResourceStringFlag && value instanceof String) { - setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE); - } else { + + try { + if (value instanceof Boolean) { + setBooleanFlagInternal(flag, (Boolean) value); + } else if (value instanceof String) { + setStringFlagInternal(flag, (String) value); + } else { + throw new IllegalArgumentException("Unknown value type"); + } + } catch (IllegalArgumentException e) { Log.w(TAG, - "Unable to set " + id + " of type " + flag.getClass() + " to value of type " - + (value == null ? null : value.getClass())); + "Unable to set " + flag.getId() + " of type " + flag.getClass() + + " to value of type " + (value == null ? null : value.getClass())); } } @@ -388,4 +420,153 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { mStringFlagCache.forEach((key, value) -> pw.println(" sysui_flag_" + key + ": [length=" + value.length() + "] \"" + value + "\"")); } + + 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"); + + @Override + public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) { + if (args.size() == 0) { + pw.println("Error: no flag id supplied"); + help(pw); + pw.println(); + printKnownFlags(pw); + return; + } + + if (args.size() > 2) { + pw.println("Invalid number of arguments."); + help(pw); + return; + } + + int id = 0; + try { + id = Integer.parseInt(args.get(0)); + if (!mAllFlags.containsKey(id)) { + pw.println("Unknown flag id: " + id); + pw.println(); + printKnownFlags(pw); + return; + } + } catch (NumberFormatException e) { + id = flagNameToId(args.get(0)); + if (id == 0) { + pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0)); + return; + } + } + Flag<?> flag = mAllFlags.get(id); + + String cmd = ""; + if (args.size() == 2) { + cmd = args.get(1).toLowerCase(); + } + + if ("erase".equals(cmd) || "reset".equals(cmd)) { + 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); + return; + } + + newValue = !enabled; + } else { + newValue = mOnCommands.contains(cmd); + if (!newValue && !mOffCommands.contains(cmd)) { + pw.println("Invalid on/off argument supplied"); + help(pw); + return; + } + } + + pw.flush(); // Next command will restart sysui, so flush before we do so. + setBooleanFlagInternal(flag, newValue); + } + + @Override + public void help(PrintWriter pw) { + pw.println( + "Usage: adb shell cmd statusbar flag <id> " + + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]"); + 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 isBooleanFlagEnabled(Flag<?> flag) { + if (flag instanceof BooleanFlag) { + return isEnabled((BooleanFlag) flag); + } else if (flag instanceof ResourceBooleanFlag) { + return isEnabled((ResourceBooleanFlag) flag); + } else if (flag instanceof SysPropFlag) { + return isEnabled((SysPropBooleanFlag) flag); + } + + return false; + } + + private int flagNameToId(String flagName) { + List<Field> fields = Flags.getFlagFields(); + for (Field field : fields) { + if (flagName.equals(field.getName())) { + return fieldToId(field); + } + } + + return 0; + } + + private int fieldToId(Field field) { + try { + Flag<?> flag = (Flag<?>) field.get(null); + return flag.getId(); + } catch (IllegalAccessException e) { + // no-op + } + + return 0; + } + + private void printKnownFlags(PrintWriter pw) { + List<Field> fields = Flags.getFlagFields(); + + int longestFieldName = 0; + for (Field field : fields) { + longestFieldName = Math.max(longestFieldName, field.getName().length()); + } + + pw.println("Known Flags:"); + pw.print("Flag Name"); + for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) { + pw.print(" "); + } + pw.println("ID Enabled?"); + for (int i = 0; i < longestFieldName; i++) { + pw.print("="); + } + pw.println(" ==== ========"); + for (Field field : fields) { + int id = fieldToId(field); + if (id == 0 || !mAllFlags.containsKey(id)) { + continue; + } + pw.print(field.getName()); + int fieldWidth = field.getName().length(); + for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) { + pw.print(" "); + } + pw.printf("%-4d ", id); + pw.println(isBooleanFlagEnabled(mAllFlags.get(id))); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 44580aa4230a..afa7d5e0a9c4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -20,7 +20,9 @@ import com.android.internal.annotations.Keep; import com.android.systemui.R; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -182,25 +184,36 @@ public class Flags { if (sFlagMap != null) { return sFlagMap; } + Map<Integer, Flag<?>> flags = new HashMap<>(); + List<Field> flagFields = getFlagFields(); + + for (Field field : flagFields) { + try { + Flag<?> flag = (Flag<?>) field.get(null); + flags.put(flag.getId(), flag); + } catch (IllegalAccessException e) { + // no-op + } + } + sFlagMap = flags; + + return sFlagMap; + } + + static List<Field> getFlagFields() { Field[] fields = Flags.class.getFields(); + List<Field> result = new ArrayList<>(); for (Field field : fields) { Class<?> t = field.getType(); if (Flag.class.isAssignableFrom(t)) { - try { - Flag<?> flag = (Flag<?>) field.get(null); - flags.put(flag.getId(), flag); - } catch (IllegalAccessException e) { - // no-op - } + result.add(field); } } - sFlagMap = flags; - - return sFlagMap; + return result; } // | . . . . . . . . . . . . . . . . . . . | // | | diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt index 6626bbe69706..b43856aae4cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt @@ -24,7 +24,11 @@ import android.test.suitebuilder.annotation.SmallTest import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor @@ -37,10 +41,12 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyString +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.inOrder import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations import java.io.PrintWriter import java.io.Serializable @@ -56,16 +62,18 @@ import org.mockito.Mockito.`when` as whenever class FeatureFlagsDebugTest : SysuiTestCase() { private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug - @Mock private lateinit var mFlagManager: FlagManager - @Mock private lateinit var mMockContext: Context - @Mock private lateinit var mSecureSettings: SecureSettings - @Mock private lateinit var mSystemProperties: SystemPropertiesHelper - @Mock private lateinit var mResources: Resources - @Mock private lateinit var mDumpManager: DumpManager - @Mock private lateinit var mBarService: IStatusBarService - private val mFlagMap = mutableMapOf<Int, Flag<*>>() - private lateinit var mBroadcastReceiver: BroadcastReceiver - private lateinit var mClearCacheAction: Consumer<Int> + @Mock private lateinit var flagManager: FlagManager + @Mock private lateinit var mockContext: Context + @Mock private lateinit var secureSettings: SecureSettings + @Mock private lateinit var systemProperties: SystemPropertiesHelper + @Mock private lateinit var resources: Resources + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var commandRegistry: CommandRegistry + @Mock private lateinit var barService: IStatusBarService + @Mock private lateinit var pw: PrintWriter + private val flagMap = mutableMapOf<Int, Flag<*>>() + private lateinit var broadcastReceiver: BroadcastReceiver + private lateinit var clearCacheAction: Consumer<Int> private val teamfoodableFlagA = BooleanFlag(500, false, true) private val teamfoodableFlagB = BooleanFlag(501, true, true) @@ -73,34 +81,35 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - mFlagMap.put(teamfoodableFlagA.id, teamfoodableFlagA) - mFlagMap.put(teamfoodableFlagB.id, teamfoodableFlagB) + flagMap.put(teamfoodableFlagA.id, teamfoodableFlagA) + flagMap.put(teamfoodableFlagB.id, teamfoodableFlagB) mFeatureFlagsDebug = FeatureFlagsDebug( - mFlagManager, - mMockContext, - mSecureSettings, - mSystemProperties, - mResources, - mDumpManager, - mFlagMap, - mBarService + flagManager, + mockContext, + secureSettings, + systemProperties, + resources, + dumpManager, + flagMap, + commandRegistry, + barService ) - verify(mFlagManager).onSettingsChangedAction = any() - mBroadcastReceiver = withArgCaptor { - verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable(), + verify(flagManager).onSettingsChangedAction = any() + broadcastReceiver = withArgCaptor { + verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(), any()) } - mClearCacheAction = withArgCaptor { - verify(mFlagManager).clearCacheAction = capture() + clearCacheAction = withArgCaptor { + verify(flagManager).clearCacheAction = capture() } - whenever(mFlagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" } + whenever(flagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" } } @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) + whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false) assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(2, true))).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(3, false))).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(4, true))).isFalse() @@ -109,7 +118,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testTeamFoodFlag_False() { - whenever(mFlagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false) + whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false) assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -120,7 +129,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testTeamFoodFlag_True() { - whenever(mFlagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true) assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -131,11 +140,11 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testTeamFoodFlag_Overridden() { - whenever(mFlagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any())) + whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any())) .thenReturn(true) - whenever(mFlagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any())) + whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any())) .thenReturn(false) - whenever(mFlagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true) assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse() @@ -146,14 +155,14 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testReadResourceBooleanFlag() { - whenever(mResources.getBoolean(1001)).thenReturn(false) - whenever(mResources.getBoolean(1002)).thenReturn(true) - whenever(mResources.getBoolean(1003)).thenReturn(false) - whenever(mResources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() } - whenever(mResources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() } + whenever(resources.getBoolean(1001)).thenReturn(false) + whenever(resources.getBoolean(1002)).thenReturn(true) + whenever(resources.getBoolean(1003)).thenReturn(false) + whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() } + whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() } - whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) - whenever(mFlagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false) + whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) + whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false) assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(1, 1001))).isFalse() assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, 1002))).isTrue() @@ -171,7 +180,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testReadSysPropBooleanFlag() { - whenever(mSystemProperties.getBoolean(anyString(), anyBoolean())).thenAnswer { + whenever(systemProperties.getBoolean(anyString(), anyBoolean())).thenAnswer { if ("b".equals(it.getArgument<String?>(0))) { return@thenAnswer true } @@ -187,8 +196,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testReadStringFlag() { - whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo") - whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar") + whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo") + whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar") assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "biz"))).isEqualTo("biz") assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "baz"))).isEqualTo("baz") assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "buz"))).isEqualTo("foo") @@ -197,16 +206,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testReadResourceStringFlag() { - whenever(mResources.getString(1001)).thenReturn("") - whenever(mResources.getString(1002)).thenReturn("resource2") - whenever(mResources.getString(1003)).thenReturn("resource3") - whenever(mResources.getString(1004)).thenReturn(null) - whenever(mResources.getString(1005)).thenAnswer { throw NameNotFoundException() } - whenever(mResources.getString(1006)).thenAnswer { throw NameNotFoundException() } + whenever(resources.getString(1001)).thenReturn("") + whenever(resources.getString(1002)).thenReturn("resource2") + whenever(resources.getString(1003)).thenReturn("resource3") + whenever(resources.getString(1004)).thenReturn(null) + whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() } + whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() } - whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3") - whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4") - whenever(mFlagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6") + whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3") + whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4") + whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6") assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(1, 1001))).isEqualTo("") assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(2, 1002))).isEqualTo("resource2") @@ -232,16 +241,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() { addFlag(StringFlag(3, "flag3")) addFlag(ResourceStringFlag(4, 1004)) - mBroadcastReceiver.onReceive(mMockContext, null) - mBroadcastReceiver.onReceive(mMockContext, Intent()) - mBroadcastReceiver.onReceive(mMockContext, Intent("invalid action")) - mBroadcastReceiver.onReceive(mMockContext, Intent(FlagManager.ACTION_SET_FLAG)) + broadcastReceiver.onReceive(mockContext, null) + broadcastReceiver.onReceive(mockContext, Intent()) + broadcastReceiver.onReceive(mockContext, Intent("invalid action")) + broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG)) setByBroadcast(0, false) // unknown id does nothing setByBroadcast(1, "string") // wrong type does nothing setByBroadcast(2, 123) // wrong type does nothing setByBroadcast(3, false) // wrong type does nothing setByBroadcast(4, 123) // wrong type does nothing - verifyNoMoreInteractions(mFlagManager, mSecureSettings) + verifyNoMoreInteractions(flagManager, secureSettings) } @Test @@ -249,15 +258,15 @@ class FeatureFlagsDebugTest : SysuiTestCase() { addFlag(BooleanFlag(1, false)) // trying to erase an id not in the map does noting - mBroadcastReceiver.onReceive( - mMockContext, + broadcastReceiver.onReceive( + mockContext, Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0) ) - verifyNoMoreInteractions(mFlagManager, mSecureSettings) + verifyNoMoreInteractions(flagManager, secureSettings) // valid id with no value puts empty string in the setting - mBroadcastReceiver.onReceive( - mMockContext, + broadcastReceiver.onReceive( + mockContext, Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1) ) verifyPutData(1, "", numReads = 0) @@ -298,48 +307,102 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun testSetFlagClearsCache() { val flag1 = addFlag(StringFlag(1, "flag1")) - whenever(mFlagManager.readFlagValue<String>(eq(1), any())).thenReturn("original") + whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original") // gets the flag & cache it assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") - verify(mFlagManager).readFlagValue(eq(1), eq(StringFlagSerializer)) + verify(flagManager).readFlagValue(eq(1), eq(StringFlagSerializer)) // hit the cache assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") - verifyNoMoreInteractions(mFlagManager) + verifyNoMoreInteractions(flagManager) // set the flag setByBroadcast(1, "new") verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2) - whenever(mFlagManager.readFlagValue<String>(eq(1), any())).thenReturn("new") + whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("new") assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new") - verify(mFlagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer)) + verify(flagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer)) + } + + @Test + fun testRegisterCommand() { + verify(commandRegistry).registerCommand(anyString(), any()) + } + + @Test + fun testNoOpCommand() { + val cmd = captureCommand() + + cmd.execute(pw, ArrayList()) + verify(pw, atLeastOnce()).println() + verify(flagManager).readFlagValue<Boolean>(eq(1), any()) + verifyZeroInteractions(secureSettings) + } + + @Test + fun testReadFlagCommand() { + addFlag(BooleanFlag(1, false)) + val cmd = captureCommand() + cmd.execute(pw, listOf("1")) + verify(flagManager).readFlagValue<Boolean>(eq(1), any()) + } + + @Test + fun testSetFlagCommand() { + addFlag(BooleanFlag(1, false)) + val cmd = captureCommand() + cmd.execute(pw, listOf("1", "on")) + verifyPutData(1, "{\"type\":\"boolean\",\"value\":true}") + } + + @Test + fun testToggleFlagCommand() { + addFlag(BooleanFlag(1, true)) + val cmd = captureCommand() + cmd.execute(pw, listOf("1", "toggle")) + verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}", 2) + } + + @Test + fun testEraseFlagCommand() { + addFlag(BooleanFlag(1, true)) + val cmd = captureCommand() + cmd.execute(pw, listOf("1", "erase")) + verify(secureSettings).putStringForUser(eq("key-1"), eq(""), anyInt()) } private fun verifyPutData(id: Int, data: String, numReads: Int = 1) { - inOrder(mFlagManager, mSecureSettings).apply { - verify(mFlagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>()) - verify(mFlagManager).idToSettingsKey(eq(id)) - verify(mSecureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt()) - verify(mFlagManager).dispatchListenersAndMaybeRestart(eq(id), any()) + inOrder(flagManager, secureSettings).apply { + verify(flagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>()) + verify(flagManager).idToSettingsKey(eq(id)) + verify(secureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt()) + verify(flagManager).dispatchListenersAndMaybeRestart(eq(id), any()) }.verifyNoMoreInteractions() - verifyNoMoreInteractions(mFlagManager, mSecureSettings) + verifyNoMoreInteractions(flagManager, secureSettings) } private fun setByBroadcast(id: Int, value: Serializable?) { val intent = Intent(FlagManager.ACTION_SET_FLAG) intent.putExtra(FlagManager.EXTRA_ID, id) intent.putExtra(FlagManager.EXTRA_VALUE, value) - mBroadcastReceiver.onReceive(mMockContext, intent) + broadcastReceiver.onReceive(mockContext, intent) } private fun <F : Flag<*>> addFlag(flag: F): F { - val old = mFlagMap.put(flag.id, flag) + val old = flagMap.put(flag.id, flag) check(old == null) { "Flag ${flag.id} already registered" } return flag } + private fun captureCommand(): Command { + val captor = argumentCaptor<Function0<Command>>() + verify(commandRegistry).registerCommand(anyString(), capture(captor)) + + return captor.value.invoke() + } + @Test fun testDump() { val flag1 = BooleanFlag(1, true) @@ -350,10 +413,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() { val flag6 = ResourceStringFlag(6, 1006) val flag7 = ResourceStringFlag(7, 1007) - whenever(mResources.getBoolean(1002)).thenReturn(true) - whenever(mResources.getString(1006)).thenReturn("resource1006") - whenever(mResources.getString(1007)).thenReturn("resource1007") - whenever(mFlagManager.readFlagValue(eq(7), eq(StringFlagSerializer))) + whenever(resources.getBoolean(1002)).thenReturn(true) + whenever(resources.getString(1006)).thenReturn("resource1006") + whenever(resources.getString(1007)).thenReturn("resource1007") + whenever(flagManager.readFlagValue(eq(7), eq(StringFlagSerializer))) .thenReturn("override7") // WHEN the flags have been accessed |