Use less SysUI specific code in Flags.

A step along they way to sharing the flagging code as a library.

Bug: 249121873
Test: atest SystemUITests
Change-Id: Id48d33a6ce0d058c4b2d3c282865297841753bb7
This commit is contained in:
Dave Mankoff
2022-09-27 20:44:39 +00:00
parent 8138992013
commit f1a5f74f40
13 changed files with 466 additions and 268 deletions

View File

@@ -18,6 +18,7 @@ package com.android.systemui.flags
import android.content.Context
import android.os.Handler
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
@@ -27,6 +28,7 @@ import dagger.Provides
import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -46,5 +48,15 @@ abstract class FlagsModule {
@Provides
@Named(ALL_FLAGS)
fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
@JvmStatic
@Provides
fun providesRestarter(barService: IStatusBarService): Restarter {
return object: Restarter {
override fun restart() {
barService.restart()
}
}
}
}
}

View File

@@ -16,11 +16,29 @@
package com.android.systemui.flags
import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
import dagger.Provides
@Module(includes = [ServerFlagReaderModule::class])
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
ServerFlagReaderModule::class
])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
@Module
companion object {
@JvmStatic
@Provides
fun providesRestarter(barService: IStatusBarService): Restarter {
return object: Restarter {
override fun restart() {
barService.restart()
}
}
}
}
}

View File

@@ -16,12 +16,14 @@
package com.android.systemui.flags
import android.util.Dumpable
/**
* Class to manage simple DeviceConfig-based feature flags.
*
* See [Flags] for instructions on defining new flags.
*/
interface FeatureFlags : FlagListenable {
interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: UnreleasedFlag): Boolean

View File

@@ -30,29 +30,21 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
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.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
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;
@@ -75,10 +67,9 @@ import javax.inject.Named;
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
private static final String TAG = "SysUIFlags";
public class FeatureFlagsDebug implements FeatureFlags {
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;
@@ -89,7 +80,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final IStatusBarService mBarService;
private final Restarter mRestarter;
@Inject
public FeatureFlagsDebug(
@@ -98,12 +89,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
DumpManager dumpManager,
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
CommandRegistry commandRegistry,
IStatusBarService barService) {
Restarter barService) {
mFlagManager = flagManager;
mSecureSettings = secureSettings;
mResources = resources;
@@ -111,7 +100,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
mBarService = barService;
mRestarter = barService;
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
@@ -120,8 +109,6 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
flagManager.setClearCacheAction(this::removeFromCache);
context.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
dumpManager.registerDumpable(TAG, this);
commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new);
}
@Override
@@ -266,7 +253,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
}
private <T> void eraseFlag(Flag<T> flag) {
<T> void eraseFlag(Flag<T> flag) {
if (flag instanceof SysPropFlag) {
mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
@@ -319,13 +306,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
return;
}
Log.i(TAG, "Restarting Android");
try {
mBarService.restart();
} catch (RemoteException e) {
}
mRestarter.restart();
}
private void setBooleanFlagInternal(Flag<?> flag, boolean value) {
void setBooleanFlagInternal(Flag<?> flag, boolean value) {
if (flag instanceof BooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
@@ -342,7 +326,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
}
}
private void setStringFlagInternal(Flag<?> flag, String value) {
void setStringFlagInternal(Flag<?> flag, String value) {
if (flag instanceof StringFlag) {
setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceStringFlag) {
@@ -476,154 +460,4 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
+ ": [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 ReleasedFlag) {
return isEnabled((ReleasedFlag) flag);
} else if (flag instanceof UnreleasedFlag) {
return isEnabled((UnreleasedFlag) 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

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.flags
import android.content.Context
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Inject
class FeatureFlagsDebugStartable
@Inject
constructor(
@Application context: Context,
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
featureFlags: FeatureFlags
) : CoreStartable(context) {
init {
dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
featureFlags.dump(pw, args)
}
}
override fun start() {
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
}
}
@Module
abstract class FeatureFlagsDebugStartableModule {
@Binds
@IntoMap
@ClassKey(FeatureFlagsDebugStartable::class)
abstract fun bind(impl: FeatureFlagsDebugStartable): CoreStartable
}

View File

@@ -24,10 +24,8 @@ import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.DeviceConfigProxy;
import org.jetbrains.annotations.NotNull;
@@ -44,27 +42,26 @@ import javax.inject.Inject;
* how to set flags.
*/
@SysUISingleton
public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
public class FeatureFlagsRelease implements FeatureFlags {
static final String TAG = "SysUIFlags";
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
private boolean mInited;
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
DumpManager dumpManager) {
ServerFlagReader serverFlagReader) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
dumpManager.registerDumpable("SysUIFlags", this);
}
@Override

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.flags
import android.content.Context
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Inject
class FeatureFlagsReleaseStartable
@Inject
constructor(@Application context: Context, dumpManager: DumpManager, featureFlags: FeatureFlags) :
CoreStartable(context) {
init {
dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
featureFlags.dump(pw, args)
}
}
override fun start() {
// no-op
}
}
@Module
abstract class FeatureFlagsReleaseStartableModule {
@Binds
@IntoMap
@ClassKey(FeatureFlagsReleaseStartable::class)
abstract fun bind(impl: FeatureFlagsReleaseStartable): CoreStartable
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.flags;
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.commandline.Command;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
/**
* A {@link Command} used to flip flags in SystemUI.
*/
public class FlagCommand implements Command {
public static final String FLAG_COMMAND = "flag";
private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
private final FeatureFlagsDebug mFeatureFlags;
private final Map<Integer, Flag<?>> mAllFlags;
@Inject
FlagCommand(
FeatureFlagsDebug featureFlags,
@Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
mAllFlags = allFlags;
}
@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)) {
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);
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.
mFeatureFlags.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 ReleasedFlag) {
return mFeatureFlags.isEnabled((ReleasedFlag) flag);
} else if (flag instanceof UnreleasedFlag) {
return mFeatureFlags.isEnabled((UnreleasedFlag) flag);
} else if (flag instanceof ResourceBooleanFlag) {
return mFeatureFlags.isEnabled((ResourceBooleanFlag) flag);
} else if (flag instanceof SysPropFlag) {
return mFeatureFlags.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

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.flags
interface Restarter {
fun restart()
}

View File

@@ -21,15 +21,10 @@ import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
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.DeviceConfigProxyFake
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
@@ -46,18 +41,16 @@ 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.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
/**
* NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
* overriding, and should never return any value other than the one provided as the default.
* NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
* the default.
*/
@SmallTest
class FeatureFlagsDebugTest : SysuiTestCase() {
@@ -68,10 +61,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@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
@Mock private lateinit var restarter: Restarter
private val flagMap = mutableMapOf<Int, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<Int>
@@ -92,12 +83,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
secureSettings,
systemProperties,
resources,
dumpManager,
deviceConfig,
serverFlagReader,
flagMap,
commandRegistry,
barService
restarter
)
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
@@ -365,53 +354,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
}
@Test
fun statusBarCommand_IsRegistered() {
verify(commandRegistry).registerCommand(anyString(), any())
}
@Test
fun noOpCommand() {
val cmd = captureCommand()
cmd.execute(pw, ArrayList())
verify(pw, atLeastOnce()).println()
verify(flagManager).readFlagValue<Boolean>(eq(1), any())
verifyZeroInteractions(secureSettings)
}
@Test
fun readFlagCommand() {
addFlag(UnreleasedFlag(1))
val cmd = captureCommand()
cmd.execute(pw, listOf("1"))
verify(flagManager).readFlagValue<Boolean>(eq(1), any())
}
@Test
fun setFlagCommand() {
addFlag(UnreleasedFlag(1))
val cmd = captureCommand()
cmd.execute(pw, listOf("1", "on"))
verifyPutData(1, "{\"type\":\"boolean\",\"value\":true}")
}
@Test
fun toggleFlagCommand() {
addFlag(ReleasedFlag(1))
val cmd = captureCommand()
cmd.execute(pw, listOf("1", "toggle"))
verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}", 2)
}
@Test
fun eraseFlagCommand() {
addFlag(ReleasedFlag(1))
val cmd = captureCommand()
cmd.execute(pw, listOf("1", "erase"))
verify(secureSettings).putStringForUser(eq("key-1"), eq(""), anyInt())
}
@Test
fun dumpFormat() {
val flag1 = ReleasedFlag(1)
@@ -471,13 +413,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
return flag
}
private fun captureCommand(): Command {
val captor = argumentCaptor<Function0<Command>>()
verify(commandRegistry).registerCommand(anyString(), capture(captor))
return captor.value.invoke()
}
private fun dumpToString(): String {
val sw = StringWriter()
val pw = PrintWriter(sw)

View File

@@ -19,17 +19,12 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -43,7 +38,6 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@Mock private lateinit var mDumpManager: DumpManager
private val serverFlagReader = ServerFlagReaderFake()
private val deviceConfig = DeviceConfigProxyFake()
@@ -55,15 +49,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
mResources,
mSystemProperties,
deviceConfig,
serverFlagReader,
mDumpManager)
}
@After
fun onFinished() {
// The dump manager should be registered with even for the release version, but that's it.
verify(mDumpManager).registerDumpable(any(), any())
verifyNoMoreInteractions(mDumpManager)
serverFlagReader)
}
@Test

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.flags
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import java.io.PrintWriter
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
class FlagCommandTest : SysuiTestCase() {
@Mock private lateinit var featureFlags: FeatureFlagsDebug
@Mock private lateinit var pw: PrintWriter
private val flagMap = mutableMapOf<Int, Flag<*>>()
private val flagA = UnreleasedFlag(500)
private val flagB = ReleasedFlag(501)
private lateinit var cmd: FlagCommand
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
flagMap.put(flagA.id, flagA)
flagMap.put(flagB.id, flagB)
cmd = FlagCommand(featureFlags, flagMap)
}
@Test
fun noOpCommand() {
cmd.execute(pw, ArrayList())
Mockito.verify(pw, Mockito.atLeastOnce()).println()
Mockito.verify(featureFlags).isEnabled(flagA)
Mockito.verify(featureFlags).isEnabled(flagB)
}
@Test
fun readFlagCommand() {
cmd.execute(pw, listOf(flagA.id.toString()))
Mockito.verify(featureFlags).isEnabled(flagA)
}
@Test
fun setFlagCommand() {
cmd.execute(pw, listOf(flagB.id.toString(), "on"))
Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
}
@Test
fun toggleFlagCommand() {
cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
}
@Test
fun eraseFlagCommand() {
cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
Mockito.verify(featureFlags).eraseFlag(flagA)
}
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.flags
import java.io.PrintWriter
class FakeFeatureFlags : FeatureFlags {
private val booleanFlags = mutableMapOf<Int, Boolean>()
private val stringFlags = mutableMapOf<Int, String>()
@@ -106,6 +108,10 @@ class FakeFeatureFlags : FeatureFlags {
}
}
override fun dump(writer: PrintWriter, args: Array<out String>?) {
// no-op
}
private fun flagName(flagId: Int): String {
return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
}