diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp index 1ad9053851..e2fadb05fb 100644 --- a/tools/aconfig/Android.bp +++ b/tools/aconfig/Android.bp @@ -58,6 +58,7 @@ rust_defaults { "libaconfig_protos", "libanyhow", "libclap", + "libitertools", "libprotobuf", "libserde", "libserde_json", diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml index 941b30d909..2edf4b89cc 100644 --- a/tools/aconfig/Cargo.toml +++ b/tools/aconfig/Cargo.toml @@ -11,6 +11,7 @@ cargo = [] [dependencies] anyhow = "1.0.69" clap = { version = "4.1.8", features = ["derive"] } +itertools = "0.10.5" paste = "1.0.11" protobuf = "3.2.0" serde = { version = "1.0.152", features = ["derive"] } diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs index 9e77b45d1c..33e54a1f3b 100644 --- a/tools/aconfig/src/codegen_cpp.rs +++ b/tools/aconfig/src/codegen_cpp.rs @@ -32,8 +32,9 @@ where I: Iterator, { let mut readwrite_count = 0; - let class_elements: Vec = - parsed_flags_iter.map(|pf| create_class_element(package, pf, &mut readwrite_count)).collect(); + let class_elements: Vec = parsed_flags_iter + .map(|pf| create_class_element(package, pf, &mut readwrite_count)) + .collect(); let readwrite = readwrite_count > 0; let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only); let header = package.replace('.', "_"); @@ -110,7 +111,9 @@ pub struct ClassElement { fn create_class_element(package: &str, pf: &ProtoParsedFlag, rw_count: &mut i32) -> ClassElement { ClassElement { readwrite_idx: if pf.permission() == ProtoFlagPermission::READ_WRITE { - let index = *rw_count; *rw_count += 1; index + let index = *rw_count; + *rw_count += 1; + index } else { -1 }, @@ -162,6 +165,8 @@ public: virtual bool disabled_rw() = 0; + virtual bool disabled_rw_2() = 0; + virtual bool enabled_fixed_ro() = 0; virtual bool enabled_ro() = 0; @@ -179,6 +184,10 @@ inline bool disabled_rw() { return provider_->disabled_rw(); } +inline bool disabled_rw_2() { + return provider_->disabled_rw_2(); +} + inline bool enabled_fixed_ro() { return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO; } @@ -200,6 +209,8 @@ bool com_android_aconfig_test_disabled_ro(); bool com_android_aconfig_test_disabled_rw(); +bool com_android_aconfig_test_disabled_rw_2(); + bool com_android_aconfig_test_enabled_fixed_ro(); bool com_android_aconfig_test_enabled_ro(); @@ -233,6 +244,10 @@ public: virtual void disabled_rw(bool val) = 0; + virtual bool disabled_rw_2() = 0; + + virtual void disabled_rw_2(bool val) = 0; + virtual bool enabled_fixed_ro() = 0; virtual void enabled_fixed_ro(bool val) = 0; @@ -266,6 +281,14 @@ inline void disabled_rw(bool val) { provider_->disabled_rw(val); } +inline bool disabled_rw_2() { + return provider_->disabled_rw_2(); +} + +inline void disabled_rw_2(bool val) { + provider_->disabled_rw_2(val); +} + inline bool enabled_fixed_ro() { return provider_->enabled_fixed_ro(); } @@ -307,6 +330,10 @@ bool com_android_aconfig_test_disabled_rw(); void set_com_android_aconfig_test_disabled_rw(bool val); +bool com_android_aconfig_test_disabled_rw_2(); + +void set_com_android_aconfig_test_disabled_rw_2(bool val); + bool com_android_aconfig_test_enabled_fixed_ro(); void set_com_android_aconfig_test_enabled_fixed_ro(bool val); @@ -352,6 +379,16 @@ namespace com::android::aconfig::test { return cache_[0]; } + virtual bool disabled_rw_2() override { + if (cache_[1] == -1) { + cache_[1] = server_configurable_flags::GetServerConfigurableFlag( + "aconfig_flags.other_namespace", + "com.android.aconfig.test.disabled_rw_2", + "false") == "true"; + } + return cache_[1]; + } + virtual bool enabled_fixed_ro() override { return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO; } @@ -361,18 +398,18 @@ namespace com::android::aconfig::test { } virtual bool enabled_rw() override { - if (cache_[1] == -1) { - cache_[1] = server_configurable_flags::GetServerConfigurableFlag( + if (cache_[2] == -1) { + cache_[2] = server_configurable_flags::GetServerConfigurableFlag( "aconfig_flags.aconfig_test", "com.android.aconfig.test.enabled_rw", "true") == "true"; } - return cache_[1]; + return cache_[2]; } }; - std::vector cache_ = std::vector(2, -1); + std::vector cache_ = std::vector(3, -1); std::unique_ptr provider_ = std::make_unique(); @@ -386,6 +423,10 @@ bool com_android_aconfig_test_disabled_rw() { return com::android::aconfig::test::disabled_rw(); } +bool com_android_aconfig_test_disabled_rw_2() { + return com::android::aconfig::test::disabled_rw_2(); +} + bool com_android_aconfig_test_enabled_fixed_ro() { return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO; } @@ -446,6 +487,22 @@ namespace com::android::aconfig::test { overrides_["disabled_rw"] = val; } + virtual bool disabled_rw_2() override { + auto it = overrides_.find("disabled_rw_2"); + if (it != overrides_.end()) { + return it->second; + } else { + return server_configurable_flags::GetServerConfigurableFlag( + "aconfig_flags.other_namespace", + "com.android.aconfig.test.disabled_rw_2", + "false") == "true"; + } + } + + virtual void disabled_rw_2(bool val) override { + overrides_["disabled_rw_2"] = val; + } + virtual bool enabled_fixed_ro() override { auto it = overrides_.find("enabled_fixed_ro"); if (it != overrides_.end()) { @@ -515,6 +572,15 @@ void set_com_android_aconfig_test_disabled_rw(bool val) { com::android::aconfig::test::disabled_rw(val); } +bool com_android_aconfig_test_disabled_rw_2() { + return com::android::aconfig::test::disabled_rw_2(); +} + + +void set_com_android_aconfig_test_disabled_rw_2(bool val) { + com::android::aconfig::test::disabled_rw_2(val); +} + bool com_android_aconfig_test_enabled_fixed_ro() { return com::android::aconfig::test::enabled_fixed_ro(); diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs index 05ee0d759f..5470cb3e36 100644 --- a/tools/aconfig/src/codegen_java.rs +++ b/tools/aconfig/src/codegen_java.rs @@ -16,7 +16,7 @@ use anyhow::Result; use serde::Serialize; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; use tinytemplate::TinyTemplate; @@ -34,12 +34,14 @@ where { let flag_elements: Vec = parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect(); + let namespace_flags = gen_flags_by_namespace(&flag_elements); let properties_set: BTreeSet = flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect(); let is_read_write = flag_elements.iter().any(|elem| elem.is_read_write); let is_test_mode = codegen_mode == CodegenMode::Test; let context = Context { flag_elements, + namespace_flags, is_test_mode, is_read_write, properties_set, @@ -72,16 +74,44 @@ where .collect::>>() } +fn gen_flags_by_namespace(flags: &[FlagElement]) -> Vec { + let mut namespace_to_flag: BTreeMap> = BTreeMap::new(); + + for flag in flags { + match namespace_to_flag.get_mut(&flag.device_config_namespace) { + Some(flag_list) => flag_list.push(flag.clone()), + None => { + namespace_to_flag.insert(flag.device_config_namespace.clone(), vec![flag.clone()]); + } + } + } + + namespace_to_flag + .iter() + .map(|(namespace, flags)| NamespaceFlags { + namespace: namespace.to_string(), + flags: flags.clone(), + }) + .collect() +} + #[derive(Serialize)] struct Context { pub flag_elements: Vec, + pub namespace_flags: Vec, pub is_test_mode: bool, pub is_read_write: bool, pub properties_set: BTreeSet, pub package_name: String, } -#[derive(Serialize)] +#[derive(Serialize, Debug)] +struct NamespaceFlags { + pub namespace: String, + pub flags: Vec, +} + +#[derive(Serialize, Clone, Debug)] struct FlagElement { pub default_value: bool, pub device_config_namespace: String, @@ -148,6 +178,8 @@ mod tests { boolean disabledRo(); @UnsupportedAppUsage boolean disabledRw(); + @UnsupportedAppUsage + boolean disabledRw2(); @com.android.aconfig.annotations.AssumeTrueForR8 @UnsupportedAppUsage boolean enabledFixedRo(); @@ -170,6 +202,8 @@ mod tests { /** @hide */ public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw"; /** @hide */ + public static final String FLAG_DISABLED_RW_2 = "com.android.aconfig.test.disabled_rw_2"; + /** @hide */ public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro"; /** @hide */ public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro"; @@ -185,6 +219,10 @@ mod tests { public static boolean disabledRw() { return FEATURE_FLAGS.disabledRw(); } + @UnsupportedAppUsage + public static boolean disabledRw2() { + return FEATURE_FLAGS.disabledRw2(); + } @com.android.aconfig.annotations.AssumeTrueForR8 @UnsupportedAppUsage public static boolean enabledFixedRo() { @@ -224,6 +262,11 @@ mod tests { } @Override @UnsupportedAppUsage + public boolean disabledRw2() { + return getValue(Flags.FLAG_DISABLED_RW_2); + } + @Override + @UnsupportedAppUsage public boolean enabledFixedRo() { return getValue(Flags.FLAG_ENABLED_FIXED_RO); } @@ -259,6 +302,7 @@ mod tests { Map.ofEntries( Map.entry(Flags.FLAG_DISABLED_RO, false), Map.entry(Flags.FLAG_DISABLED_RW, false), + Map.entry(Flags.FLAG_DISABLED_RW_2, false), Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), Map.entry(Flags.FLAG_ENABLED_RO, false), Map.entry(Flags.FLAG_ENABLED_RW, false) @@ -289,7 +333,52 @@ mod tests { import android.provider.DeviceConfig.Properties; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - private Properties mPropertiesAconfigTest; + private static boolean aconfig_test_is_cached = false; + private static boolean other_namespace_is_cached = false; + private static boolean disabledRw = false; + private static boolean disabledRw2 = false; + private static boolean enabledRw = true; + + + private void load_overrides_aconfig_test() { + try { + Properties properties = DeviceConfig.getProperties("aconfig_test"); + disabledRw = + properties.getBoolean("com.android.aconfig.test.disabled_rw", false); + enabledRw = + properties.getBoolean("com.android.aconfig.test.enabled_rw", true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace aconfig_test " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + aconfig_test_is_cached = true; + } + + private void load_overrides_other_namespace() { + try { + Properties properties = DeviceConfig.getProperties("other_namespace"); + disabledRw2 = + properties.getBoolean("com.android.aconfig.test.disabled_rw_2", false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace other_namespace " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + other_namespace_is_cached = true; + } + + @Override @UnsupportedAppUsage public boolean disabledRo() { @@ -298,18 +387,18 @@ mod tests { @Override @UnsupportedAppUsage public boolean disabledRw() { - if (mPropertiesAconfigTest == null) { - mPropertiesAconfigTest = - getProperties( - "aconfig_test", - "com.android.aconfig.test.disabled_rw" - ); + if (!aconfig_test_is_cached) { + load_overrides_aconfig_test(); } - return mPropertiesAconfigTest - .getBoolean( - "com.android.aconfig.test.disabled_rw", - false - ); + return disabledRw; + } + @Override + @UnsupportedAppUsage + public boolean disabledRw2() { + if (!other_namespace_is_cached) { + load_overrides_other_namespace(); + } + return disabledRw2; } @Override @UnsupportedAppUsage @@ -324,36 +413,10 @@ mod tests { @Override @UnsupportedAppUsage public boolean enabledRw() { - if (mPropertiesAconfigTest == null) { - mPropertiesAconfigTest = - getProperties( - "aconfig_test", - "com.android.aconfig.test.enabled_rw" - ); + if (!aconfig_test_is_cached) { + load_overrides_aconfig_test(); } - return mPropertiesAconfigTest - .getBoolean( - "com.android.aconfig.test.enabled_rw", - true - ); - } - private Properties getProperties( - String namespace, - String flagName) { - Properties properties = null; - try { - properties = DeviceConfig.getProperties(namespace); - } catch (NullPointerException e) { - throw new RuntimeException( - "Cannot read value of flag " + flagName + " from DeviceConfig. " - + "It could be that the code using flag executed " - + "before SettingsProvider initialization. " - + "Please use fixed read-only flag by adding " - + "is_fixed_read_only: true in flag declaration.", - e - ); - } - return properties; + return enabledRw; } } "#; @@ -426,6 +489,12 @@ mod tests { } @Override @UnsupportedAppUsage + public boolean disabledRw2() { + throw new UnsupportedOperationException( + "Method is not implemented."); + } + @Override + @UnsupportedAppUsage public boolean enabledFixedRo() { throw new UnsupportedOperationException( "Method is not implemented."); diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen_rust.rs index 78e62ba095..fb4f98191a 100644 --- a/tools/aconfig/src/codegen_rust.rs +++ b/tools/aconfig/src/codegen_rust.rs @@ -104,6 +104,12 @@ lazy_static::lazy_static! { "com.android.aconfig.test.disabled_rw", "false") == "true"; + /// flag value cache for disabled_rw_2 + static ref CACHED_disabled_rw_2: bool = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.other_namespace", + "com.android.aconfig.test.disabled_rw_2", + "false") == "true"; + /// flag value cache for enabled_rw static ref CACHED_enabled_rw: bool = flags_rust::GetServerConfigurableFlag( "aconfig_flags.aconfig_test", @@ -122,6 +128,11 @@ impl FlagProvider { *CACHED_disabled_rw } + /// query flag disabled_rw_2 + pub fn disabled_rw_2(&self) -> bool { + *CACHED_disabled_rw_2 + } + /// query flag enabled_fixed_ro pub fn enabled_fixed_ro(&self) -> bool { true @@ -153,6 +164,12 @@ pub fn disabled_rw() -> bool { PROVIDER.disabled_rw() } +/// query flag disabled_rw_2 +#[inline(always)] +pub fn disabled_rw_2() -> bool { + PROVIDER.disabled_rw_2() +} + /// query flag enabled_fixed_ro #[inline(always)] pub fn enabled_fixed_ro() -> bool { @@ -211,6 +228,21 @@ impl FlagProvider { self.overrides.insert("disabled_rw", val); } + /// query flag disabled_rw_2 + pub fn disabled_rw_2(&self) -> bool { + self.overrides.get("disabled_rw_2").copied().unwrap_or( + flags_rust::GetServerConfigurableFlag( + "aconfig_flags.other_namespace", + "com.android.aconfig.test.disabled_rw_2", + "false") == "true" + ) + } + + /// set flag disabled_rw_2 + pub fn set_disabled_rw_2(&mut self, val: bool) { + self.overrides.insert("disabled_rw_2", val); + } + /// query flag enabled_fixed_ro pub fn enabled_fixed_ro(&self) -> bool { self.overrides.get("enabled_fixed_ro").copied().unwrap_or( @@ -285,6 +317,18 @@ pub fn set_disabled_rw(val: bool) { PROVIDER.lock().unwrap().set_disabled_rw(val); } +/// query flag disabled_rw_2 +#[inline(always)] +pub fn disabled_rw_2() -> bool { + PROVIDER.lock().unwrap().disabled_rw_2() +} + +/// set flag disabled_rw_2 +#[inline(always)] +pub fn set_disabled_rw_2(val: bool) { + PROVIDER.lock().unwrap().set_disabled_rw_2(val); +} + /// query flag enabled_fixed_ro #[inline(always)] pub fn enabled_fixed_ro() -> bool { diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs index 7b051471aa..63aef29b07 100644 --- a/tools/aconfig/src/commands.rs +++ b/tools/aconfig/src/commands.rs @@ -334,7 +334,7 @@ mod tests { assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state()); assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission()); - assert_eq!(5, parsed_flags.parsed_flag.len()); + assert_eq!(6, parsed_flags.parsed_flag.len()); for pf in parsed_flags.parsed_flag.iter() { if pf.name() == "enabled_fixed_ro" { continue; @@ -433,7 +433,7 @@ mod tests { let input = parse_test_flags_as_input(); let bytes = create_device_config_defaults(input).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); - assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text); + assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\nother_namespace:com.android.aconfig.test.disabled_rw_2=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text); } #[test] @@ -441,7 +441,7 @@ mod tests { let input = parse_test_flags_as_input(); let bytes = create_device_config_sysprops(input).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); - assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text); + assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.disabled_rw_2=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text); } #[test] diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs index 9034704283..922d4c6e26 100644 --- a/tools/aconfig/src/test.rs +++ b/tools/aconfig/src/test.rs @@ -58,6 +58,26 @@ parsed_flag { } is_fixed_read_only: false } +parsed_flag { + package: "com.android.aconfig.test" + name: "disabled_rw_2" + namespace: "other_namespace" + description: "This flag is DISABLED + READ_WRITE" + bug: "999" + state: DISABLED + permission: READ_WRITE + trace { + source: "tests/test.aconfig" + state: DISABLED + permission: READ_WRITE + } + trace { + source: "tests/first.values" + state: DISABLED + permission: READ_WRITE + } + is_fixed_read_only: false +} parsed_flag { package: "com.android.aconfig.test" name: "enabled_fixed_ro" diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template index ff089dfdc8..ec8822c49c 100644 --- a/tools/aconfig/templates/FeatureFlagsImpl.java.template +++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template @@ -8,10 +8,41 @@ import android.provider.DeviceConfig.Properties; {{ endif }} /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags \{ -{{ if is_read_write- }} -{{ for properties in properties_set }} - private Properties {properties}; +{{- if is_read_write }} +{{- for namespace_with_flags in namespace_flags }} + private static boolean {namespace_with_flags.namespace}_is_cached = false; +{{- endfor- }} + +{{ for flag in flag_elements }} +{{- if flag.is_read_write }} + private static boolean {flag.method_name} = {flag.default_value}; +{{- endif- }} {{ endfor }} + +{{ for namespace_with_flags in namespace_flags }} + private void load_overrides_{namespace_with_flags.namespace}() \{ + try \{ + Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}"); + + {{- for flag in namespace_with_flags.flags }} + {{- if flag.is_read_write }} + {flag.method_name} = + properties.getBoolean("{flag.device_config_flag}", {flag.default_value}); + {{- endif- }} + {{ endfor }} + } catch (NullPointerException e) \{ + throw new RuntimeException( + "Cannot read value from namespace {namespace_with_flags.namespace} " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + {namespace_with_flags.namespace}_is_cached = true; + } +{{ endfor- }} {{ endif- }} {{ for flag in flag_elements }} @@ -19,45 +50,15 @@ public final class FeatureFlagsImpl implements FeatureFlags \{ @UnsupportedAppUsage public boolean {flag.method_name}() \{ {{ -if flag.is_read_write }} - if ({flag.properties} == null) \{ - {flag.properties} = - getProperties( - "{flag.device_config_namespace}", - "{flag.device_config_flag}" - ); + if (!{flag.device_config_namespace}_is_cached) \{ + load_overrides_{flag.device_config_namespace}(); } - return {flag.properties} - .getBoolean( - "{flag.device_config_flag}", - {flag.default_value} - ); + return {flag.method_name}; {{ else }} return {flag.default_value}; {{ endif- }} } {{ endfor }} - -{{ -if is_read_write }} - private Properties getProperties( - String namespace, - String flagName) \{ - Properties properties = null; - try \{ - properties = DeviceConfig.getProperties(namespace); - } catch (NullPointerException e) \{ - throw new RuntimeException( - "Cannot read value of flag " + flagName + " from DeviceConfig. " - + "It could be that the code using flag executed " - + "before SettingsProvider initialization. " - + "Please use fixed read-only flag by adding " - + "is_fixed_read_only: true in flag declaration.", - e - ); - } - - return properties; - } -{{ endif- }} } {{ else }} {#- Generate only stub if in test mode #} @@ -70,6 +71,6 @@ public final class FeatureFlagsImpl implements FeatureFlags \{ throw new UnsupportedOperationException( "Method is not implemented."); } -{{ endfor }} +{{ endfor- }} } {{ endif }} diff --git a/tools/aconfig/tests/first.values b/tools/aconfig/tests/first.values index a450f7869e..448fb3a66c 100644 --- a/tools/aconfig/tests/first.values +++ b/tools/aconfig/tests/first.values @@ -16,6 +16,12 @@ flag_value { state: ENABLED permission: READ_WRITE } +flag_value { + package: "com.android.aconfig.test" + name: "disabled_rw_2" + state: DISABLED + permission: READ_WRITE +} flag_value { package: "com.android.aconfig.test" name: "enabled_fixed_ro" diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/tests/test.aconfig index aaa6df585e..3076f5ee66 100644 --- a/tools/aconfig/tests/test.aconfig +++ b/tools/aconfig/tests/test.aconfig @@ -51,3 +51,10 @@ flag { bug: "" is_fixed_read_only: true } + +flag { + name: "disabled_rw_2" + namespace: "other_namespace" + description: "This flag is DISABLED + READ_WRITE" + bug: "999" +}