Merge "Add adb shell command to set flags." into tm-dev am: 9a8e56e94e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17663404

Change-Id: I5d44723ba20a599d2b210d72650487caef655328
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Dave Mankoff
2022-04-14 18:08:40 +00:00
committed by Automerger Merge Worker
3 changed files with 365 additions and 108 deletions

View File

@@ -42,10 +42,14 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager; 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 com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.TreeMap; import java.util.TreeMap;
@@ -59,6 +63,10 @@ import javax.inject.Named;
* *
* Flags can be set (or unset) via the following adb command: * 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>] * 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. * 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 { public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
private static final String TAG = "SysUIFlags"; private static final String TAG = "SysUIFlags";
static final String ALL_FLAGS = "all_flags"; static final String ALL_FLAGS = "all_flags";
private static final String FLAG_COMMAND = "flag";
private final FlagManager mFlagManager; private final FlagManager mFlagManager;
private final SecureSettings mSecureSettings; private final SecureSettings mSecureSettings;
@@ -86,12 +95,15 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
@Main Resources resources, @Main Resources resources,
DumpManager dumpManager, DumpManager dumpManager,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags, @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
CommandRegistry commandRegistry,
IStatusBarService barService) { IStatusBarService barService) {
mFlagManager = flagManager; mFlagManager = flagManager;
mSecureSettings = secureSettings; mSecureSettings = secureSettings;
mResources = resources; mResources = resources;
mSystemProperties = systemProperties; mSystemProperties = systemProperties;
mAllFlags = allFlags; mAllFlags = allFlags;
mBarService = barService;
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG); filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS); filter.addAction(ACTION_GET_FLAGS);
@@ -100,7 +112,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
context.registerReceiver(mReceiver, filter, null, null, context.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED); Context.RECEIVER_EXPORTED_UNAUDITED);
dumpManager.registerDumpable(TAG, this); dumpManager.registerDumpable(TAG, this);
mBarService = barService; commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new);
} }
@Override @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() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@@ -327,24 +364,19 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
} }
Object value = extras.get(EXTRA_VALUE); Object value = extras.get(EXTRA_VALUE);
if (flag instanceof BooleanFlag && value instanceof Boolean) {
setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE); try {
} else if (flag instanceof ResourceBooleanFlag && value instanceof Boolean) { if (value instanceof Boolean) {
setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE); setBooleanFlagInternal(flag, (Boolean) value);
} else if (flag instanceof SysPropBooleanFlag && value instanceof Boolean) { } else if (value instanceof String) {
// Store SysProp flags in SystemProperties where they can read by outside parties. setStringFlagInternal(flag, (String) value);
mSystemProperties.setBoolean( } else {
((SysPropBooleanFlag) flag).getName(), (Boolean) value); throw new IllegalArgumentException("Unknown value type");
dispatchListenersAndMaybeRestart(flag.getId(), }
FeatureFlagsDebug.this::restartAndroid); } catch (IllegalArgumentException e) {
} 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 {
Log.w(TAG, Log.w(TAG,
"Unable to set " + id + " of type " + flag.getClass() + " to value of type " "Unable to set " + flag.getId() + " of type " + flag.getClass()
+ (value == null ? null : value.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 mStringFlagCache.forEach((key, value) -> pw.println(" sysui_flag_" + key
+ ": [length=" + value.length() + "] \"" + value + "\"")); + ": [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)));
}
}
}
} }

View File

@@ -20,7 +20,9 @@ import com.android.internal.annotations.Keep;
import com.android.systemui.R; import com.android.systemui.R;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@@ -182,19 +184,16 @@ public class Flags {
if (sFlagMap != null) { if (sFlagMap != null) {
return sFlagMap; return sFlagMap;
} }
Map<Integer, Flag<?>> flags = new HashMap<>(); Map<Integer, Flag<?>> flags = new HashMap<>();
List<Field> flagFields = getFlagFields();
Field[] fields = Flags.class.getFields(); for (Field field : flagFields) {
try {
for (Field field : fields) { Flag<?> flag = (Flag<?>) field.get(null);
Class<?> t = field.getType(); flags.put(flag.getId(), flag);
if (Flag.class.isAssignableFrom(t)) { } catch (IllegalAccessException e) {
try { // no-op
Flag<?> flag = (Flag<?>) field.get(null);
flags.put(flag.getId(), flag);
} catch (IllegalAccessException e) {
// no-op
}
} }
} }
@@ -202,6 +201,20 @@ public class Flags {
return sFlagMap; 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)) {
result.add(field);
}
}
return result;
}
// | . . . . . . . . . . . . . . . . . . . | // | . . . . . . . . . . . . . . . . . . . |
// | | // | |
// \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/

View File

@@ -24,7 +24,11 @@ import android.test.suitebuilder.annotation.SmallTest
import com.android.internal.statusbar.IStatusBarService import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager 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.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.eq
import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.mockito.withArgCaptor
@@ -37,10 +41,12 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString import org.mockito.Mockito.anyString
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.inOrder import org.mockito.Mockito.inOrder
import org.mockito.Mockito.times import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.io.PrintWriter import java.io.PrintWriter
import java.io.Serializable import java.io.Serializable
@@ -56,16 +62,18 @@ import org.mockito.Mockito.`when` as whenever
class FeatureFlagsDebugTest : SysuiTestCase() { class FeatureFlagsDebugTest : SysuiTestCase() {
private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
@Mock private lateinit var mFlagManager: FlagManager @Mock private lateinit var flagManager: FlagManager
@Mock private lateinit var mMockContext: Context @Mock private lateinit var mockContext: Context
@Mock private lateinit var mSecureSettings: SecureSettings @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper @Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var mResources: Resources @Mock private lateinit var resources: Resources
@Mock private lateinit var mDumpManager: DumpManager @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var mBarService: IStatusBarService @Mock private lateinit var commandRegistry: CommandRegistry
private val mFlagMap = mutableMapOf<Int, Flag<*>>() @Mock private lateinit var barService: IStatusBarService
private lateinit var mBroadcastReceiver: BroadcastReceiver @Mock private lateinit var pw: PrintWriter
private lateinit var mClearCacheAction: Consumer<Int> 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 teamfoodableFlagA = BooleanFlag(500, false, true)
private val teamfoodableFlagB = BooleanFlag(501, true, true) private val teamfoodableFlagB = BooleanFlag(501, true, true)
@@ -73,34 +81,35 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Before @Before
fun setup() { fun setup() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
mFlagMap.put(teamfoodableFlagA.id, teamfoodableFlagA) flagMap.put(teamfoodableFlagA.id, teamfoodableFlagA)
mFlagMap.put(teamfoodableFlagB.id, teamfoodableFlagB) flagMap.put(teamfoodableFlagB.id, teamfoodableFlagB)
mFeatureFlagsDebug = FeatureFlagsDebug( mFeatureFlagsDebug = FeatureFlagsDebug(
mFlagManager, flagManager,
mMockContext, mockContext,
mSecureSettings, secureSettings,
mSystemProperties, systemProperties,
mResources, resources,
mDumpManager, dumpManager,
mFlagMap, flagMap,
mBarService commandRegistry,
barService
) )
verify(mFlagManager).onSettingsChangedAction = any() verify(flagManager).onSettingsChangedAction = any()
mBroadcastReceiver = withArgCaptor { broadcastReceiver = withArgCaptor {
verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable(), verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
any()) any())
} }
mClearCacheAction = withArgCaptor { clearCacheAction = withArgCaptor {
verify(mFlagManager).clearCacheAction = capture() verify(flagManager).clearCacheAction = capture()
} }
whenever(mFlagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" } whenever(flagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
} }
@Test @Test
fun testReadBooleanFlag() { fun testReadBooleanFlag() {
// Remember that the TEAMFOOD flag is id#1 and has special behavior. // Remember that the TEAMFOOD flag is id#1 and has special behavior.
whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
whenever(mFlagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false) whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(2, true))).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(2, true))).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(3, false))).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(3, false))).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(4, true))).isFalse() assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(4, true))).isFalse()
@@ -109,7 +118,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testTeamFoodFlag_False() { 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(teamfoodableFlagA)).isFalse()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -120,7 +129,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testTeamFoodFlag_True() { 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(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -131,11 +140,11 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testTeamFoodFlag_Overridden() { fun testTeamFoodFlag_Overridden() {
whenever(mFlagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any())) whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
.thenReturn(true) .thenReturn(true)
whenever(mFlagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any())) whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
.thenReturn(false) .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(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse() assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
@@ -146,14 +155,14 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testReadResourceBooleanFlag() { fun testReadResourceBooleanFlag() {
whenever(mResources.getBoolean(1001)).thenReturn(false) whenever(resources.getBoolean(1001)).thenReturn(false)
whenever(mResources.getBoolean(1002)).thenReturn(true) whenever(resources.getBoolean(1002)).thenReturn(true)
whenever(mResources.getBoolean(1003)).thenReturn(false) whenever(resources.getBoolean(1003)).thenReturn(false)
whenever(mResources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() } whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
whenever(mResources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() } whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true) whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
whenever(mFlagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false) whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(1, 1001))).isFalse() assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(1, 1001))).isFalse()
assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, 1002))).isTrue() assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, 1002))).isTrue()
@@ -171,7 +180,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testReadSysPropBooleanFlag() { fun testReadSysPropBooleanFlag() {
whenever(mSystemProperties.getBoolean(anyString(), anyBoolean())).thenAnswer { whenever(systemProperties.getBoolean(anyString(), anyBoolean())).thenAnswer {
if ("b".equals(it.getArgument<String?>(0))) { if ("b".equals(it.getArgument<String?>(0))) {
return@thenAnswer true return@thenAnswer true
} }
@@ -187,8 +196,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testReadStringFlag() { fun testReadStringFlag() {
whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo") whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar") whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "biz"))).isEqualTo("biz") assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "biz"))).isEqualTo("biz")
assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "baz"))).isEqualTo("baz") assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "baz"))).isEqualTo("baz")
assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "buz"))).isEqualTo("foo") assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "buz"))).isEqualTo("foo")
@@ -197,16 +206,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testReadResourceStringFlag() { fun testReadResourceStringFlag() {
whenever(mResources.getString(1001)).thenReturn("") whenever(resources.getString(1001)).thenReturn("")
whenever(mResources.getString(1002)).thenReturn("resource2") whenever(resources.getString(1002)).thenReturn("resource2")
whenever(mResources.getString(1003)).thenReturn("resource3") whenever(resources.getString(1003)).thenReturn("resource3")
whenever(mResources.getString(1004)).thenReturn(null) whenever(resources.getString(1004)).thenReturn(null)
whenever(mResources.getString(1005)).thenAnswer { throw NameNotFoundException() } whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() }
whenever(mResources.getString(1006)).thenAnswer { throw NameNotFoundException() } whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() }
whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3") whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3")
whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4") whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
whenever(mFlagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6") whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(1, 1001))).isEqualTo("") assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(2, 1002))).isEqualTo("resource2") assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(2, 1002))).isEqualTo("resource2")
@@ -232,16 +241,16 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
addFlag(StringFlag(3, "flag3")) addFlag(StringFlag(3, "flag3"))
addFlag(ResourceStringFlag(4, 1004)) addFlag(ResourceStringFlag(4, 1004))
mBroadcastReceiver.onReceive(mMockContext, null) broadcastReceiver.onReceive(mockContext, null)
mBroadcastReceiver.onReceive(mMockContext, Intent()) broadcastReceiver.onReceive(mockContext, Intent())
mBroadcastReceiver.onReceive(mMockContext, Intent("invalid action")) broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
mBroadcastReceiver.onReceive(mMockContext, Intent(FlagManager.ACTION_SET_FLAG)) broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
setByBroadcast(0, false) // unknown id does nothing setByBroadcast(0, false) // unknown id does nothing
setByBroadcast(1, "string") // wrong type does nothing setByBroadcast(1, "string") // wrong type does nothing
setByBroadcast(2, 123) // wrong type does nothing setByBroadcast(2, 123) // wrong type does nothing
setByBroadcast(3, false) // wrong type does nothing setByBroadcast(3, false) // wrong type does nothing
setByBroadcast(4, 123) // wrong type does nothing setByBroadcast(4, 123) // wrong type does nothing
verifyNoMoreInteractions(mFlagManager, mSecureSettings) verifyNoMoreInteractions(flagManager, secureSettings)
} }
@Test @Test
@@ -249,15 +258,15 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
addFlag(BooleanFlag(1, false)) addFlag(BooleanFlag(1, false))
// trying to erase an id not in the map does noting // trying to erase an id not in the map does noting
mBroadcastReceiver.onReceive( broadcastReceiver.onReceive(
mMockContext, mockContext,
Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0) 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 // valid id with no value puts empty string in the setting
mBroadcastReceiver.onReceive( broadcastReceiver.onReceive(
mMockContext, mockContext,
Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1) Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1)
) )
verifyPutData(1, "", numReads = 0) verifyPutData(1, "", numReads = 0)
@@ -298,48 +307,102 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test @Test
fun testSetFlagClearsCache() { fun testSetFlagClearsCache() {
val flag1 = addFlag(StringFlag(1, "flag1")) 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 // gets the flag & cache it
assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
verify(mFlagManager).readFlagValue(eq(1), eq(StringFlagSerializer)) verify(flagManager).readFlagValue(eq(1), eq(StringFlagSerializer))
// hit the cache // hit the cache
assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original") assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
verifyNoMoreInteractions(mFlagManager) verifyNoMoreInteractions(flagManager)
// set the flag // set the flag
setByBroadcast(1, "new") setByBroadcast(1, "new")
verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2) 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") 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) { private fun verifyPutData(id: Int, data: String, numReads: Int = 1) {
inOrder(mFlagManager, mSecureSettings).apply { inOrder(flagManager, secureSettings).apply {
verify(mFlagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>()) verify(flagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
verify(mFlagManager).idToSettingsKey(eq(id)) verify(flagManager).idToSettingsKey(eq(id))
verify(mSecureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt()) verify(secureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt())
verify(mFlagManager).dispatchListenersAndMaybeRestart(eq(id), any()) verify(flagManager).dispatchListenersAndMaybeRestart(eq(id), any())
}.verifyNoMoreInteractions() }.verifyNoMoreInteractions()
verifyNoMoreInteractions(mFlagManager, mSecureSettings) verifyNoMoreInteractions(flagManager, secureSettings)
} }
private fun setByBroadcast(id: Int, value: Serializable?) { private fun setByBroadcast(id: Int, value: Serializable?) {
val intent = Intent(FlagManager.ACTION_SET_FLAG) val intent = Intent(FlagManager.ACTION_SET_FLAG)
intent.putExtra(FlagManager.EXTRA_ID, id) intent.putExtra(FlagManager.EXTRA_ID, id)
intent.putExtra(FlagManager.EXTRA_VALUE, value) intent.putExtra(FlagManager.EXTRA_VALUE, value)
mBroadcastReceiver.onReceive(mMockContext, intent) broadcastReceiver.onReceive(mockContext, intent)
} }
private fun <F : Flag<*>> addFlag(flag: F): F { 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" } check(old == null) { "Flag ${flag.id} already registered" }
return flag return flag
} }
private fun captureCommand(): Command {
val captor = argumentCaptor<Function0<Command>>()
verify(commandRegistry).registerCommand(anyString(), capture(captor))
return captor.value.invoke()
}
@Test @Test
fun testDump() { fun testDump() {
val flag1 = BooleanFlag(1, true) val flag1 = BooleanFlag(1, true)
@@ -350,10 +413,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
val flag6 = ResourceStringFlag(6, 1006) val flag6 = ResourceStringFlag(6, 1006)
val flag7 = ResourceStringFlag(7, 1007) val flag7 = ResourceStringFlag(7, 1007)
whenever(mResources.getBoolean(1002)).thenReturn(true) whenever(resources.getBoolean(1002)).thenReturn(true)
whenever(mResources.getString(1006)).thenReturn("resource1006") whenever(resources.getString(1006)).thenReturn("resource1006")
whenever(mResources.getString(1007)).thenReturn("resource1007") whenever(resources.getString(1007)).thenReturn("resource1007")
whenever(mFlagManager.readFlagValue(eq(7), eq(StringFlagSerializer))) whenever(flagManager.readFlagValue(eq(7), eq(StringFlagSerializer)))
.thenReturn("override7") .thenReturn("override7")
// WHEN the flags have been accessed // WHEN the flags have been accessed