Add server flag support to flags. am: 96c315ea7b

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

Change-Id: I456a2b4fb66c3ffe5a804ac1dec8d6037c918578
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Dave Mankoff
2022-11-10 02:03:38 +00:00
committed by Automerger Merge Worker
9 changed files with 102 additions and 133 deletions

View File

@@ -22,9 +22,19 @@ import android.annotation.StringRes
import android.os.Parcel
import android.os.Parcelable
/**
* Base interface for flags that can change value on a running device.
* @property id unique id to help identify this flag. Must be unique. This will be removed soon.
* @property teamfood Set to true to include this flag as part of the teamfood flag. This will
* be removed soon.
* @property name Used for server-side flagging where appropriate. Also used for display. No spaces.
* @property namespace The server-side namespace that this flag lives under.
*/
interface Flag<T> {
val id: Int
val teamfood: Boolean
val name: String
val namespace: String
}
interface ParcelableFlag<T> : Flag<T>, Parcelable {
@@ -38,13 +48,10 @@ interface ResourceFlag<T> : Flag<T> {
}
interface DeviceConfigFlag<T> : Flag<T> {
val name: String
val namespace: String
val default: T
}
interface SysPropFlag<T> : Flag<T> {
val name: String
val default: T
}
@@ -57,6 +64,8 @@ interface SysPropFlag<T> : Flag<T> {
abstract class BooleanFlag constructor(
override val id: Int,
override val default: Boolean = false,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
) : ParcelableFlag<Boolean> {
@@ -91,20 +100,24 @@ abstract class BooleanFlag constructor(
*/
data class UnreleasedFlag constructor(
override val id: Int,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
) : BooleanFlag(id, false, teamfood, overridden)
) : BooleanFlag(id, false, name, namespace, teamfood, overridden)
/**
* A Flag that is is true by default.
* A Flag that is true by default.
*
* It can be changed or overridden in any build, meaning it can be turned off if needed.
*/
data class ReleasedFlag constructor(
override val id: Int,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
) : BooleanFlag(id, true, teamfood, overridden)
) : BooleanFlag(id, true, name, namespace, teamfood, overridden)
/**
* A Flag that reads its default values from a resource overlay instead of code.
@@ -114,6 +127,8 @@ data class ReleasedFlag constructor(
data class ResourceBooleanFlag constructor(
override val id: Int,
@BoolRes override val resourceId: Int,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false
) : ResourceFlag<Boolean>
@@ -142,7 +157,8 @@ data class DeviceConfigBooleanFlag constructor(
data class SysPropBooleanFlag constructor(
override val id: Int,
override val name: String,
override val default: Boolean = false
override val default: Boolean = false,
override val namespace: String = ""
) : SysPropFlag<Boolean> {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
override val teamfood: Boolean = false
@@ -151,6 +167,8 @@ data class SysPropBooleanFlag constructor(
data class StringFlag constructor(
override val id: Int,
override val default: String = "",
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
) : ParcelableFlag<String> {
@@ -176,12 +194,16 @@ data class StringFlag constructor(
data class ResourceStringFlag constructor(
override val id: Int,
@StringRes override val resourceId: Int,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false
) : ResourceFlag<String>
data class IntFlag constructor(
override val id: Int,
override val default: Int = 0,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
) : ParcelableFlag<Int> {
@@ -208,6 +230,8 @@ data class IntFlag constructor(
data class ResourceIntFlag constructor(
override val id: Int,
@IntegerRes override val resourceId: Int,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false
) : ResourceFlag<Int>
@@ -215,6 +239,8 @@ data class LongFlag constructor(
override val id: Int,
override val default: Long = 0,
override val teamfood: Boolean = false,
override val name: String = "",
override val namespace: String = "",
override val overridden: Boolean = false
) : ParcelableFlag<Long> {
@@ -240,6 +266,8 @@ data class LongFlag constructor(
data class FloatFlag constructor(
override val id: Int,
override val default: Float = 0f,
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
override val overridden: Boolean = false
) : ParcelableFlag<Float> {
@@ -266,13 +294,17 @@ data class FloatFlag constructor(
data class ResourceFloatFlag constructor(
override val id: Int,
override val resourceId: Int,
override val teamfood: Boolean = false
override val name: String = "",
override val namespace: String = "",
override val teamfood: Boolean = false,
) : ResourceFlag<Int>
data class DoubleFlag constructor(
override val id: Int,
override val default: Double = 0.0,
override val teamfood: Boolean = false,
override val name: String = "",
override val namespace: String = "",
override val overridden: Boolean = false
) : ParcelableFlag<Double> {

View File

@@ -33,9 +33,6 @@ interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: ResourceBooleanFlag): Boolean
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: SysPropBooleanFlag): Boolean

View File

@@ -39,7 +39,6 @@ import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
@@ -76,7 +75,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
@@ -99,7 +97,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
Restarter restarter) {
@@ -108,7 +105,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
mRestarter = restarter;
@@ -140,7 +136,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
mBooleanFlagCache.put(id,
readFlagValue(id, flag.getDefault()));
readBooleanFlagInternal(flag, flag.getDefault()));
}
return mBooleanFlagCache.get(id);
@@ -151,19 +147,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
mBooleanFlagCache.put(id,
readFlagValue(id, mResources.getBoolean(flag.getResourceId())));
}
return mBooleanFlagCache.get(id);
}
@Override
public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
flag.getName(), flag.getDefault());
mBooleanFlagCache.put(id, readFlagValue(id, deviceConfigValue));
readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId())));
}
return mBooleanFlagCache.get(id);
@@ -179,7 +163,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
id,
mSystemProperties.getBoolean(
flag.getName(),
readFlagValue(id, flag.getDefault())));
readBooleanFlagInternal(flag, flag.getDefault())));
}
return mBooleanFlagCache.get(id);
@@ -191,7 +175,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
int id = flag.getId();
if (!mStringFlagCache.containsKey(id)) {
mStringFlagCache.put(id,
readFlagValue(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
}
return mStringFlagCache.get(id);
@@ -203,20 +187,21 @@ public class FeatureFlagsDebug implements FeatureFlags {
int id = flag.getId();
if (!mStringFlagCache.containsKey(id)) {
mStringFlagCache.put(id,
readFlagValue(id, mResources.getString(flag.getResourceId()),
readFlagValueInternal(id, mResources.getString(flag.getResourceId()),
StringFlagSerializer.INSTANCE));
}
return mStringFlagCache.get(id);
}
@NonNull
@Override
public int getInt(@NonNull IntFlag flag) {
int id = flag.getId();
if (!mIntFlagCache.containsKey(id)) {
mIntFlagCache.put(id,
readFlagValue(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
}
return mIntFlagCache.get(id);
@@ -228,27 +213,31 @@ public class FeatureFlagsDebug implements FeatureFlags {
int id = flag.getId();
if (!mIntFlagCache.containsKey(id)) {
mIntFlagCache.put(id,
readFlagValue(id, mResources.getInteger(flag.getResourceId()),
readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()),
IntFlagSerializer.INSTANCE));
}
return mIntFlagCache.get(id);
}
/** Specific override for Boolean flags that checks against the teamfood list. */
private boolean readFlagValue(int id, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(id);
boolean hasServerOverride = mServerFlagReader.hasOverride(id);
/** Specific override for Boolean flags that checks against the teamfood list.*/
private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(flag.getId());
boolean hasServerOverride = mServerFlagReader.hasOverride(
flag.getNamespace(), flag.getName());
// Only check for teamfood if the default is false
// and there is no server override.
if (!hasServerOverride && !defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
return isEnabled(Flags.TEAMFOOD);
}
if (!hasServerOverride
&& !defaultValue
&& result == null
&& flag.getId() != Flags.TEAMFOOD.getId()
&& flag.getTeamfood()) {
return isEnabled(Flags.TEAMFOOD);
}
return result == null ? mServerFlagReader.readServerOverride(id, defaultValue) : result;
return result == null ? mServerFlagReader.readServerOverride(
flag.getNamespace(), flag.getName(), defaultValue) : result;
}
private Boolean readBooleanFlagOverride(int id) {
@@ -256,7 +245,8 @@ public class FeatureFlagsDebug implements FeatureFlags {
}
@NonNull
private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
private <T> T readFlagValueInternal(
int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
requireNonNull(defaultValue, "defaultValue");
T result = readFlagValueInternal(id, serializer);
return result == null ? defaultValue : result;
@@ -355,8 +345,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof DeviceConfigBooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof SysPropBooleanFlag) {
// Store SysProp flags in SystemProperties where they can read by outside parties.
mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
@@ -474,9 +462,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
} else if (f instanceof ResourceBooleanFlag) {
enabled = isEnabled((ResourceBooleanFlag) f);
overridden = readBooleanFlagOverride(f.getId()) != null;
} else if (f instanceof DeviceConfigBooleanFlag) {
enabled = isEnabled((DeviceConfigBooleanFlag) f);
overridden = false;
} else if (f instanceof SysPropBooleanFlag) {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
enabled = isEnabled((SysPropBooleanFlag) f);
@@ -489,9 +474,11 @@ public class FeatureFlagsDebug implements FeatureFlags {
}
if (enabled) {
return new ReleasedFlag(f.getId(), teamfood, overridden);
return new ReleasedFlag(
f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
} else {
return new UnreleasedFlag(f.getId(), teamfood, overridden);
return new UnreleasedFlag(
f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
}
}
};

View File

@@ -101,7 +101,7 @@ public class FeatureFlagsRelease implements FeatureFlags {
@Override
public boolean isEnabled(@NotNull ReleasedFlag flag) {
return mServerFlagReader.readServerOverride(flag.getId(), true);
return mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true);
}
@Override
@@ -114,18 +114,6 @@ public class FeatureFlagsRelease implements FeatureFlags {
return mBooleanCache.valueAt(cacheIndex);
}
@Override
public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
if (cacheIndex < 0) {
boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
flag.getName(), flag.getDefault());
return isEnabled(flag.getId(), deviceConfigValue);
}
return mBooleanCache.valueAt(cacheIndex);
}
@Override
public boolean isEnabled(SysPropBooleanFlag flag) {
int cacheIndex = mBooleanCache.indexOfKey(flag.getId());

View File

@@ -264,11 +264,10 @@ object Flags {
@Keep
@JvmField
val WM_ENABLE_PARTIAL_SCREEN_SHARING =
DeviceConfigBooleanFlag(
UnreleasedFlag(
1102,
"record_task_content",
DeviceConfig.NAMESPACE_WINDOW_MANAGER,
false,
name = "record_task_content",
namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
teamfood = true
)

View File

@@ -27,11 +27,10 @@ import javax.inject.Inject
interface ServerFlagReader {
/** Returns true if there is a server-side setting stored. */
fun hasOverride(flagId: Int): Boolean
fun hasOverride(namespace: String, name: String): Boolean
/** Returns any stored server-side setting or the default if not set. */
fun readServerOverride(flagId: Int, default: Boolean): Boolean
fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean
/** Register a listener for changes to any of the passed in flags. */
fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
@@ -68,19 +67,19 @@ class ServerFlagReaderImpl @Inject constructor(
}
}
override fun hasOverride(flagId: Int): Boolean =
deviceConfig.getProperty(
override fun hasOverride(namespace: String, name: String): Boolean =
!namespace.isBlank() && !name.isBlank() && deviceConfig.getProperty(
namespace,
getServerOverrideName(flagId)
name
) != null
override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
return deviceConfig.getBoolean(
override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean =
!namespace.isBlank() && !name.isBlank() && deviceConfig.getBoolean(
namespace,
getServerOverrideName(flagId),
name,
default
)
}
override fun listenForChanges(
flags: Collection<Flag<*>>,
@@ -121,24 +120,24 @@ interface ServerFlagReaderModule {
}
class ServerFlagReaderFake : ServerFlagReader {
private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
private val flagMap: MutableMap<String, Boolean> = mutableMapOf()
private val listeners =
mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
override fun hasOverride(flagId: Int): Boolean {
return flagMap.containsKey(flagId)
override fun hasOverride(namespace: String, name: String): Boolean {
return flagMap.containsKey(name)
}
override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
return flagMap.getOrDefault(flagId, default)
override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean {
return flagMap.getOrDefault(name, default)
}
fun setFlagValue(flagId: Int, value: Boolean) {
flagMap.put(flagId, value)
fun setFlagValue(namespace: String, name: String, value: Boolean) {
flagMap.put(name, value)
for ((listener, flags) in listeners) {
flagLoop@ for (flag in flags) {
if (flagId == flag.id) {
if (name == flag.name) {
listener.onChange()
break@flagLoop
}
@@ -146,8 +145,8 @@ class ServerFlagReaderFake : ServerFlagReader {
}
}
fun eraseFlag(flagId: Int) {
flagMap.remove(flagId)
fun eraseFlag(namespace: String, name: String) {
flagMap.remove(name)
}
override fun listenForChanges(

View File

@@ -31,10 +31,6 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.Serializable
import java.io.StringWriter
import java.util.function.Consumer
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -46,8 +42,12 @@ import org.mockito.Mockito.inOrder
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import java.io.PrintWriter
import java.io.Serializable
import java.io.StringWriter
import java.util.function.Consumer
import org.mockito.Mockito.`when` as whenever
/**
* NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
@@ -70,8 +70,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
private val serverFlagReader = ServerFlagReaderFake()
private val deviceConfig = DeviceConfigProxyFake()
private val teamfoodableFlagA = UnreleasedFlag(500, true)
private val teamfoodableFlagB = ReleasedFlag(501, true)
private val teamfoodableFlagA = UnreleasedFlag(500, teamfood = true)
private val teamfoodableFlagB = ReleasedFlag(501, teamfood = true)
@Before
fun setup() {
@@ -84,7 +84,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
secureSettings,
systemProperties,
resources,
deviceConfig,
serverFlagReader,
flagMap,
restarter
@@ -191,21 +190,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e"))).isFalse()
}
@Test
fun readDeviceConfigBooleanFlag() {
val namespace = "test_namespace"
deviceConfig.setProperty(namespace, "a", "true", false)
deviceConfig.setProperty(namespace, "b", "false", false)
deviceConfig.setProperty(namespace, "c", null, false)
assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
.isTrue()
assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
.isFalse()
assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
.isFalse()
}
@Test
fun readStringFlag() {
whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
@@ -379,7 +363,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
fun serverSide_Overrides_MakesFalse() {
val flag = ReleasedFlag(100)
serverFlagReader.setFlagValue(flag.id, false)
serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse()
}
@@ -388,7 +372,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
fun serverSide_Overrides_MakesTrue() {
val flag = UnreleasedFlag(100)
serverFlagReader.setFlagValue(flag.id, true)
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
}

View File

@@ -25,8 +25,8 @@ import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
/**
* NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
@@ -82,21 +82,6 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
}
}
@Test
fun testReadDeviceConfigBooleanFlag() {
val namespace = "test_namespace"
deviceConfig.setProperty(namespace, "a", "true", false)
deviceConfig.setProperty(namespace, "b", "false", false)
deviceConfig.setProperty(namespace, "c", null, false)
assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
.isTrue()
assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
.isFalse()
assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
.isFalse()
}
@Test
fun testSysPropBooleanFlag() {
val flagId = 213
@@ -112,7 +97,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
fun serverSide_OverridesReleased_MakesFalse() {
val flag = ReleasedFlag(100)
serverFlagReader.setFlagValue(flag.id, false)
serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
}
@@ -121,7 +106,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
fun serverSide_OverridesUnreleased_Ignored() {
val flag = UnreleasedFlag(100)
serverFlagReader.setFlagValue(flag.id, true)
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
}

View File

@@ -87,8 +87,6 @@ class FakeFeatureFlags : FeatureFlags {
override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
override fun getString(flag: StringFlag): String = requireStringValue(flag.id)