Merge "Use less SysUI specific code in Flags." into tm-qpr-dev

This commit is contained in:
Dave Mankoff
2022-10-11 16:41:28 +00:00
committed by Android (Google) Code Review
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.content.Context
import android.os.Handler import android.os.Handler
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule import com.android.systemui.util.settings.SettingsUtilModule
@@ -27,6 +28,7 @@ import dagger.Provides
import javax.inject.Named import javax.inject.Named
@Module(includes = [ @Module(includes = [
FeatureFlagsDebugStartableModule::class,
ServerFlagReaderModule::class, ServerFlagReaderModule::class,
SettingsUtilModule::class, SettingsUtilModule::class,
]) ])
@@ -46,5 +48,15 @@ abstract class FlagsModule {
@Provides @Provides
@Named(ALL_FLAGS) @Named(ALL_FLAGS)
fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags() 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 package com.android.systemui.flags
import com.android.internal.statusbar.IStatusBarService
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides
@Module(includes = [ServerFlagReaderModule::class]) @Module(includes = [
FeatureFlagsReleaseStartableModule::class,
ServerFlagReaderModule::class
])
abstract class FlagsModule { abstract class FlagsModule {
@Binds @Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags 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 package com.android.systemui.flags
import android.util.Dumpable
/** /**
* Class to manage simple DeviceConfig-based feature flags. * Class to manage simple DeviceConfig-based feature flags.
* *
* See [Flags] for instructions on defining new flags. * See [Flags] for instructions on defining new flags.
*/ */
interface FeatureFlags : FlagListenable { interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a boolean value for the given flag. */ /** Returns a boolean value for the given flag. */
fun isEnabled(flag: UnreleasedFlag): Boolean fun isEnabled(flag: UnreleasedFlag): Boolean

View File

@@ -30,29 +30,21 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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.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.statusbar.commandline.Command;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
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;
@@ -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. * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/ */
@SysUISingleton @SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags, Dumpable { public class FeatureFlagsDebug implements FeatureFlags {
private static final String TAG = "SysUIFlags"; 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;
@@ -89,7 +80,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
private final Map<Integer, Flag<?>> mAllFlags; private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>(); private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
private final Map<Integer, String> mStringFlagCache = new TreeMap<>(); private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final IStatusBarService mBarService; private final Restarter mRestarter;
@Inject @Inject
public FeatureFlagsDebug( public FeatureFlagsDebug(
@@ -98,12 +89,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
SecureSettings secureSettings, SecureSettings secureSettings,
SystemPropertiesHelper systemProperties, SystemPropertiesHelper systemProperties,
@Main Resources resources, @Main Resources resources,
DumpManager dumpManager,
DeviceConfigProxy deviceConfigProxy, DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader, ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags, @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
CommandRegistry commandRegistry, Restarter barService) {
IStatusBarService barService) {
mFlagManager = flagManager; mFlagManager = flagManager;
mSecureSettings = secureSettings; mSecureSettings = secureSettings;
mResources = resources; mResources = resources;
@@ -111,7 +100,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
mDeviceConfigProxy = deviceConfigProxy; mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader; mServerFlagReader = serverFlagReader;
mAllFlags = allFlags; mAllFlags = allFlags;
mBarService = barService; mRestarter = barService;
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG); filter.addAction(ACTION_SET_FLAG);
@@ -120,8 +109,6 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
flagManager.setClearCacheAction(this::removeFromCache); flagManager.setClearCacheAction(this::removeFromCache);
context.registerReceiver(mReceiver, filter, null, null, context.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED); Context.RECEIVER_EXPORTED_UNAUDITED);
dumpManager.registerDumpable(TAG, this);
commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new);
} }
@Override @Override
@@ -266,7 +253,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI); mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
} }
private <T> void eraseFlag(Flag<T> flag) { <T> void eraseFlag(Flag<T> flag) {
if (flag instanceof SysPropFlag) { if (flag instanceof SysPropFlag) {
mSystemProperties.erase(((SysPropFlag<T>) flag).getName()); mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid); dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
@@ -319,13 +306,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
return; return;
} }
Log.i(TAG, "Restarting Android"); Log.i(TAG, "Restarting Android");
try { mRestarter.restart();
mBarService.restart();
} catch (RemoteException e) {
}
} }
private void setBooleanFlagInternal(Flag<?> flag, boolean value) { void setBooleanFlagInternal(Flag<?> flag, boolean value) {
if (flag instanceof BooleanFlag) { if (flag instanceof BooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE); setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) { } 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) { if (flag instanceof StringFlag) {
setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE); setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceStringFlag) { } else if (flag instanceof ResourceStringFlag) {
@@ -476,154 +460,4 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
+ ": [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 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 androidx.annotation.NonNull;
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.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxy;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -44,27 +42,26 @@ import javax.inject.Inject;
* how to set flags. * how to set flags.
*/ */
@SysUISingleton @SysUISingleton
public class FeatureFlagsRelease implements FeatureFlags, Dumpable { public class FeatureFlagsRelease implements FeatureFlags {
static final String TAG = "SysUIFlags";
private final Resources mResources; private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties; private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy; private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader; private final ServerFlagReader mServerFlagReader;
SparseBooleanArray mBooleanCache = new SparseBooleanArray(); SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>(); SparseArray<String> mStringCache = new SparseArray<>();
private boolean mInited;
@Inject @Inject
public FeatureFlagsRelease( public FeatureFlagsRelease(
@Main Resources resources, @Main Resources resources,
SystemPropertiesHelper systemProperties, SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy, DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader, ServerFlagReader serverFlagReader) {
DumpManager dumpManager) {
mResources = resources; mResources = resources;
mSystemProperties = systemProperties; mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy; mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader; mServerFlagReader = serverFlagReader;
dumpManager.registerDumpable("SysUIFlags", this);
} }
@Override @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.pm.PackageManager.NameNotFoundException
import android.content.res.Resources import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest import android.test.suitebuilder.annotation.SmallTest
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.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.DeviceConfigProxyFake
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
@@ -46,18 +41,16 @@ 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.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
/** /**
* NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow * NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
* overriding, and should never return any value other than the one provided as the default. * the default.
*/ */
@SmallTest @SmallTest
class FeatureFlagsDebugTest : SysuiTestCase() { class FeatureFlagsDebugTest : SysuiTestCase() {
@@ -68,10 +61,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Mock private lateinit var secureSettings: SecureSettings @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var systemProperties: SystemPropertiesHelper @Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var resources: Resources @Mock private lateinit var resources: Resources
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var commandRegistry: CommandRegistry @Mock private lateinit var commandRegistry: CommandRegistry
@Mock private lateinit var barService: IStatusBarService @Mock private lateinit var restarter: Restarter
@Mock private lateinit var pw: PrintWriter
private val flagMap = mutableMapOf<Int, Flag<*>>() private val flagMap = mutableMapOf<Int, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<Int> private lateinit var clearCacheAction: Consumer<Int>
@@ -92,12 +83,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
secureSettings, secureSettings,
systemProperties, systemProperties,
resources, resources,
dumpManager,
deviceConfig, deviceConfig,
serverFlagReader, serverFlagReader,
flagMap, flagMap,
commandRegistry, restarter
barService
) )
verify(flagManager).onSettingsChangedAction = any() verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor { broadcastReceiver = withArgCaptor {
@@ -365,53 +354,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue() 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 @Test
fun dumpFormat() { fun dumpFormat() {
val flag1 = ReleasedFlag(1) val flag1 = ReleasedFlag(1)
@@ -471,13 +413,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
return flag 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 { private fun dumpToString(): String {
val sw = StringWriter() val sw = StringWriter()
val pw = PrintWriter(sw) val pw = PrintWriter(sw)

View File

@@ -19,17 +19,12 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Assert.assertThrows import org.junit.Assert.assertThrows
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@@ -43,7 +38,6 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
@Mock private lateinit var mResources: Resources @Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@Mock private lateinit var mDumpManager: DumpManager
private val serverFlagReader = ServerFlagReaderFake() private val serverFlagReader = ServerFlagReaderFake()
private val deviceConfig = DeviceConfigProxyFake() private val deviceConfig = DeviceConfigProxyFake()
@@ -55,15 +49,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
mResources, mResources,
mSystemProperties, mSystemProperties,
deviceConfig, deviceConfig,
serverFlagReader, 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)
} }
@Test @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 package com.android.systemui.flags
import java.io.PrintWriter
class FakeFeatureFlags : FeatureFlags { class FakeFeatureFlags : FeatureFlags {
private val booleanFlags = mutableMapOf<Int, Boolean>() private val booleanFlags = mutableMapOf<Int, Boolean>()
private val stringFlags = mutableMapOf<Int, String>() 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 { private fun flagName(flagId: Int): String {
return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)" return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
} }