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.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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
|
||||||
|
|||||||
@@ -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.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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
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)"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user