Merge "Use less SysUI specific code in Flags." into tm-qpr-dev
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user